diff --git a/Capfile b/Capfile new file mode 100644 index 0000000..4c9f5b9 --- /dev/null +++ b/Capfile @@ -0,0 +1,38 @@ +# Load DSL and set up stages +require "capistrano/setup" + +# Include default deployment tasks +require "capistrano/deploy" + +# Include tasks from other gems included in your Gemfile +# +# For documentation on these, see for example: +# +# https://github.com/capistrano/rvm +# https://github.com/capistrano/rbenv +# https://github.com/capistrano/chruby +# https://github.com/capistrano/bundler +# https://github.com/capistrano/rails +# https://github.com/capistrano/passenger +# +# require 'capistrano/rvm' +# require 'capistrano/rbenv' +# require 'capistrano/bundler' +# require 'capistrano/chruby' +# require 'capistrano/rails/assets' +# require 'capistrano/rails/migrations' +# require 'capistrano/passenger' + +require "capistrano/docker" +require "capistrano/docker/assets" +require "capistrano/docker/migration" + +require "capistrano/safe_deploy_to" +require "capistrano/ssh_doctor" +require "capistrano/file-permissions" +require "capistrano/sentry" + +require "figaro" + +# 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 5647b19..2dc4506 100644 --- a/Gemfile +++ b/Gemfile @@ -99,3 +99,23 @@ group :rubocop do end gem "sentry-ruby", "~> 4.8" + +# Remote Development +group :development do + gem "capistrano", "~> 3.6.0" + gem "capistrano-bundler", require: false + gem "capistrano-docker", github: "netguru/capistrano-docker", require: false + gem "capistrano-file-permissions", require: false + gem "capistrano-nc", require: false + gem "capistrano-pending", require: false + gem "capistrano-rails", require: false + gem "capistrano-rvm", require: false + gem "capistrano-safe-deploy-to", require: false + gem "capistrano-sidekiq", require: false + gem "capistrano-ssh-doctor", require: false + gem "capistrano3-puma", require: false + gem "capistrano-sentry", require: false + + gem "bcrypt_pbkdf", "~> 1.1" + gem "ed25519", "~> 1.3" +end diff --git a/Gemfile.lock b/Gemfile.lock index cc46fc2..0b46168 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,10 @@ +GIT + remote: https://github.com/netguru/capistrano-docker.git + revision: 873aa5fdce66c6962dad555a914d31d4fdc77f5c + specs: + capistrano-docker (0.2.11) + capistrano (>= 3.3) + GIT remote: https://github.com/state-machines/state_machines.git revision: 6a9bb977e0b599eef49fc1fea31b12af113905da @@ -70,10 +77,13 @@ GEM activerecord (>= 4.2) addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) + airbrussh (1.4.0) + sshkit (>= 1.6.1, != 1.7.0) ancestry (4.1.0) activerecord (>= 5.2.6) ast (2.4.2) bcrypt (3.1.16) + bcrypt_pbkdf (1.1.0) better_errors (2.9.1) coderay (>= 1.0.0) erubi (>= 1.0.0) @@ -89,6 +99,40 @@ GEM rails (>= 5.2) thread-local (>= 1.1.0) cancancan (1.17.0) + capistrano (3.6.1) + airbrussh (>= 1.0.0) + capistrano-harrow + i18n + rake (>= 10.0.0) + sshkit (>= 1.9.0) + capistrano-bundler (2.0.1) + capistrano (~> 3.1) + capistrano-file-permissions (1.0.0) + capistrano (~> 3.0) + capistrano-harrow (0.5.3) + capistrano-nc (0.2.0) + capistrano (~> 3.0) + terminal-notifier (~> 2.0) + capistrano-pending (0.2.0) + capistrano (>= 3.2.0) + capistrano-rails (1.6.1) + capistrano (~> 3.1) + capistrano-bundler (>= 1.1, < 3) + capistrano-rvm (0.1.2) + capistrano (~> 3.0) + sshkit (~> 1.2) + capistrano-safe-deploy-to (1.1.1) + capistrano (>= 3.0) + capistrano-sentry (0.4.2) + capistrano (~> 3.1) + capistrano-sidekiq (0.10.0) + capistrano + sidekiq (>= 3.4) + capistrano-ssh-doctor (1.0.0) + capistrano (>= 3.1) + capistrano3-puma (1.2.1) + capistrano (~> 3.0) + puma (>= 2.6) capybara (3.36.0) addressable matrix @@ -101,6 +145,7 @@ GEM childprocess (4.1.0) coderay (1.1.3) concurrent-ruby (1.1.9) + connection_pool (2.2.5) crass (1.0.6) debug_inspector (1.1.0) devise (4.8.1) @@ -109,6 +154,7 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) + ed25519 (1.3.0) erubi (1.10.0) faraday (2.0.1) faraday-net_http (~> 2.0) @@ -151,6 +197,9 @@ GEM mini_mime (1.1.2) minitest (5.15.0) msgpack (1.4.2) + net-scp (3.0.0) + net-ssh (>= 2.6.5, < 7.0.0) + net-ssh (6.1.0) nio4r (2.5.8) nokogiri (1.13.0-x86_64-darwin) racc (~> 1.4) @@ -272,6 +321,10 @@ GEM sentry-ruby-core (4.8.1) concurrent-ruby faraday + sidekiq (6.4.0) + connection_pool (>= 2.2.2) + rack (~> 2.0) + redis (>= 4.2.0) spring (4.0.0) sprockets (4.0.2) concurrent-ruby (~> 1.0) @@ -280,6 +333,9 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) + sshkit (1.21.2) + net-scp (>= 1.1.2) + net-ssh (>= 2.8.0) state_machines-activemodel (0.8.0) activemodel (>= 5.1) state_machines (>= 0.5.0) @@ -306,6 +362,7 @@ GEM sunspot (= 2.5.0) sunspot_solr (2.5.0) sunspot_submodel_index (0.0.5) + terminal-notifier (2.0.0) thor (1.2.1) thread-local (1.1.0) tilt (2.0.10) @@ -348,13 +405,28 @@ PLATFORMS DEPENDENCIES acts_as_list ancestry + bcrypt_pbkdf (~> 1.1) better_errors binding_of_caller bootsnap (>= 1.4.4) byebug cancancan (~> 1.17.0) + capistrano (~> 3.6.0) + capistrano-bundler + capistrano-docker! + capistrano-file-permissions + capistrano-nc + capistrano-pending + capistrano-rails + capistrano-rvm + capistrano-safe-deploy-to + capistrano-sentry + capistrano-sidekiq + capistrano-ssh-doctor + capistrano3-puma capybara (>= 3.26) devise (>= 4.7.1) + ed25519 (~> 1.3) figaro foreman jbuilder (~> 2.7) diff --git a/config/deploy.rb b/config/deploy.rb new file mode 100644 index 0000000..8056ad0 --- /dev/null +++ b/config/deploy.rb @@ -0,0 +1,130 @@ +# config valid only for current version of Capistrano +lock "3.6.1" + +set :application, "cdao-pjet" +set :repo_url, "git@github.com:CDAsia/cdao-pjet.git" +set :branch, "master" +set :deploy_to, -> { "/var/www/#{fetch(:application)}" } + +# Default value for :linked_files is [] +set :docker_copy_data, fetch(:linked_files, []).push(".env", + "config/application.yml") + +# Default value for keep_releases is 5 +set :keep_releases, 5 + +set :log_level, :info + +set :bundle_flags, "--deployment --quiet" + +set :docker_role, :web +set :docker_compose, true +set :docker_compose_up_services, "--remove-orphans --scale web=3 load_balancer" +set :docker_compose_project_name, -> { fetch(:application) } +set :docker_assets_precompile_command, -> { + ["bundle exec rake assets:precompile", + "bundle exec rake assets:non_digested", + "bundle exec rake assets:clean"].join(" && ") +} +set :docker_migrate_command, -> { "bundle exec rake db:migrate" } +set :docker_compose_path, -> { release_path.join(fetch(:docker_compose_file)) } + +namespace :deploy do + desc "Fix repo" + task :set_origin_url do + on roles(:all) do + if File.directory?(deploy_path.join("repo")) + execute "cd #{fetch(:deploy_to)}/repo && git remote set-url origin #{fetch(:repo_url)}" + end + end + end + + desc "Load ENV on figaro" + before :starting, :set_figaro_env do + run_locally do + opts = { + environment: fetch(:rack_env, fetch(:rails_env, "production")), + path: "config/application.yml" + } + Figaro.application = Figaro::Application.new(opts) + Figaro.load + end + end +end + +namespace :docker do + namespace :rails do + def docker_compose_execute(exec) + cmd = ["run --rm"] # --rm, Remove container after run + cmd.unshift("-p #{fetch(:docker_compose_project_name)}") unless fetch(:docker_compose_project_name).nil? + cmd.unshift("-f #{fetch(:docker_compose_path)}") + cmd << "web bash -c '#{exec}'" + + cmd.join(" ") + end + + task :bundle do + on release_roles(fetch(:docker_role)) do + within release_path do + options = [] + # options << "--gemfile #{fetch(:bundle_gemfile)}" if fetch(:bundle_gemfile) + # options << "--path #{fetch(:bundle_path)}" if fetch(:bundle_path) + unless test(:'docker-compose', docker_compose_execute("bundle check #{options.join(' ')}")) + # options << "--binstubs #{fetch(:bundle_binstubs)}" if fetch(:bundle_binstubs) + # options << "--jobs #{fetch(:bundle_jobs)}" if fetch(:bundle_jobs) + # options << "--without #{fetch(:bundle_without)}" if fetch(:bundle_without) + # options << fetch(:bundle_flags).to_s if fetch(:bundle_flags) + execute :'docker-compose', docker_compose_execute("bundle install #{options.join(' ')}") + end + end + end + end + + namespace :assets do + task :precompile do + on release_roles(fetch(:docker_role)) do + within release_path do + execute :'docker-compose', docker_compose_execute(fetch(:docker_assets_precompile_command)) + end + end + end + end + + namespace :db do + task :migrate do + on release_roles(fetch(:docker_role)) do + within release_path do + execute :'docker-compose', docker_compose_execute(fetch(:docker_migrate_command)) + end + end + end + end + + def _compose_option_up_services + opt = fetch(:docker_compose_up_services) + opt.nil? ? "" : opt + end + + def compose_start_command + cmd = %w[up -d --remove-orphans] + cmd << _compose_option_up_services + cmd.unshift _compose_option_project_name + cmd.unshift _compose_option_compose_path + + cmd.join(" ") + end + end +end + +# Sentry deployment notificationt +set :sentry_api_token, ENV["SENTRY_API_TOKEN"] +set :sentry_organization, ENV["SENTRY_ORGANIZATION"] +set :sentry_project, ENV["SENTRY_PROJECT"] + +before "deploy:starting", "sentry:validate_config" +after "deploy:published", "sentry:notice_deployment" + +after "deploy:check:directories", "deploy:set_origin_url" +after "docker:deploy:compose:build", "docker:rails:bundle" +after "docker:rails:bundle", "docker:rails:db:migrate" +after "docker:rails:bundle", "docker:rails:assets:precompile" diff --git a/config/deploy/production.rb b/config/deploy/production.rb new file mode 100644 index 0000000..22cd555 --- /dev/null +++ b/config/deploy/production.rb @@ -0,0 +1,64 @@ +# server-based syntax +# ====================== +# Defines a single server with a list of roles and multiple properties. +# You can define all roles on a single server, or split them: + +# server 'example.com', user: 'deploy', roles: %w{app db web}, my_property: :my_value +# server 'example.com', user: 'deploy', roles: %w{app web}, other_property: :other_value +# server 'db.example.com', user: 'deploy', roles: %w{db} + +server "002-app.cdasia.com", user: "deploy", roles: %w[web app db worker] + +# role-based syntax +# ================== + +# Defines a role with one or multiple servers. The primary server in each +# group is considered to be the first unless any hosts have the primary +# property set. Specify the username and a domain or IP for the server. +# Don't use `:all`, it's a meta role. + +# role :app, %w{deploy@example.com}, my_property: :my_value +# role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value +# role :db, %w{deploy@example.com} + + + +# Configuration +# ============= +# You can set any configuration variable like in config/deploy.rb +# These variables are then only loaded and set in this stage. +# For available Capistrano configuration variables see the documentation page. +# http://capistranorb.com/documentation/getting-started/configuration/ +# Feel free to add new variables to customise your setup. + + + +# Custom SSH Options +# ================== +# You may pass any option but keep in mind that net/ssh understands a +# limited set of options, consult the Net::SSH documentation. +# http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start +# +# Global options +# -------------- +# set :ssh_options, { +# keys: %w(/home/rlisowski/.ssh/id_rsa), +# forward_agent: false, +# auth_methods: %w(password) +# } +# +# The server-based syntax can be used to override options: +# ------------------------------------ +# server 'example.com', +# user: 'user_name', +# roles: %w{web app}, +# ssh_options: { +# user: 'user_name', # overrides user setting above +# keys: %w(/home/user_name/.ssh/id_rsa), +# forward_agent: false, +# auth_methods: %w(publickey password) +# # password: 'please use keys' +# } + +set :rails_env, "production" +set :docker_compose_file, -> { "docker-compose.production.yml" } diff --git a/config/deploy/staging.rb b/config/deploy/staging.rb new file mode 100644 index 0000000..22cd555 --- /dev/null +++ b/config/deploy/staging.rb @@ -0,0 +1,64 @@ +# server-based syntax +# ====================== +# Defines a single server with a list of roles and multiple properties. +# You can define all roles on a single server, or split them: + +# server 'example.com', user: 'deploy', roles: %w{app db web}, my_property: :my_value +# server 'example.com', user: 'deploy', roles: %w{app web}, other_property: :other_value +# server 'db.example.com', user: 'deploy', roles: %w{db} + +server "002-app.cdasia.com", user: "deploy", roles: %w[web app db worker] + +# role-based syntax +# ================== + +# Defines a role with one or multiple servers. The primary server in each +# group is considered to be the first unless any hosts have the primary +# property set. Specify the username and a domain or IP for the server. +# Don't use `:all`, it's a meta role. + +# role :app, %w{deploy@example.com}, my_property: :my_value +# role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value +# role :db, %w{deploy@example.com} + + + +# Configuration +# ============= +# You can set any configuration variable like in config/deploy.rb +# These variables are then only loaded and set in this stage. +# For available Capistrano configuration variables see the documentation page. +# http://capistranorb.com/documentation/getting-started/configuration/ +# Feel free to add new variables to customise your setup. + + + +# Custom SSH Options +# ================== +# You may pass any option but keep in mind that net/ssh understands a +# limited set of options, consult the Net::SSH documentation. +# http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start +# +# Global options +# -------------- +# set :ssh_options, { +# keys: %w(/home/rlisowski/.ssh/id_rsa), +# forward_agent: false, +# auth_methods: %w(password) +# } +# +# The server-based syntax can be used to override options: +# ------------------------------------ +# server 'example.com', +# user: 'user_name', +# roles: %w{web app}, +# ssh_options: { +# user: 'user_name', # overrides user setting above +# keys: %w(/home/user_name/.ssh/id_rsa), +# forward_agent: false, +# auth_methods: %w(publickey password) +# # password: 'please use keys' +# } + +set :rails_env, "production" +set :docker_compose_file, -> { "docker-compose.production.yml" } diff --git a/docker-compose.production.yml b/docker-compose.production.yml new file mode 100644 index 0000000..a95c819 --- /dev/null +++ b/docker-compose.production.yml @@ -0,0 +1,37 @@ +version: "2" +services: + data: + image: busybox + volumes: + - .:/var/www + - ./../../shared/bundle:/bundle + - ./../../shared/.bundle:/var/www/.bundle + - ./../../shared/public/assets:/var/www/public/assets + - ./../../shared/public/system:/var/www/public/system + - ./../../shared/storage:/var/www/storage + - ./../../shared/tmp:/var/www/tmp + - ./../../shared/tmp/pids:/var/www/tmp/pids + - ./../../shared/vendor/bundle:/var/www/vendor/bundle + - ./../../shared/node_modules:/var/www/node_modules + solr: + image: tenshiamd/solr:5-alpine + ports: + - ${SOLR_PORT}:8983 + volumes: + - ./../../shared/solr:/var/lib/solr + web: + restart: ${WEB_RESTART_MODE} + image: tenshiamd/ruby:2.6-alpine + command: | + bash -c 'bash -s <