├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── docs └── images │ └── how-it-works.gif ├── lib └── mina │ ├── multideploy.rb │ ├── multideploy │ ├── base_service.rb │ ├── create_scripts.rb │ ├── init.rb │ ├── railtie.rb │ ├── templates │ │ └── servers_deploy.rb │ └── version.rb │ └── tasks │ └── multideploy_tasks.rake ├── mina-multideploy.gemspec └── spec ├── mina └── multideploy_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | 10 | # rspec failure tracking 11 | .rspec_status 12 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sudo: false 3 | language: ruby 4 | cache: bundler 5 | rvm: 6 | - 2.5.1 7 | before_install: gem install bundler -v 1.16.4 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } 4 | 5 | # Specify your gem's dependencies in mina-multideploy.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Sergey Volkov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mina multideploy 2 | 3 | A useful tool for parallel deployment on multiple servers with [mina](https://github.com/mina-deploy/mina). 4 | 5 | ## How it works 6 | ![How it works](https://raw.githubusercontent.com/codica2/mina-multideploy/master/docs/images/how-it-works.gif) 7 | 8 | This gem will help you to deploy the application on multiple servers simultaneously. It takes original mina `deploy.rb` file, changes `application_name`, `domain` and starts the deployment process. 9 | 10 | ## Installation 11 | Add this line to your application's Gemfile: 12 | 13 | ```ruby 14 | gem 'mina-multideploy', '~> 1.1.0' 15 | ``` 16 | 17 | And then execute: 18 | ``` 19 | bundle install 20 | ``` 21 | 22 | Or install it yourself as: 23 | ```bash 24 | gem install mina-multideploy 25 | ``` 26 | 27 | ## Getting Started 28 | Start by generating a configuration file: 29 | ``` 30 | bundle exec rails multideploy:init 31 | ``` 32 | It should give you a file in: 33 | ``` 34 | config/initializers/multideploy.rb 35 | ``` 36 | It should look something like this: 37 | ```ruby 38 | return unless defined? Mina::Multideploy 39 | 40 | Mina::Multideploy.configure do |config| 41 | config.servers = {} 42 | # Default velues 43 | # config.original = 'config/deploy.rb' 44 | # config.w_dir = 'tmp/deploy' 45 | end 46 | ``` 47 | 48 | ## Configuration 49 | 50 | *`servers`* - hash at format `domain` => `array of application_name's`. 51 | 52 | Example: 53 | ```ruby 54 | config.servers = { 55 | '84.155.207.209' => %w[carghana caryange cartanzania] 56 | '105.87.69.69' => %w[poster] 57 | '48.84.207.183' => %w[codica timebot] 58 | } 59 | ``` 60 | It means that your code will be deployed to 3 servers, and there can be several applications on one server. 61 | 62 | *`original`* - path to the original mina `deploy.rb` file which will be taken as a basic. 63 | 64 | *`w_dir`* - path to directory where temoporary files and logs will be created. 65 | 66 | ## Available features 67 | After you have configured servers at `config/initializers/multideploy.rb` you can start deploying in two ways. 68 | 69 | ### Semi-automatic deploy (recommended for first deploy) 70 | Run this command: 71 | ```ruby 72 | bundle exec rails multideploy:prepare 73 | ``` 74 | You will get file `servers_deploy.rb` at working directory (tmp/deploy by default). Check it and run `ruby ./tmp/deploy/server_deploy.rb`. 75 | 76 | ### Automatic deploy 77 | Run this command: 78 | ```ruby 79 | bundle exec rails multideploy:start 80 | ``` 81 | It will make the same as `multideploy:prepare`, but the deployment will start automatically. 82 | 83 | ### Runing mina or rake tasks 84 | Use command as argument for `multideploy:run` 85 | 86 | ```ruby 87 | bundle exec rails "multideploy:run[rake[db:migrate]]" 88 | ``` 89 | 90 | 91 | ## Additional information 92 | * all scripts are updated according config file before launch `multideploy:prepare` and `multideploy:start` 93 | * add public SSH key, so you can login to server without password. Run `ssh-copy-id user@$host` 94 | 95 | ## License 96 | mina-multideploy is Copyright © 2015-2019 Codica. It is released under the [MIT License](https://opensource.org/licenses/MIT). 97 | 98 | ## About Codica 99 | 100 | [![Codica logo](https://www.codica.com/assets/images/logo/logo.svg)](https://www.codica.com) 101 | 102 | mina-multideploy is maintained and funded by Codica. The names and logos for Codica are trademarks of Codica. 103 | 104 | We love open source software! See [our other projects](https://github.com/codica2) or [hire us](https://www.codica.com/) to design, develop, and grow your product. 105 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: :spec 7 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'mina/multideploy' 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require 'irb' 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /docs/images/how-it-works.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codica2/mina-multideploy/8aca313b738773ea682a0b05be12da608b9244d2/docs/images/how-it-works.gif -------------------------------------------------------------------------------- /lib/mina/multideploy.rb: -------------------------------------------------------------------------------- 1 | require 'mina/multideploy/railtie' 2 | require 'mina/multideploy/base_service' 3 | require 'mina/multideploy/version' 4 | 5 | module Mina 6 | module Multideploy 7 | class << self 8 | attr_accessor :configuration 9 | end 10 | 11 | def self.configure 12 | self.configuration ||= Configuration.new 13 | yield(configuration) 14 | end 15 | 16 | class Configuration 17 | attr_accessor :servers, :original, :w_dir 18 | 19 | def initialize 20 | @servers = {} 21 | @original = 'config/deploy.rb' 22 | @w_dir = 'tmp/deploy' 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/mina/multideploy/base_service.rb: -------------------------------------------------------------------------------- 1 | module Multideploy 2 | class BaseService 3 | def self.call(*args) 4 | new(*args).call 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/mina/multideploy/create_scripts.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | module Multideploy 4 | class CreateScripts < BaseService 5 | attr_reader :c 6 | 7 | def initialize 8 | @c = Mina::Multideploy.configuration 9 | end 10 | 11 | def call 12 | create_dir 13 | write_ruby_script 14 | end 15 | 16 | private 17 | 18 | def create_dir 19 | FileUtils.mkdir_p(working_dir) 20 | end 21 | 22 | def working_dir 23 | c.w_dir 24 | end 25 | 26 | def deploy_file 27 | 'servers_deploy.rb' 28 | end 29 | 30 | def write_ruby_script 31 | File.open("#{working_dir}/#{deploy_file}", 'w+') do |f| 32 | f.write(ruby_script) 33 | end 34 | FileUtils.chmod 0o755, "#{working_dir}/#{deploy_file}" 35 | end 36 | 37 | def ruby_script 38 | template_path = File.join(File.dirname(__FILE__), "./templates/#{deploy_file}") 39 | script = File.read(template_path) 40 | script = script.gsub('SERVERS_TO_REPLACE', c.servers.inspect) 41 | script = script.gsub('ORIGINAL_DEPLOY_FILE_TO_REPLACE', c.original) 42 | script = script.gsub('CUSTOM_W_DIR_TO_REPLACE', c.w_dir) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/mina/multideploy/init.rb: -------------------------------------------------------------------------------- 1 | module Multideploy 2 | class Init < BaseService 3 | def call 4 | create 5 | end 6 | 7 | private 8 | 9 | def path 10 | 'config/initializers/multideploy.rb' 11 | end 12 | 13 | def content 14 | <<-EOS 15 | return unless defined? Mina::Multideploy 16 | 17 | Mina::Multideploy.configure do |config| 18 | config.servers = {} 19 | # Default velues 20 | # config.original = 'config/deploy.rb' 21 | # config.w_dir = 'tmp/deploy' 22 | end 23 | EOS 24 | end 25 | 26 | def create 27 | if File.exist?(path) 28 | puts "#{path} already exist." 29 | else 30 | File.open(path, 'w+') do |f| 31 | f.write(content) 32 | end 33 | puts "#{path} created. Feel free to chenge it!" 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/mina/multideploy/railtie.rb: -------------------------------------------------------------------------------- 1 | module Multideploy 2 | class Railtie < ::Rails::Railtie 3 | rake_tasks do 4 | load 'mina/tasks/multideploy_tasks.rake' 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/mina/multideploy/templates/servers_deploy.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'logger' 3 | require 'parallel' 4 | require 'tty-progressbar' 5 | # require 'byebug' 6 | 7 | # Servers list 8 | SERVERS = SERVERS_TO_REPLACE 9 | 10 | w_dir = Dir.pwd 11 | c_dir = "#{w_dir}/CUSTOM_W_DIR_TO_REPLACE/config" 12 | l_dir = "#{w_dir}/CUSTOM_W_DIR_TO_REPLACE/log" 13 | command_argument = ARGV[0] || 'deploy' 14 | 15 | original_deploy_config = File.read("#{w_dir}/ORIGINAL_DEPLOY_FILE_TO_REPLACE") 16 | max_ip_length = SERVERS.keys.map(&:length).max + 1 17 | multibar = TTY::ProgressBar::Multi.new 18 | 19 | FileUtils.mkdir_p c_dir unless File.exist?(c_dir) 20 | FileUtils.mkdir_p l_dir unless File.exist?(l_dir) 21 | 22 | # Deploy script 23 | Parallel.each(SERVERS, in_threads: SERVERS.length) do |ip, names| 24 | bar = multibar.register("#{ip.ljust(max_ip_length)} [:current/:total] :report", total: names.size) 25 | bar.start 26 | 27 | report = '' 28 | bar.advance(0, report: report) 29 | 30 | names.each do |site| 31 | c_file_name = "#{ip}-#{site}.rb" 32 | l_file_name = "#{ip}-#{site}.log" 33 | 34 | custom_deploy_config = original_deploy_config.gsub(/^set :application_name(.*)/, "set :application_name, :#{site}") 35 | custom_deploy_config = custom_deploy_config.gsub(/^set :domain(.*)/, "set :domain, '#{ip}'") 36 | 37 | FileUtils.rm "#{l_dir}/#{l_file_name}" if File.exist?("#{l_dir}/#{l_file_name}") 38 | File.write("#{c_dir}/#{c_file_name}", custom_deploy_config) 39 | 40 | cmd = "mina #{command_argument} -f #{c_dir}/#{c_file_name}" 41 | cmd = `#{cmd}` 42 | 43 | logger = Logger.new("#{l_dir}/#{l_file_name}") 44 | logger.info(cmd) 45 | 46 | report += "#{site} ✗ " if cmd.include?('ERROR') 47 | 48 | bar.advance(report: report) 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/mina/multideploy/version.rb: -------------------------------------------------------------------------------- 1 | module Mina 2 | module Multideploy 3 | VERSION = '1.2.0'.freeze 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/mina/tasks/multideploy_tasks.rake: -------------------------------------------------------------------------------- 1 | require 'mina/multideploy/init' 2 | require 'mina/multideploy/create_scripts' 3 | 4 | namespace :multideploy do 5 | desc 'Create initializer file' 6 | task init: :environment do 7 | Multideploy::Init.call 8 | end 9 | 10 | desc 'Prepare deploy scripts' 11 | task prepare: :environment do 12 | c = Mina::Multideploy.configuration 13 | Multideploy::CreateScripts.call 14 | puts "Run 'ruby ./#{c.w_dir}/servers_deploy.rb' to start deploy" 15 | end 16 | 17 | desc 'Prepare deploy scripts and start deploying' 18 | task start: :environment do 19 | c = Mina::Multideploy.configuration 20 | Multideploy::CreateScripts.call 21 | exec "ruby ./#{c.w_dir}/servers_deploy.rb" 22 | end 23 | 24 | desc 'Runing mina or rake tasks' 25 | task :run, [:task_arg] => [:environment] do |task, args| 26 | c = Mina::Multideploy.configuration 27 | Multideploy::CreateScripts.call 28 | exec "ruby ./#{c.w_dir}/servers_deploy.rb #{args[:task_arg]}" 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /mina-multideploy.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('lib', __dir__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'mina/multideploy/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'mina-multideploy' 7 | spec.version = Mina::Multideploy::VERSION 8 | spec.authors = ['Sergey Volkov'] 9 | spec.email = ['sergvolkov.codica@gmail.com'] 10 | 11 | spec.summary = 'Parallel deploying on multiple servers with mina.' 12 | spec.description = 'This gem will help you deploy the application on multiple servers in parallel. It takes original mina deploy.rb file, changes application_name, domain and starts deploying process.' 13 | spec.homepage = 'https://github.com/codica2/mina-multideploy' 14 | spec.license = 'MIT' 15 | 16 | # Specify which files should be added to the gem when it is released. 17 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 18 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 19 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 20 | end 21 | spec.bindir = 'exe' 22 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 23 | spec.require_paths = ['lib'] 24 | 25 | spec.add_development_dependency 'bundler', '~> 1.16' 26 | spec.add_development_dependency 'rake', '~> 10.0' 27 | spec.add_development_dependency 'rspec', '~> 3.0' 28 | spec.add_runtime_dependency 'parallel' 29 | spec.add_runtime_dependency 'tty-progressbar' 30 | end 31 | -------------------------------------------------------------------------------- /spec/mina/multideploy_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Mina::Multideploy do 2 | it 'has a version number' do 3 | expect(Mina::Multideploy::VERSION).not_to be nil 4 | end 5 | 6 | end 7 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'mina/multideploy' 3 | 4 | RSpec.configure do |config| 5 | # Enable flags like --only-failures and --next-failure 6 | config.example_status_persistence_file_path = '.rspec_status' 7 | 8 | # Disable RSpec exposing methods globally on `Module` and `main` 9 | config.disable_monkey_patching! 10 | 11 | config.expect_with :rspec do |c| 12 | c.syntax = :expect 13 | end 14 | end 15 | --------------------------------------------------------------------------------