mina-deploy / mina

Blazing fast deployer and server automation tool
https://rubygems.org/gems/mina
Other
4.36k stars 492 forks source link

Rails 7.1 Credentials in Production #726

Open TimeTravelerFromNow opened 6 months ago

TimeTravelerFromNow commented 6 months ago

Mina and Rails 7.1 Credentialling broke my production when I updated to Ruby 3.2.0 and Rails 7.1.3.2

The credentials system in Rails 7.1 manages the secret differently. I'm still learning how they work, but my production deploys would all fail with Rails version 7.1 (at the db:migrate step).

Today I fixed it after watching deanin's credential management video.

Production was down for more than a week before today, I did a lot of guesswork, here's what I remember doing to get production working again:

  1. I had to comment out my mina deploy script at the db:migrate step. (see my script below)
  2. ssh to my server where the app is (under ~/sebsite-live/current for me)
  3. generate credentials the Rails 7.1 way: VISUAL="vim" rails credentials:edit --environment production
  4. pasting the key from the terminal output that appears AFTER SAVING to new file config/master.key in production
  5. running rails setup commands starting with RAILS_ENV=production rails db:migrate rails assets:precompile
  6. restarting (it's a unicorn server process) sebsite-live@vultr:~/app/current$ sudo systemctl restart sebsite-live

How do we automate step 3 in Mina?

The new solution mina should be easy, but safely handle credentials too. I would be happy to contribute in any way I can, if I can. Why don't I just switch to Docker deploys? (See comments below)

ERROR LOG (shortened but this would occur when secrets were not setup in production
       rake aborted!
       ActiveSupport::MessageEncryptor::InvalidMessage: missing separator (ActiveSupport::MessageEncryptor::InvalidMessage)
       /home/sebsite-live/app/tmp/build-171565374418312/vendor/bundle/ruby/3.2.0/gems/activesupport-7.1.3.2/lib/active_support/messages/codec.rb:57:in `catch_and_raise'
       /home/sebsite-live/app/tmp/build-171565374418312/vendor/bundle/ruby/3.2.0/gems/activesupport-7.1.3.2/lib/active_support/message_encryptor.rb:242:in `decrypt_and_verify'
       /home/sebsite-live/app/tmp/build-171565374418312/vendor/bundle/ruby/3.2.0/gems/activesupport-7.1.3.2/lib/active_support/encrypted_file.rb:109:in `decrypt'
       /home/sebsite-live/app/tmp/build-171565374418312/vendor/bundle/ruby/3.2.0/gems/activesupport-7.1.3.2/lib/active_support/encrypted_file.rb:72:in `read'
       /home/sebsite-live/app/tmp/build-171565374418312/vendor/bundle/ruby/3.2.0/gems/activesupport-7.1.3.2/lib/active_support/encrypted_configuration.rb:57:in `read'
       /home/sebsite-live/app/tmp/build-171565374418312/vendor/bundle/ruby/3.2.0/gems/activesupport-7.1.3.2/lib/active_support/encrypted_configuration.rb:76:in `config'
       /home/sebsite-live/app/tmp/build-171565374418312/vendor/bundle/ruby/3.2.0/gems/activesupport-7.1.3.2/lib/active_support/encrypted_configuration.rb:95:in `options'
       /home/sebsite-live/app/tmp/build-171565374418312/vendor/bundle/ruby/3.2.0/gems/activesupport-7.1.3.2/lib/active_support/core_ext/module/delegation.rb:332:in `method_missing'
       /home/sebsite-live/app/tmp/build-171565374418312/vendor/bundle/ruby/3.2.0/gems/activerecord-7.1.3.2/lib/active_record/railtie.rb:384:in `block (2 levels) in <class:Railtie>'
...
       /home/sebsite-live/app/tmp/build-171565374418312/vendor/bundle/ruby/3.2.0/gems/zeitwerk-2.6.13/lib/zeitwerk/kernel.rb:34:in `require'
       /home/sebsite-live/app/tmp/build-171565374418312/vendor/bundle/ruby/3.2.0/gems/railties-7.1.3.2/lib/rails/application.rb:402:in `require_environment!'
       /home/sebsite-live/app/tmp/build-171565374418312/vendor/bundle/ruby/3.2.0/gems/railties-7.1.3.2/lib/rails/application.rb:588:in `block in run_tasks_blocks'
       /home/sebsite-live/app/tmp/build-171565374418312/vendor/bundle/ruby/3.2.0/gems/sprockets-rails-3.4.2/lib/sprockets/rails/task.rb:61:in `block (2 levels) in define'
       /home/sebsite-live/app/tmp/build-171565374418312/vendor/bundle/ruby/3.2.0/gems/rake-13.2.1/exe/rake:27:in `<top (required)>'
       /usr/local/rvm/gems/ruby-3.2.0/bin/ruby_executable_hooks:22:in `eval'
       /usr/local/rvm/gems/ruby-3.2.0/bin/ruby_executable_hooks:22:in `<main>'
       Tasks: TOP => environment
       (See full trace by running task with --trace)
 !     ERROR: Deploy failed.
-----> Cleaning up build
       Unlinking current
       OK
       Connection to 149.248.16.163 closed.

 !     Run Error

config/deploy.rb

require 'mina/rails'
require 'mina/git'
require 'mina/rvm'

# Basic settings:
#   domain       - The hostname to SSH to.
#   deploy_to    - Path to deploy into.
#   repository   - Git repo to clone from. (needed by mina/git)
#   branch       - Branch name to deploy. (needed by mina/git)

set :application_name, 'sebsite-live'
set :domain, '149.248.16.163'
set :user, fetch(:application_name)
set :deploy_to, "/home/#{fetch(:user)}/app"
set :repository, 'git@github.com:TimeTravelerFromNow/sebsite-live.git'
set :branch, 'main'
set :rvm_use_path, '/etc/profile.d/rvm.sh'

# Optional settings:
#   set :user, 'foobar'          # Username in the server to SSH to.
#   set :port, '30000'           # SSH port number.
#   set :forward_agent, true     # SSH forward_agent.

# Shared dirs and files will be symlinked into the app-folder by the 'deploy:link_shared_paths' step.
# Some plugins already add folders to shared_dirs like `mina/rails` add `public/assets`, `vendor/bundle` and many more
# run `mina -d` to see all folders and files already included in `shared_dirs` and `shared_files`
# set :shared_dirs, fetch(:shared_dirs, []).push('public/assets')
set :shared_files, fetch(:shared_files, []).push('config/database.yml', 'config/secrets.yml')
set :shared_dirs, fetch(:shared_dirs, []).push('public/packs', 'node_modules')

# This task is the environment that is loaded for all remote run commands, such as
# `mina deploy` or `mina rake`.
task :remote_environment do
  ruby_version = File.read('.ruby-version').strip
  raise "Couldn't determine Ruby version: Do you have a file .ruby-version in your project root?" if ruby_version.empty?

  invoke :'rvm:use', ruby_version
end

# Put any custom commands you need to run at setup
# All paths in `shared_dirs` and `shared_paths` will be created on their own.
task :setup do

  in_path(fetch(:shared_path)) do

    command %[mkdir -p config]

    # Create database.yml for Postgres if it doesn't exist
    path_database_yml = "config/database.yml"
    database_yml = %[production:
  database: #{fetch(:user)}
  adapter: postgresql
  pool: 5
  timeout: 5000]
    command %[test -e #{path_database_yml} || echo "#{database_yml}" > #{path_database_yml}]

    # Remove others-permission for config directory
    command %[chmod -R o-rwx config]
  end

end

desc "Deploys the current version to the server."
task :deploy do
  # uncomment this line to make sure you pushed your local branch to the remote origin
  invoke :'git:ensure_pushed'
  deploy do
    # Put things that will set up an empty directory into a fully set-up
    # instance of your project.
    invoke :'git:clone'
    on :link_shared_paths do
      ruby_version = File.read('.ruby-version').strip
      raise "Couldn't determine Ruby version: Do you have a file .ruby-version in your project root?" if ruby_version.empty?

      invoke :'rvm:use', ruby_version
    end

    invoke :'bundle:install'

 # commented out and ran these manually because they would need secrets properly set up.
 #   invoke :'rails:db_migrate'
 #   invoke :'rails:assets_clean'
 #  invoke :'rails:assets_precompile'
    on :launch do
#      command "rake title_to_slug"
      command "sudo systemctl restart #{fetch(:user)}"
    end

  end

  # you can use `run :local` to run tasks on local machine before of after the deploy scripts
  # run(:local){ say 'done' }
end

# For help in making your deploy script, see the Mina documentation:
#
#  - https://github.com/mina-deploy/mina/tree/master/docs

since 2022

Docker Question:

The train of dockerization and deploy seems to have left the station, but maybe... Skipping containerization comes with small performance benefits. Is there a world where we have a second option – to deploy to production without docker? Since 2022 I have used mina to deploy sebastiandetering.com, following Ralf Ebert's Tutorial. Mina is so super cool, Docker is too, but I want to be part of the minority who can deploy without containerizing when we want to.