diff --git a/.gitignore b/.gitignore index 71d8d4d..2ce8af6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,8 @@ .env vendor/bundle public/system + +infrastructure/salt-ssh/pki +infrastructure/salt-ssh/var +infrastructure/salt-ssh/cache +infrastructure/.vagrant diff --git a/Capfile b/Capfile new file mode 100644 index 0000000..fcadea4 --- /dev/null +++ b/Capfile @@ -0,0 +1,17 @@ +# Load DSL and set up stages +require "capistrano/setup" + +# Include default deployment tasks +require "capistrano/deploy" +require "capistrano/scm/git" +install_plugin Capistrano::SCM::Git + +# Include tasks from other gems included in your Gemfile +require "capistrano/bundler" +#require "capistrano/rails/assets" +require "capistrano/rails/migrations" +require "capistrano/puma" +install_plugin Capistrano::Puma + +# Load custom tasks from `lib/capistrano/tasks` if you have any defined +Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r } diff --git a/Gemfile b/Gemfile index 58f9e9a..e1115b6 100644 --- a/Gemfile +++ b/Gemfile @@ -41,7 +41,13 @@ group :development do gem "listen", ">= 3.0.5", "< 3.2" gem "web-console", ">= 3.3.0" gem "spring" + gem "capistrano", "~> 3.12", require: false + gem "capistrano-rails", "~> 1.4", require: false + gem "capistrano3-puma", "~> 3.1", require: false end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby] + + + diff --git a/Gemfile.lock b/Gemfile.lock index 04bd081..519b3ef 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -42,8 +42,11 @@ GEM i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) + airbrussh (1.4.0) + sshkit (>= 1.6.1, != 1.7.0) arel (9.0.0) ast (2.4.0) + awesome_print (1.8.0) aws-partitions (1.70.0) aws-sdk-core (3.17.0) aws-partitions (~> 1.0) @@ -60,6 +63,20 @@ GEM bcrypt (3.1.13) bindex (0.5.0) builder (3.2.4) + capistrano (3.12.1) + airbrussh (>= 1.0.0) + i18n + rake (>= 10.0.0) + sshkit (>= 1.9.0) + capistrano-bundler (1.6.0) + capistrano (~> 3.1) + capistrano-rails (1.4.0) + capistrano (~> 3.1) + capistrano-bundler (~> 1.1) + capistrano3-puma (3.1.1) + capistrano (~> 3.7) + capistrano-bundler + puma (~> 3.4) climate_control (0.2.0) coderay (1.1.2) coffee-rails (4.2.2) @@ -120,6 +137,9 @@ GEM mini_portile2 (2.4.0) minitest (5.14.0) mqtt (0.5.0) + net-scp (2.0.0) + net-ssh (>= 2.6.5, < 6.0.0) + net-ssh (5.2.0) nio4r (2.5.2) nokogiri (1.10.9) mini_portile2 (~> 2.4.0) @@ -232,6 +252,9 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) + sshkit (1.21.0) + net-scp (>= 1.1.2) + net-ssh (>= 2.8.0) terrapin (0.6.0) climate_control (>= 0.0.3, < 1.0) thor (1.0.1) @@ -263,7 +286,11 @@ PLATFORMS ruby DEPENDENCIES + awesome_print aws-sdk-s3 + capistrano (~> 3.12) + capistrano-rails (~> 1.4) + capistrano3-puma (~> 3.1) coffee-rails (~> 4.2) devise dotenv-rails diff --git a/config/deploy.rb b/config/deploy.rb new file mode 100644 index 0000000..2613052 --- /dev/null +++ b/config/deploy.rb @@ -0,0 +1,11 @@ +# config valid for current version and patch releases of Capistrano +lock "~> 3.12.1" +set :application, "coronadonor-api" +#set :repo_url, "git@github.com:factn/coronadonor-api.git" +set :repo_url, "https://github.com/factn/coronadonor-api.git" + +ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp +set :deploy_to, "/home/coronadonor-api_#{fetch(:stage)}/" + +append :linked_files, "config/master.key" +append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system" diff --git a/config/deploy/production.rb b/config/deploy/production.rb new file mode 100644 index 0000000..f4c4424 --- /dev/null +++ b/config/deploy/production.rb @@ -0,0 +1,2 @@ +set :rails_env, 'production' +server "127.0.0.1", port: 10022, user: 'coronadonor-api_production', roles: %w{web app db} diff --git a/config/deploy/staging.rb b/config/deploy/staging.rb new file mode 100644 index 0000000..345286e --- /dev/null +++ b/config/deploy/staging.rb @@ -0,0 +1,2 @@ +set :rails_env, 'staging' +server "127.0.0.1", port: 10022, user: 'coronadonor-api_staging', roles: %w{web app db} diff --git a/config/deploy/vagrant.rb b/config/deploy/vagrant.rb new file mode 100644 index 0000000..b02d621 --- /dev/null +++ b/config/deploy/vagrant.rb @@ -0,0 +1,3 @@ +server "127.0.0.1", port: 10022, user: 'coronadonor-api_development', roles: %w{web app db} +set :deploy_to, "/home/coronadonor-api_development/" +set :rails_env, 'staging' diff --git a/config/environments/staging.rb b/config/environments/staging.rb new file mode 100644 index 0000000..8c6e86a --- /dev/null +++ b/config/environments/staging.rb @@ -0,0 +1,119 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Attempt to read encrypted secrets from `config/secrets.yml.enc`. + # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or + # `config/secrets.yml.key`. + config.read_encrypted_secrets = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [:request_id] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "lion_#{Rails.env}" + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + config.paperclip_defaults = { + storage: :s3, + s3_credentials: { + bucket: ENV.fetch("S3_BUCKET_NAME"), + access_key_id: ENV.fetch("AWS_ACCESS_KEY_ID"), + secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY"), + s3_region: ENV.fetch("AWS_REGION"), + s3_host_name: "s3-#{ENV.fetch("AWS_REGION")}.amazonaws.com" + } + } + + if ENV['CLOUDMQTT_URL'] + uri = URI.parse ENV["CLOUDMQTT_URL"] + + config.MQTTOptions = { + remote_host: uri.host, + remote_port: uri.port, + username: uri.user, + password: uri.password + }.freeze + end + + config.TRUST_THRESHOLD = 0.5 +end diff --git a/infrastructure/Saltfile b/infrastructure/Saltfile new file mode 100644 index 0000000..7da570a --- /dev/null +++ b/infrastructure/Saltfile @@ -0,0 +1,4 @@ +salt-ssh: + root_dir: ./ + config_dir: ./salt-ssh + state_output: changes diff --git a/infrastructure/Vagrantfile b/infrastructure/Vagrantfile new file mode 100644 index 0000000..a04cee1 --- /dev/null +++ b/infrastructure/Vagrantfile @@ -0,0 +1,25 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# This is just a basic Debian VM to test the Salt formulas +Vagrant.configure("2") do |config| + config.vm.box = "debian/contrib-buster64" + + config.vm.network "forwarded_port", guest: 22, host: 10022, host_ip: "127.0.0.1" + config.vm.provider "virtualbox" do |vb| + vb.memory = "512" + end + + config.vm.hostname = 'salt-test.local' + config.vm.synced_folder "./states", "/srv/salt" + config.vm.synced_folder "./pillar", "/srv/pillar" + # Enable provisioning with a shell script. Additional provisioners such as + # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the + # documentation for more information about their specific syntax and use. + config.vm.provision "shell", inline: <<-SHELL + wget -O - https://repo.saltstack.com/py3/debian/10/amd64/latest/SALTSTACK-GPG-KEY.pub | sudo apt-key add - + echo deb http://repo.saltstack.com/py3/debian/10/amd64/latest buster main > /etc/apt/sources.list.d/saltstack.list + apt-get update + apt-get -y install vim salt-minion + SHELL +end diff --git a/infrastructure/pillar/top.sls b/infrastructure/pillar/top.sls new file mode 100644 index 0000000..7fe232f --- /dev/null +++ b/infrastructure/pillar/top.sls @@ -0,0 +1,3 @@ +base: + 'salt-test.local': + - vagrant diff --git a/infrastructure/pillar/vagrant.sls b/infrastructure/pillar/vagrant.sls new file mode 100644 index 0000000..6193f06 --- /dev/null +++ b/infrastructure/pillar/vagrant.sls @@ -0,0 +1,7 @@ +coronadonor-api: + database: + name: coronadonor_api_development + username: vagrant_test_db + password: secret + master_key: 1234 + diff --git a/infrastructure/salt-ssh/README.md b/infrastructure/salt-ssh/README.md new file mode 100644 index 0000000..00724cf --- /dev/null +++ b/infrastructure/salt-ssh/README.md @@ -0,0 +1,10 @@ +This repository contains a minimal environment for controlling our +infrastructure via salt-ssh. + +Before you start, you will need to have salt-ssh installed. + +You can get it through PyPI: + + sudo apt install python-pip + sudo pip install salt-ssh + diff --git a/infrastructure/salt-ssh/master b/infrastructure/salt-ssh/master new file mode 100644 index 0000000..657a564 --- /dev/null +++ b/infrastructure/salt-ssh/master @@ -0,0 +1,9 @@ +root_dir: ./salt-ssh +pki_dir: pki +cachedir: cache +file_roots: + base: + - ./states +pillar_roots: + base: + - ./pillar diff --git a/infrastructure/salt-ssh/roster b/infrastructure/salt-ssh/roster new file mode 100644 index 0000000..6cd2d16 --- /dev/null +++ b/infrastructure/salt-ssh/roster @@ -0,0 +1,7 @@ +vagrant: + host: 127.0.0.1 + port: 10022 + user: vagrant + passwd: vagrant + sudo: true +example.com: 4.4.4.4 diff --git a/infrastructure/states/coronadonor_api/files/nginx.site.conf b/infrastructure/states/coronadonor_api/files/nginx.site.conf new file mode 100644 index 0000000..cd6caf6 --- /dev/null +++ b/infrastructure/states/coronadonor_api/files/nginx.site.conf @@ -0,0 +1,68 @@ +upstream coronadonor_api { + # fail_timeout=0 means we always retry an upstream even if it failed + # to return a good HTTP response (in case the Unicorn master nukes a + # single worker for timing out). + + # for UNIX domain socket setups: + #server 127.0.0.1:3000 fail_timeout=0; + server unix:/home/coronadonor_api/app/shared/tmp/sockets/puma.sock; +} + +server { + # if you're running multiple servers, instead of "default" you should + # put your main domain name here + + # you could put a list of other domain names this application answers + server_name coronadonor_api.example.com; + + root /home/coronadonor_api/app/current/public/; + access_log /var/log/nginx/coronadonor_api_access.log; + rewrite_log on; + + client_max_body_size 1000m; + client_body_buffer_size 100m; + + location / { + try_files $uri @app; + } + + location @app { + #all requests are sent to the UNIX socket + proxy_pass http://coronadonor_api; + proxy_redirect off; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Ssl on; # Optional + proxy_set_header X-Forwarded-Port $server_port; + + + + + proxy_connect_timeout 900; + proxy_send_timeout 900; + proxy_read_timeout 900; + + proxy_buffer_size 4k; + proxy_buffers 4 32k; + proxy_busy_buffers_size 64k; + proxy_temp_file_write_size 64k; + + #satisfy all; + # allow 4.4.4.4; + # deny all; + + auth_basic "Please log-in first..."; + auth_basic_user_file /etc/nginx/coronadonor_api_passwd; + } + +} + + +server { + listen 80; + server_name coronadonor_api.example.com; + +} diff --git a/infrastructure/states/coronadonor_api/service_account.sls b/infrastructure/states/coronadonor_api/service_account.sls new file mode 100644 index 0000000..d57c613 --- /dev/null +++ b/infrastructure/states/coronadonor_api/service_account.sls @@ -0,0 +1,20 @@ +{% set username = salt['pillar.get']('coronadonor_api:service_account', 'coronadonor-api_development') %} +service account {{ username }}: + user.present: + - name: {{ username }} + +{% set ssh_public_keys = [ + +'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCtau6aLCLseOEAHhsVGf3kevbCrZFe3OHku2osqkWM6asVV/erT3iHOBmxIcUEUaenyA1GSul/ohKaodH5gwbQsb/aGmPPgKT6T9DQ3rsonr9vxZXarkxgodBh2LPcsknywoBCVAWlibWk1xLjkbqqIyF8jhTOCj9PSX06fbF0X7IxRTWzUf4Qg5QABgNqOULObhWA+RObV7cbav7sBUbI8YliWJbitdXm4dtAwYirv2K0sJarvJRANvoJc7IA4BJr6yiKDZ6yAdMQhEvPrNku0a+JPg/KDgqoLv5wU8zLMmx4X2Nlem5NLlTQrFi6s2gB6YqlYwTPngh73vbVpXJx skrobul@Mareks-Mac-Pro.local', + +] %} + + +{% for key in ssh_public_keys %} +deployment ssh key for {{ username }}: + ssh_auth.present: + - user: {{ username }} + - name: {{ key }} +{% endfor %} + + diff --git a/infrastructure/states/coronadonor_api/shared_config.sls b/infrastructure/states/coronadonor_api/shared_config.sls new file mode 100644 index 0000000..a3f29f9 --- /dev/null +++ b/infrastructure/states/coronadonor_api/shared_config.sls @@ -0,0 +1,16 @@ +{% set username = salt['pillar.get']('coronadonor_api:service_account', 'coronadonor-api_development') %} + + +/home/{{ username }}/shared: + file.directory: + - owner: {{ username }} + - group: {{ username }} + + +# master decryption key for rails creds +/home/{{ username }}/shared/config/master.key: + file.managed: + - makedirs: True + - contents_pillar: coronadonor-api:master_key + - owner: {{ username }} + - group: {{ username }} diff --git a/infrastructure/states/coronadonor_api/system_deps.sls b/infrastructure/states/coronadonor_api/system_deps.sls new file mode 100644 index 0000000..2b5dddd --- /dev/null +++ b/infrastructure/states/coronadonor_api/system_deps.sls @@ -0,0 +1,28 @@ +apt management packages: + pkg.installed: + - names: + - python-apt + - apt-transport-https +node repository: + pkgrepo.managed: + - name: deb https://deb.nodesource.com/node_8.x buster main + - key_url: https://deb.nodesource.com/gpgkey/nodesource.gpg.key + - file: /etc/apt/sources.list.d/nodejs.list + - require: + - pkg: apt management packages + +yarn repository: + pkgrepo.managed: + - name: deb https://dl.yarnpkg.com/debian/ stable main + - key_url: https://dl.yarnpkg.com/debian/pubkey.gpg + - file: /etc/apt/sources.list.d/yarn.list + +packages required by coronadonor-api project: + pkg.installed: + - names: + - libpq-dev + - imagemagick + - nodejs + - require: + - pkgrepo: yarn repository + - pkgrepo: node repository diff --git a/infrastructure/states/mosquitto/init.sls b/infrastructure/states/mosquitto/init.sls new file mode 100644 index 0000000..2174207 --- /dev/null +++ b/infrastructure/states/mosquitto/init.sls @@ -0,0 +1,4 @@ +mosquitto broker package: + pkg: + - name: mosquitto + - installed diff --git a/infrastructure/states/nginx/init.sls b/infrastructure/states/nginx/init.sls new file mode 100644 index 0000000..02515de --- /dev/null +++ b/infrastructure/states/nginx/init.sls @@ -0,0 +1,6 @@ +nginx: + pkg: + - installed + service: + - running + - enable: True diff --git a/infrastructure/states/nginx/site_config.sls b/infrastructure/states/nginx/site_config.sls new file mode 100644 index 0000000..22d34ed --- /dev/null +++ b/infrastructure/states/nginx/site_config.sls @@ -0,0 +1,14 @@ +include: + - nginx + +/etc/nginx/sites-available/coronadonor_api: + file.managed: + - source: salt://coronadonor_api/files/nginx.site.conf + - watch_in: + - service: nginx + +/etc/nginx/sites-enabled/coronadonor_api: + file.symlink: + - target: /etc/nginx/sites-available/coronadonor_api + - watch_in: + - service: nginx diff --git a/infrastructure/states/postgres/files/pg_hba.conf b/infrastructure/states/postgres/files/pg_hba.conf new file mode 100644 index 0000000..41c580e --- /dev/null +++ b/infrastructure/states/postgres/files/pg_hba.conf @@ -0,0 +1,4 @@ +local all postgres peer +local all all md5 +host all all 127.0.0.1/32 md5 +host all all ::1/128 md5 diff --git a/infrastructure/states/postgres/init.sls b/infrastructure/states/postgres/init.sls new file mode 100644 index 0000000..09d5f60 --- /dev/null +++ b/infrastructure/states/postgres/init.sls @@ -0,0 +1,33 @@ +{%- set creds = salt['pillar.get']('coronadonor-api:database') %} + +production user for coronadonor-api endpoint: + postgres_user.present: + - name: {{ creds.username }} + - login: True + - password: {{ creds.password }} + - require: + - service: postgresql@11-main +# +postgres database for coronadonor-api: + postgres_database: + - present + - name: {{ creds.name }} + - owner: {{ creds.username }} + - require: + - postgres_user: {{ creds.username }} + +postgresql-11: + pkg.installed + +postgresql@11-main: + service: + - running + - enable: True + - watch: + - file: /etc/postgresql/11/main/pg_hba.conf + - require: + - pkg: postgresql-11 + +/etc/postgresql/11/main/pg_hba.conf: + file.managed: + - source: salt://postgres/files/pg_hba.conf diff --git a/infrastructure/states/ruby-install/init.sls b/infrastructure/states/ruby-install/init.sls new file mode 100644 index 0000000..943a28e --- /dev/null +++ b/infrastructure/states/ruby-install/init.sls @@ -0,0 +1,47 @@ +required packages: + pkg.installed: + - names: + - gcc + - bash + +ruby-install: + archive.extracted: + - name: /usr/src/ruby-install/ + - source_hash: 500a8ac84b8f65455958a02bcefd1ed4bfcaeaa2bb97b8f31e61ded5cd0fd70b + - source: https://github.com/postmodern/ruby-install/archive/v0.7.0.tar.gz + - require: + - pkg: required packages + +install ruby-install: + cmd.run: + - name: "make install" + - cwd: /usr/src/ruby-install/ruby-install-0.7.0/ + - creates: /usr/local/bin/ruby-install + - require: + - archive: ruby-install + +install ruby: + cmd.run: + - name: "ruby-install --system ruby 2.6.5" + - creates: /usr/local/bin/ruby + - require: + - archive: ruby-install + - cmd: install ruby-install + +install gemrc: + file.managed: + - name: /etc/gemrc + - contents: -N --no-ri --no-rdoc + +update rubygems: + cmd.run: + - name: gem update --system + - onchanges: + - cmd: install ruby + +install bundler: + cmd.run: + - name: "gem install bundler --no-ri --no-rdoc" + - creates: /usr/local/bin/bundler + - require: + - cmd: install ruby diff --git a/infrastructure/states/top.sls b/infrastructure/states/top.sls new file mode 100644 index 0000000..f8ff750 --- /dev/null +++ b/infrastructure/states/top.sls @@ -0,0 +1,10 @@ +base: + 'salt-test.local': + - postgres + - mosquitto + - nginx + - nginx.site_config + - ruby-install + - coronadonor_api.system_deps + - coronadonor_api.service_account + - coronadonor_api.shared_config