Skip to content

Instantly share code, notes, and snippets.

@joselo
Forked from jbonney/deploy.rb
Last active August 29, 2015 14:15
Show Gist options
  • Save joselo/eef2103723e9a514d91b to your computer and use it in GitHub Desktop.
Save joselo/eef2103723e9a514d91b to your computer and use it in GitHub Desktop.

Revisions

  1. @jbonney jbonney created this gist Aug 17, 2013.
    291 changes: 291 additions & 0 deletions deploy.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,291 @@
    require 'mina/bundler'
    require 'mina/rails'
    require 'mina/git'
    require 'mina/rvm'

    # Usually mina focuses on deploying to one host and the deploy options are therefore simple.
    # In our case, there is a number of possible servers to deploy to, it is therefore necessary to
    # specify the host that we are targeting.
    server = ENV['server']
    # Since the same host can have multiple applications running in parallel, it is necessary to
    # specify further which application we want to deploy
    version = ENV['version']

    # Set the repository (here on BitBucket)
    set :repository, '[email protected]:username/project.git'
    # setting the term_mode to system disable the "pretty-print" but prevent some other issues
    set :term_mode, :system

    # Manually create these paths in shared/ (eg: shared/config/database.yml) in your server.
    # They will be linked in the 'deploy:link_shared_paths' step.
    set :shared_paths, ['config/database.yml', 'log']

    # Optional SSH settings:
    # SSH forward agent to ensure that credentials are passed through for git operations
    set :forward_agent, true

    ##########################################################################
    #
    # Setup environment
    #
    ##########################################################################

    # This task is the environment that is loaded for most commands, such as
    # `mina deploy` or `mina rake`.
    task :environment do
    # Ensure that a server has been set
    unless server
    print_error "A server needs to be specified."
    exit
    end

    # Remote application folder
    set :deploy_to, "/home/username/project/#{version}"

    # Set the basic environment variables based on the server and version
    case server
    when 'qa'
    # The hostname to SSH to
    set :domain, 'qa-domain.com'
    # SSH Optional settings
    set :user, 'foobar' # Username in the server to SSH to.
    # set :port, '30000' # SSH port number.
    # Rails environment
    set :rails_env, 'qa'
    when 'prod'
    # The hostname to SSH to
    set :domain, 'prod-domain.com'
    # SSH Optional settings
    set :user, 'foobar' # Username in the server to SSH to.
    # set :port, '30000' # SSH port number.
    # Rails environment
    set :rails_env, 'production'
    end

    # For those using RVM, use this to load an RVM version@gemset.
    invoke :'rvm:use[ruby-1.9.3-p327@project]'
    end

    ##########################################################################
    #
    # Create new host tasks
    # Tasks below are related to deploying a new version of the application
    #
    ##########################################################################

    # Function extracted from http://blog.nicolai86.eu/posts/2013-05-06/syncing-database-content-down-with-mina
    # allowing to read the content of the database.yml file
    RYAML = <<-BASH
    function ryaml {
    ruby -ryaml -e 'puts ARGV[1..-1].inject(YAML.load(File.read(ARGV[0]))) {|acc, key| acc[key] }' "$@"
    };
    BASH

    # Execute all setup tasks defined below
    desc "Create new folder structure + database.yml + DB + VirtualHost"
    task :'setup:all' => :environment do
    queue! %[echo "-----> Setup folder structure on server"]
    invoke :setup
    queue! %[echo "-----> Setup the DB (create user / DB)"]
    invoke :'setup:db'
    queue! %[echo "-----> Setup Apache VirtualHost Configuration"]
    invoke :'setup:apache'
    queue! %[echo "-----> Deploy Master for this version"]
    invoke :deploy
    queue! %[echo "-----> Enable Apache host and restart Apache"]
    invoke :'apache:enable'
    end

    # Put any custom mkdir's in here for when `mina setup` is ran.
    # For Rails apps, we'll make some of the shared paths that are shared between
    # all releases.
    task :setup => :environment do
    queue! %[mkdir -p "#{deploy_to}/shared/log"]
    queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/log"]

    queue! %[mkdir -p "#{deploy_to}/shared/config"]
    queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/config"]

    queue! %[touch "#{deploy_to}/shared/config/database.yml"]
    queue %[echo "-----> Fill in information below to populate 'shared/config/database.yml'."]
    invoke :'setup:db:database_yml'
    end

    # Populate file database.yml with the appropriate rails_env
    # Database name and user name are based on convention
    # Password is defined by the user during setup
    desc "Populate database.yml"
    task :'setup:db:database_yml' => :environment do
    puts "Enter a name for the new database"
    db_name = STDIN.gets.chomp
    puts "Enter a user for the new database"
    db_username = STDIN.gets.chomp
    puts "Enter a password for the new database"
    db_pass = STDIN.gets.chomp
    # Virtual Host configuration file
    database_yml = <<-DATABASE.dedent
    #{rails_env}:
    adapter: mysql2
    encoding: utf8
    database: #{db_name}
    username: #{db_username}
    password: #{db_pass}
    host: localhost
    timeout: 5000
    DATABASE
    queue! %{
    echo "-----> Populating database.yml"
    echo "#{database_yml}" > #{deploy_to!}/shared/config/database.yml
    echo "-----> Done"
    }
    end

    # Create the new database based on information from database.yml
    # In this application DB, user is given full access to the new DB
    desc "Create new database"
    task :'setup:db' => :environment do
    queue! %{
    echo "-----> Import RYAML function"
    #{RYAML}
    echo "-----> Read database.yml"
    USERNAME=$(ryaml #{deploy_to!}/#{shared_path!}/config/database.yml #{rails_env} username)
    PASSWORD=$(ryaml #{deploy_to!}/#{shared_path!}/config/database.yml #{rails_env} password)
    DATABASE=$(ryaml #{deploy_to!}/#{shared_path!}/config/database.yml #{rails_env} database)
    echo "-----> Create SQL query"
    Q1="CREATE DATABASE IF NOT EXISTS $DATABASE;"
    Q2="GRANT USAGE ON *.* TO $USERNAME@localhost IDENTIFIED BY '$PASSWORD';"
    Q3="GRANT ALL PRIVILEGES ON $DATABASE.* TO $USERNAME@localhost;"
    Q4="FLUSH PRIVILEGES;"
    SQL="${Q1}${Q2}${Q3}${Q4}"
    echo "-----> Execute SQL query to create DB and user"
    echo "-----> Enter MySQL root password on prompt below"
    #{echo_cmd %[mysql -uroot -p -e "$SQL"]}
    echo "-----> Done"
    }
    end

    # Create a new VirtualHost file
    # Server name is defined by convention
    # Script executes some sudo operations
    desc "Create Apache site file"
    task :'setup:apache' => :environment do
    # Get variable for virtual host configuration file
    fqdn = get_fqdn(server, version)
    fqdn_ext = external_fqdn(server, version)
    # Virtual Host configuration file
    vhost = <<-HOSTFILE.dedent
    <VirtualHost *:80>
    ServerAdmin [email protected]
    ServerName #{get_fqdn(server, version)}
    DocumentRoot #{deploy_to!}/#{current_path!}/public
    RailsEnv #{rails_env}
    <Directory #{deploy_to!}/#{current_path!}/public>
    Options -MultiViews
    AllowOverride all
    </Directory>
    PassengerMinInstances 5
    # Maintenance page
    ErrorDocument 503 /503.html
    RewriteEngine On
    RewriteCond %{REQUEST_URI} !.(css|gif|jpg|png)$
    RewriteCond %{DOCUMENT_ROOT}/503.html -f
    RewriteCond %{SCRIPT_FILENAME} !503.html
    RewriteRule ^.*$ - [redirect=503,last]
    </VirtualHost>
    HOSTFILE
    queue! %{
    echo "-----> Create Temporary Apache Virtual Host"
    echo "#{vhost}" > #{fqdn}.tmp
    echo "-----> Copy Virtual Host file to /etc/apache2/sites-available/ (requires sudo)"
    #{echo_cmd %[sudo cp #{fqdn}.tmp /etc/apache2/sites-available/#{fqdn}]}
    echo "-----> Remove Temporary Apache Virtual Host"
    rm #{fqdn}.tmp
    echo "-----> Done"
    }
    end

    # Enable the new Virtual Host and restart Apache
    desc "Enable new Apache host file"
    task :'apache:enable' => :environment do
    fqdn = get_fqdn(server, version)
    queue! %{
    echo "-----> Enable Apache Virtual Host"
    #{echo_cmd %[sudo a2ensite #{fqdn}]}
    echo "-----> Remove Temporary Apache Virtual Host"
    #{echo_cmd %[sudo service apache2 reload]}
    }
    end

    ##########################################################################
    #
    # Deployment related task
    #
    ##########################################################################

    desc "Deploys the current version to the server."
    task :deploy => :environment do
    deploy do
    # Put things that will set up an empty directory into a fully set-up
    # instance of your project.
    invoke :'git:clone'
    invoke :'deploy:link_shared_paths'
    invoke :'bundle:install'
    invoke :'rails:db_migrate'
    invoke :'rails:assets_precompile:force'

    to :launch do
    queue "touch #{deploy_to}/#{current_path}/tmp/restart.txt"
    end
    end
    end

    #########################################################################
    #
    # Helper functions
    #
    ##########################################################################

    #
    # Get the main domain based on the server
    #
    # @return [String] the main domain
    def main_domain(server)
    case server
    when 'qa'
    "qa-domain.com"
    when 'prod'
    "prod-domain.com"
    end
    end

    #
    # Fully Qualified Domain Name of the host
    # Concatenation of the version and the domain name
    #
    # @return [String] the FQDN
    def get_fqdn(server, version)
    fqdn = "#{version}.#{main_domain(server)}"
    return fqdn
    end

    #########################################################################
    #
    # Libraries
    #
    ##########################################################################

    #
    # See https://github.com/cespare/ruby-dedent/blob/master/lib/dedent.rb
    #
    class String
    def dedent
    lines = split "\n"
    return self if lines.empty?
    indents = lines.map do |line|
    line =~ /\S/ ? (line.start_with?(" ") ? line.match(/^ +/).offset(0)[1] : 0) : nil
    end
    min_indent = indents.compact.min
    return self if min_indent.zero?
    lines.map { |line| line =~ /\S/ ? line.gsub(/^ {#{min_indent}}/, "") : line }.join "\n"
    end
    end