kamal 1.3.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/kamal/cli/accessory.rb +38 -29
- data/lib/kamal/cli/app/boot.rb +67 -0
- data/lib/kamal/cli/app/prepare_assets.rb +24 -0
- data/lib/kamal/cli/app.rb +25 -67
- data/lib/kamal/cli/base.rb +23 -8
- data/lib/kamal/cli/env.rb +3 -5
- data/lib/kamal/cli/main.rb +7 -4
- data/lib/kamal/cli/prune.rb +6 -2
- data/lib/kamal/cli/server.rb +3 -1
- data/lib/kamal/cli/templates/deploy.yml +5 -1
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +7 -0
- data/lib/kamal/cli/traefik.rb +16 -13
- data/lib/kamal/commander/specifics.rb +49 -0
- data/lib/kamal/commander.rb +9 -33
- data/lib/kamal/commands/accessory.rb +2 -2
- data/lib/kamal/commands/app/assets.rb +12 -12
- data/lib/kamal/commands/app/cord.rb +4 -4
- data/lib/kamal/commands/app/execution.rb +10 -8
- data/lib/kamal/commands/app/images.rb +1 -1
- data/lib/kamal/commands/app/logging.rb +2 -2
- data/lib/kamal/commands/app.rb +38 -18
- data/lib/kamal/commands/auditor.rb +1 -1
- data/lib/kamal/commands/base.rb +12 -0
- data/lib/kamal/commands/builder/base.rb +22 -5
- data/lib/kamal/commands/builder/multiarch.rb +17 -9
- data/lib/kamal/commands/builder/native/cached.rb +7 -6
- data/lib/kamal/commands/builder/native/remote.rb +9 -9
- data/lib/kamal/commands/builder/native.rb +8 -7
- data/lib/kamal/commands/docker.rb +10 -1
- data/lib/kamal/commands/healthcheck.rb +0 -1
- data/lib/kamal/commands/hook.rb +1 -1
- data/lib/kamal/commands/lock.rb +19 -9
- data/lib/kamal/commands/prune.rb +4 -4
- data/lib/kamal/commands/registry.rb +4 -1
- data/lib/kamal/commands/server.rb +1 -1
- data/lib/kamal/commands/traefik.rb +10 -16
- data/lib/kamal/configuration/accessory.rb +10 -20
- data/lib/kamal/configuration/boot.rb +1 -1
- data/lib/kamal/configuration/builder.rb +11 -3
- data/lib/kamal/configuration/env.rb +40 -0
- data/lib/kamal/configuration/role.rb +23 -40
- data/lib/kamal/configuration.rb +53 -21
- data/lib/kamal/env_file.rb +12 -15
- data/lib/kamal/sshkit_with_ext.rb +1 -0
- data/lib/kamal/utils.rb +7 -3
- data/lib/kamal/version.rb +1 -1
- data/lib/kamal.rb +1 -1
- metadata +8 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: d8429a6060103f16d9aadb517e77fbb32fc6c6d2e6b9b3cf2989c8a9309e0067
         | 
| 4 | 
            +
              data.tar.gz: e997f3c5765639c4b14aad08a81344c5af182904e3d80f4867792ed55a9fc25b
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 575353b6ea9e9d429a6c6e6b89013fd36f0b371b4edbffbf3ab33f707a9637191872d7463fb670dbe45f9a36bc7a1792335fa9c0709e31d2339b1549c4da32ff
         | 
| 7 | 
            +
              data.tar.gz: 6a71437092b0ce21cce12a6d127098e2aba2a0b18846026b6df1c9bb1d33f60d27da3ac1c3b553490a2f4daa76a7ae2e725b4f2ee7918d4cc808bc29b556e8f3
         | 
    
        data/lib/kamal/cli/accessory.rb
    CHANGED
    
    | @@ -5,11 +5,11 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 5 5 | 
             
                  if name == "all"
         | 
| 6 6 | 
             
                    KAMAL.accessory_names.each { |accessory_name| boot(accessory_name) }
         | 
| 7 7 | 
             
                  else
         | 
| 8 | 
            -
                    with_accessory(name) do |accessory|
         | 
| 8 | 
            +
                    with_accessory(name) do |accessory, hosts|
         | 
| 9 9 | 
             
                      directories(name)
         | 
| 10 10 | 
             
                      upload(name)
         | 
| 11 11 |  | 
| 12 | 
            -
                      on( | 
| 12 | 
            +
                      on(hosts) do
         | 
| 13 13 | 
             
                        execute *KAMAL.registry.login if login
         | 
| 14 14 | 
             
                        execute *KAMAL.auditor.record("Booted #{name} accessory"), verbosity: :debug
         | 
| 15 15 | 
             
                        execute *accessory.run
         | 
| @@ -22,8 +22,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 22 22 | 
             
              desc "upload [NAME]", "Upload accessory files to host", hide: true
         | 
| 23 23 | 
             
              def upload(name)
         | 
| 24 24 | 
             
                mutating do
         | 
| 25 | 
            -
                  with_accessory(name) do |accessory|
         | 
| 26 | 
            -
                    on( | 
| 25 | 
            +
                  with_accessory(name) do |accessory, hosts|
         | 
| 26 | 
            +
                    on(hosts) do
         | 
| 27 27 | 
             
                      accessory.files.each do |(local, remote)|
         | 
| 28 28 | 
             
                        accessory.ensure_local_file_present(local)
         | 
| 29 29 |  | 
| @@ -39,8 +39,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 39 39 | 
             
              desc "directories [NAME]", "Create accessory directories on host", hide: true
         | 
| 40 40 | 
             
              def directories(name)
         | 
| 41 41 | 
             
                mutating do
         | 
| 42 | 
            -
                  with_accessory(name) do |accessory|
         | 
| 43 | 
            -
                    on( | 
| 42 | 
            +
                  with_accessory(name) do |accessory, hosts|
         | 
| 43 | 
            +
                    on(hosts) do
         | 
| 44 44 | 
             
                      accessory.directories.keys.each do |host_path|
         | 
| 45 45 | 
             
                        execute *accessory.make_directory(host_path)
         | 
| 46 46 | 
             
                      end
         | 
| @@ -55,8 +55,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 55 55 | 
             
                  if name == "all"
         | 
| 56 56 | 
             
                    KAMAL.accessory_names.each { |accessory_name| reboot(accessory_name) }
         | 
| 57 57 | 
             
                  else
         | 
| 58 | 
            -
                    with_accessory(name) do |accessory|
         | 
| 59 | 
            -
                      on( | 
| 58 | 
            +
                    with_accessory(name) do |accessory, hosts|
         | 
| 59 | 
            +
                      on(hosts) do
         | 
| 60 60 | 
             
                        execute *KAMAL.registry.login
         | 
| 61 61 | 
             
                      end
         | 
| 62 62 |  | 
| @@ -71,8 +71,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 71 71 | 
             
              desc "start [NAME]", "Start existing accessory container on host"
         | 
| 72 72 | 
             
              def start(name)
         | 
| 73 73 | 
             
                mutating do
         | 
| 74 | 
            -
                  with_accessory(name) do |accessory|
         | 
| 75 | 
            -
                    on( | 
| 74 | 
            +
                  with_accessory(name) do |accessory, hosts|
         | 
| 75 | 
            +
                    on(hosts) do
         | 
| 76 76 | 
             
                      execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug
         | 
| 77 77 | 
             
                      execute *accessory.start
         | 
| 78 78 | 
             
                    end
         | 
| @@ -83,8 +83,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 83 83 | 
             
              desc "stop [NAME]", "Stop existing accessory container on host"
         | 
| 84 84 | 
             
              def stop(name)
         | 
| 85 85 | 
             
                mutating do
         | 
| 86 | 
            -
                  with_accessory(name) do |accessory|
         | 
| 87 | 
            -
                    on( | 
| 86 | 
            +
                  with_accessory(name) do |accessory, hosts|
         | 
| 87 | 
            +
                    on(hosts) do
         | 
| 88 88 | 
             
                      execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug
         | 
| 89 89 | 
             
                      execute *accessory.stop, raise_on_non_zero_exit: false
         | 
| 90 90 | 
             
                    end
         | 
| @@ -107,8 +107,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 107 107 | 
             
                if name == "all"
         | 
| 108 108 | 
             
                  KAMAL.accessory_names.each { |accessory_name| details(accessory_name) }
         | 
| 109 109 | 
             
                else
         | 
| 110 | 
            -
                  with_accessory(name) do |accessory|
         | 
| 111 | 
            -
                    on( | 
| 110 | 
            +
                  with_accessory(name) do |accessory, hosts|
         | 
| 111 | 
            +
                    on(hosts) { puts capture_with_info(*accessory.info) }
         | 
| 112 112 | 
             
                  end
         | 
| 113 113 | 
             
                end
         | 
| 114 114 | 
             
              end
         | 
| @@ -117,7 +117,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 117 117 | 
             
              option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
         | 
| 118 118 | 
             
              option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
         | 
| 119 119 | 
             
              def exec(name, cmd)
         | 
| 120 | 
            -
                with_accessory(name) do |accessory|
         | 
| 120 | 
            +
                with_accessory(name) do |accessory, hosts|
         | 
| 121 121 | 
             
                  case
         | 
| 122 122 | 
             
                  when options[:interactive] && options[:reuse]
         | 
| 123 123 | 
             
                    say "Launching interactive command with via SSH from existing container...", :magenta
         | 
| @@ -129,14 +129,14 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 129 129 |  | 
| 130 130 | 
             
                  when options[:reuse]
         | 
| 131 131 | 
             
                    say "Launching command from existing container...", :magenta
         | 
| 132 | 
            -
                    on( | 
| 132 | 
            +
                    on(hosts) do
         | 
| 133 133 | 
             
                      execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
         | 
| 134 134 | 
             
                      capture_with_info(*accessory.execute_in_existing_container(cmd))
         | 
| 135 135 | 
             
                    end
         | 
| 136 136 |  | 
| 137 137 | 
             
                  else
         | 
| 138 138 | 
             
                    say "Launching command from new container...", :magenta
         | 
| 139 | 
            -
                    on( | 
| 139 | 
            +
                    on(hosts) do
         | 
| 140 140 | 
             
                      execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
         | 
| 141 141 | 
             
                      capture_with_info(*accessory.execute_in_new_container(cmd))
         | 
| 142 142 | 
             
                    end
         | 
| @@ -150,12 +150,12 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 150 150 | 
             
              option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
         | 
| 151 151 | 
             
              option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
         | 
| 152 152 | 
             
              def logs(name)
         | 
| 153 | 
            -
                with_accessory(name) do |accessory|
         | 
| 153 | 
            +
                with_accessory(name) do |accessory, hosts|
         | 
| 154 154 | 
             
                  grep = options[:grep]
         | 
| 155 155 |  | 
| 156 156 | 
             
                  if options[:follow]
         | 
| 157 157 | 
             
                    run_locally do
         | 
| 158 | 
            -
                      info "Following logs on #{ | 
| 158 | 
            +
                      info "Following logs on #{hosts}..."
         | 
| 159 159 | 
             
                      info accessory.follow_logs(grep: grep)
         | 
| 160 160 | 
             
                      exec accessory.follow_logs(grep: grep)
         | 
| 161 161 | 
             
                    end
         | 
| @@ -163,7 +163,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 163 163 | 
             
                    since = options[:since]
         | 
| 164 164 | 
             
                    lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
         | 
| 165 165 |  | 
| 166 | 
            -
                    on( | 
| 166 | 
            +
                    on(hosts) do
         | 
| 167 167 | 
             
                      puts capture_with_info(*accessory.logs(since: since, lines: lines, grep: grep))
         | 
| 168 168 | 
             
                    end
         | 
| 169 169 | 
             
                  end
         | 
| @@ -177,7 +177,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 177 177 | 
             
                  if name == "all"
         | 
| 178 178 | 
             
                    KAMAL.accessory_names.each { |accessory_name| remove(accessory_name) }
         | 
| 179 179 | 
             
                  else
         | 
| 180 | 
            -
                     | 
| 180 | 
            +
                    confirming "This will remove all containers, images and data directories for #{name}. Are you sure?" do
         | 
| 181 181 | 
             
                      with_accessory(name) do
         | 
| 182 182 | 
             
                        stop(name)
         | 
| 183 183 | 
             
                        remove_container(name)
         | 
| @@ -192,8 +192,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 192 192 | 
             
              desc "remove_container [NAME]", "Remove accessory container from host", hide: true
         | 
| 193 193 | 
             
              def remove_container(name)
         | 
| 194 194 | 
             
                mutating do
         | 
| 195 | 
            -
                  with_accessory(name) do |accessory|
         | 
| 196 | 
            -
                    on( | 
| 195 | 
            +
                  with_accessory(name) do |accessory, hosts|
         | 
| 196 | 
            +
                    on(hosts) do
         | 
| 197 197 | 
             
                      execute *KAMAL.auditor.record("Remove #{name} accessory container"), verbosity: :debug
         | 
| 198 198 | 
             
                      execute *accessory.remove_container
         | 
| 199 199 | 
             
                    end
         | 
| @@ -204,8 +204,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 204 204 | 
             
              desc "remove_image [NAME]", "Remove accessory image from host", hide: true
         | 
| 205 205 | 
             
              def remove_image(name)
         | 
| 206 206 | 
             
                mutating do
         | 
| 207 | 
            -
                  with_accessory(name) do |accessory|
         | 
| 208 | 
            -
                    on( | 
| 207 | 
            +
                  with_accessory(name) do |accessory, hosts|
         | 
| 208 | 
            +
                    on(hosts) do
         | 
| 209 209 | 
             
                      execute *KAMAL.auditor.record("Removed #{name} accessory image"), verbosity: :debug
         | 
| 210 210 | 
             
                      execute *accessory.remove_image
         | 
| 211 211 | 
             
                    end
         | 
| @@ -216,8 +216,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 216 216 | 
             
              desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host", hide: true
         | 
| 217 217 | 
             
              def remove_service_directory(name)
         | 
| 218 218 | 
             
                mutating do
         | 
| 219 | 
            -
                  with_accessory(name) do |accessory|
         | 
| 220 | 
            -
                    on( | 
| 219 | 
            +
                  with_accessory(name) do |accessory, hosts|
         | 
| 220 | 
            +
                    on(hosts) do
         | 
| 221 221 | 
             
                      execute *accessory.remove_service_directory
         | 
| 222 222 | 
             
                    end
         | 
| 223 223 | 
             
                  end
         | 
| @@ -226,8 +226,9 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 226 226 |  | 
| 227 227 | 
             
              private
         | 
| 228 228 | 
             
                def with_accessory(name)
         | 
| 229 | 
            -
                  if  | 
| 230 | 
            -
                     | 
| 229 | 
            +
                  if KAMAL.config.accessory(name)
         | 
| 230 | 
            +
                    accessory = KAMAL.accessory(name)
         | 
| 231 | 
            +
                    yield accessory, accessory_hosts(accessory)
         | 
| 231 232 | 
             
                  else
         | 
| 232 233 | 
             
                    error_on_missing_accessory(name)
         | 
| 233 234 | 
             
                  end
         | 
| @@ -240,4 +241,12 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base | |
| 240 241 | 
             
                    "No accessory by the name of '#{name}'" +
         | 
| 241 242 | 
             
                    (options ? " (options: #{options.to_sentence})" : "")
         | 
| 242 243 | 
             
                end
         | 
| 244 | 
            +
             | 
| 245 | 
            +
                def accessory_hosts(accessory)
         | 
| 246 | 
            +
                  if KAMAL.specific_hosts&.any?
         | 
| 247 | 
            +
                    KAMAL.specific_hosts & accessory.hosts
         | 
| 248 | 
            +
                  else
         | 
| 249 | 
            +
                    accessory.hosts
         | 
| 250 | 
            +
                  end
         | 
| 251 | 
            +
                end
         | 
| 243 252 | 
             
            end
         | 
| @@ -0,0 +1,67 @@ | |
| 1 | 
            +
            class Kamal::Cli::App::Boot
         | 
| 2 | 
            +
              attr_reader :host, :role, :version, :sshkit
         | 
| 3 | 
            +
              delegate :execute, :capture_with_info, :info, to: :sshkit
         | 
| 4 | 
            +
              delegate :uses_cord?, :assets?, to: :role
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def initialize(host, role, version, sshkit)
         | 
| 7 | 
            +
                @host = host
         | 
| 8 | 
            +
                @role = role
         | 
| 9 | 
            +
                @version = version
         | 
| 10 | 
            +
                @sshkit = sshkit
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              def run
         | 
| 14 | 
            +
                old_version = old_version_renamed_if_clashing
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                start_new_version
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                if old_version
         | 
| 19 | 
            +
                  stop_old_version(old_version)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              private
         | 
| 24 | 
            +
                def app
         | 
| 25 | 
            +
                  @app ||= KAMAL.app(role: role)
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def auditor
         | 
| 29 | 
            +
                  @auditor = KAMAL.auditor(role: role)
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def audit(message)
         | 
| 33 | 
            +
                  execute *auditor.record(message), verbosity: :debug
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def old_version_renamed_if_clashing
         | 
| 37 | 
            +
                  if capture_with_info(*app.container_id_for_version(version), raise_on_non_zero_exit: false).present?
         | 
| 38 | 
            +
                    renamed_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
         | 
| 39 | 
            +
                    info "Renaming container #{version} to #{renamed_version} as already deployed on #{host}"
         | 
| 40 | 
            +
                    audit("Renaming container #{version} to #{renamed_version}")
         | 
| 41 | 
            +
                    execute *app.rename_container(version: version, new_version: renamed_version)
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip.presence
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def start_new_version
         | 
| 48 | 
            +
                  audit "Booted app version #{version}"
         | 
| 49 | 
            +
                  execute *app.tie_cord(role.cord_host_file) if uses_cord?
         | 
| 50 | 
            +
                  execute *app.run(hostname: "#{host}-#{SecureRandom.hex(6)}")
         | 
| 51 | 
            +
                  Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                def stop_old_version(version)
         | 
| 55 | 
            +
                  if uses_cord?
         | 
| 56 | 
            +
                    cord = capture_with_info(*app.cord(version: version), raise_on_non_zero_exit: false).strip
         | 
| 57 | 
            +
                    if cord.present?
         | 
| 58 | 
            +
                      execute *app.cut_cord(cord)
         | 
| 59 | 
            +
                      Kamal::Cli::Healthcheck::Poller.wait_for_unhealthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  execute *app.stop(version: version), raise_on_non_zero_exit: false
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  execute *app.clean_up_assets if assets?
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
            end
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            class Kamal::Cli::App::PrepareAssets
         | 
| 2 | 
            +
              attr_reader :host, :role, :sshkit
         | 
| 3 | 
            +
              delegate :execute, :capture_with_info, :info, to: :sshkit
         | 
| 4 | 
            +
              delegate :assets?, to: :role
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def initialize(host, role, sshkit)
         | 
| 7 | 
            +
                @host = host
         | 
| 8 | 
            +
                @role = role
         | 
| 9 | 
            +
                @sshkit = sshkit
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def run
         | 
| 13 | 
            +
                if assets?
         | 
| 14 | 
            +
                  execute *app.extract_assets
         | 
| 15 | 
            +
                  old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
         | 
| 16 | 
            +
                  execute *app.sync_asset_volumes(old_version: old_version)
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              private
         | 
| 21 | 
            +
                def app
         | 
| 22 | 
            +
                  @app ||= KAMAL.app(role: role)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
            end
         | 
    
        data/lib/kamal/cli/app.rb
    CHANGED
    
    | @@ -7,60 +7,23 @@ class Kamal::Cli::App < Kamal::Cli::Base | |
| 7 7 | 
             
                    using_version(version_or_latest) do |version|
         | 
| 8 8 | 
             
                      say "Start container with version #{version} using a #{KAMAL.config.readiness_delay}s readiness delay (or reboot if already running)...", :magenta
         | 
| 9 9 |  | 
| 10 | 
            +
                      # Assets are prepared in a separate step to ensure they are on all hosts before booting
         | 
| 10 11 | 
             
                      on(KAMAL.hosts) do
         | 
| 11 | 
            -
                        execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug
         | 
| 12 | 
            -
                        execute *KAMAL.app.tag_current_image_as_latest
         | 
| 13 | 
            -
             | 
| 14 12 | 
             
                        KAMAL.roles_on(host).each do |role|
         | 
| 15 | 
            -
                           | 
| 16 | 
            -
                          role_config = KAMAL.config.role(role)
         | 
| 17 | 
            -
             | 
| 18 | 
            -
                          if role_config.assets?
         | 
| 19 | 
            -
                            execute *app.extract_assets
         | 
| 20 | 
            -
                            old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
         | 
| 21 | 
            -
                            execute *app.sync_asset_volumes(old_version: old_version)
         | 
| 22 | 
            -
                          end
         | 
| 13 | 
            +
                          Kamal::Cli::App::PrepareAssets.new(host, role, self).run
         | 
| 23 14 | 
             
                        end
         | 
| 24 15 | 
             
                      end
         | 
| 25 16 |  | 
| 26 17 | 
             
                      on(KAMAL.hosts, **KAMAL.boot_strategy) do |host|
         | 
| 27 18 | 
             
                        KAMAL.roles_on(host).each do |role|
         | 
| 28 | 
            -
                           | 
| 29 | 
            -
                          auditor = KAMAL.auditor(role: role)
         | 
| 30 | 
            -
                          role_config = KAMAL.config.role(role)
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                          if capture_with_info(*app.container_id_for_version(version), raise_on_non_zero_exit: false).present?
         | 
| 33 | 
            -
                            tmp_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
         | 
| 34 | 
            -
                            info "Renaming container #{version} to #{tmp_version} as already deployed on #{host}"
         | 
| 35 | 
            -
                            execute *auditor.record("Renaming container #{version} to #{tmp_version}"), verbosity: :debug
         | 
| 36 | 
            -
                            execute *app.rename_container(version: version, new_version: tmp_version)
         | 
| 37 | 
            -
                          end
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                          old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                          execute *app.tie_cord(role_config.cord_host_file) if role_config.uses_cord?
         | 
| 42 | 
            -
             | 
| 43 | 
            -
                          execute *auditor.record("Booted app version #{version}"), verbosity: :debug
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                          execute *app.run(hostname: "#{host}-#{SecureRandom.hex(6)}")
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                          Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                          if old_version.present?
         | 
| 50 | 
            -
                            if role_config.uses_cord?
         | 
| 51 | 
            -
                              cord = capture_with_info(*app.cord(version: old_version), raise_on_non_zero_exit: false).strip
         | 
| 52 | 
            -
                              if cord.present?
         | 
| 53 | 
            -
                                execute *app.cut_cord(cord)
         | 
| 54 | 
            -
                                Kamal::Cli::Healthcheck::Poller.wait_for_unhealthy(pause_after_ready: true) { capture_with_info(*app.status(version: old_version)) }
         | 
| 55 | 
            -
                              end
         | 
| 56 | 
            -
                            end
         | 
| 57 | 
            -
             | 
| 58 | 
            -
                            execute *app.stop(version: old_version), raise_on_non_zero_exit: false
         | 
| 59 | 
            -
             | 
| 60 | 
            -
                            execute *app.clean_up_assets if role_config.assets?
         | 
| 61 | 
            -
                          end
         | 
| 19 | 
            +
                          Kamal::Cli::App::Boot.new(host, role, version, self).run
         | 
| 62 20 | 
             
                        end
         | 
| 63 21 | 
             
                      end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                      on(KAMAL.hosts) do |host|
         | 
| 24 | 
            +
                        execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug
         | 
| 25 | 
            +
                        execute *KAMAL.app.tag_latest_image
         | 
| 26 | 
            +
                      end
         | 
| 64 27 | 
             
                    end
         | 
| 65 28 | 
             
                  end
         | 
| 66 29 | 
             
                end
         | 
| @@ -109,13 +72,15 @@ class Kamal::Cli::App < Kamal::Cli::Base | |
| 109 72 | 
             
              desc "exec [CMD]", "Execute a custom command on servers (use --help to show options)"
         | 
| 110 73 | 
             
              option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
         | 
| 111 74 | 
             
              option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
         | 
| 75 | 
            +
              option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
         | 
| 112 76 | 
             
              def exec(cmd)
         | 
| 77 | 
            +
                env = options[:env]
         | 
| 113 78 | 
             
                case
         | 
| 114 79 | 
             
                when options[:interactive] && options[:reuse]
         | 
| 115 80 | 
             
                  say "Get current version of running container...", :magenta unless options[:version]
         | 
| 116 81 | 
             
                  using_version(options[:version] || current_running_version) do |version|
         | 
| 117 82 | 
             
                    say "Launching interactive command with version #{version} via SSH from existing container on #{KAMAL.primary_host}...", :magenta
         | 
| 118 | 
            -
                    run_locally { exec KAMAL.app(role: KAMAL.primary_role).execute_in_existing_container_over_ssh(cmd, host: KAMAL.primary_host) }
         | 
| 83 | 
            +
                    run_locally { exec KAMAL.app(role: KAMAL.primary_role).execute_in_existing_container_over_ssh(cmd, host: KAMAL.primary_host, env: env) }
         | 
| 119 84 | 
             
                  end
         | 
| 120 85 |  | 
| 121 86 | 
             
                when options[:interactive]
         | 
| @@ -123,7 +88,7 @@ class Kamal::Cli::App < Kamal::Cli::Base | |
| 123 88 | 
             
                  using_version(version_or_latest) do |version|
         | 
| 124 89 | 
             
                    say "Launching interactive command with version #{version} via SSH from new container on #{KAMAL.primary_host}...", :magenta
         | 
| 125 90 | 
             
                    run_locally do
         | 
| 126 | 
            -
                      exec KAMAL.app(role: KAMAL.primary_role).execute_in_new_container_over_ssh(cmd, host: KAMAL.primary_host)
         | 
| 91 | 
            +
                      exec KAMAL.app(role: KAMAL.primary_role).execute_in_new_container_over_ssh(cmd, host: KAMAL.primary_host, env: env)
         | 
| 127 92 | 
             
                    end
         | 
| 128 93 | 
             
                  end
         | 
| 129 94 |  | 
| @@ -137,7 +102,7 @@ class Kamal::Cli::App < Kamal::Cli::Base | |
| 137 102 |  | 
| 138 103 | 
             
                      roles.each do |role|
         | 
| 139 104 | 
             
                        execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}", role: role), verbosity: :debug
         | 
| 140 | 
            -
                        puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_existing_container(cmd))
         | 
| 105 | 
            +
                        puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_existing_container(cmd, env: env))
         | 
| 141 106 | 
             
                      end
         | 
| 142 107 | 
             
                    end
         | 
| 143 108 | 
             
                  end
         | 
| @@ -151,7 +116,7 @@ class Kamal::Cli::App < Kamal::Cli::Base | |
| 151 116 |  | 
| 152 117 | 
             
                      roles.each do |role|
         | 
| 153 118 | 
             
                        execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
         | 
| 154 | 
            -
                        puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_new_container(cmd))
         | 
| 119 | 
            +
                        puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_new_container(cmd, env: env))
         | 
| 155 120 | 
             
                      end
         | 
| 156 121 | 
             
                    end
         | 
| 157 122 | 
             
                  end
         | 
| @@ -175,7 +140,10 @@ class Kamal::Cli::App < Kamal::Cli::Base | |
| 175 140 | 
             
                    roles = KAMAL.roles_on(host)
         | 
| 176 141 |  | 
| 177 142 | 
             
                    roles.each do |role|
         | 
| 178 | 
            -
                       | 
| 143 | 
            +
                      versions = capture_with_info(*KAMAL.app(role: role).list_versions, raise_on_non_zero_exit: false).split("\n")
         | 
| 144 | 
            +
                      versions -= [ capture_with_info(*KAMAL.app(role: role).current_running_version, raise_on_non_zero_exit: false).strip ]
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                      versions.each do |version|
         | 
| 179 147 | 
             
                        if stop
         | 
| 180 148 | 
             
                          puts_by_host host, "Stopping stale container for role #{role} with version #{version}"
         | 
| 181 149 | 
             
                          execute *KAMAL.app(role: role).stop(version: version), raise_on_non_zero_exit: false
         | 
| @@ -202,19 +170,20 @@ class Kamal::Cli::App < Kamal::Cli::Base | |
| 202 170 | 
             
                # FIXME: Catch when app containers aren't running
         | 
| 203 171 |  | 
| 204 172 | 
             
                grep = options[:grep]
         | 
| 205 | 
            -
             | 
| 173 | 
            +
                since = options[:since]
         | 
| 206 174 | 
             
                if options[:follow]
         | 
| 175 | 
            +
                  lines = options[:lines].presence || ((since || grep) ? nil : 10) # Default to 10 lines if since or grep isn't set
         | 
| 176 | 
            +
             | 
| 207 177 | 
             
                  run_locally do
         | 
| 208 178 | 
             
                    info "Following logs on #{KAMAL.primary_host}..."
         | 
| 209 179 |  | 
| 210 | 
            -
                    KAMAL.specific_roles ||= ["web"]
         | 
| 180 | 
            +
                    KAMAL.specific_roles ||= [ "web" ]
         | 
| 211 181 | 
             
                    role = KAMAL.roles_on(KAMAL.primary_host).first
         | 
| 212 182 |  | 
| 213 | 
            -
                    info KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, grep: grep)
         | 
| 214 | 
            -
                    exec KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, grep: grep)
         | 
| 183 | 
            +
                    info KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, lines: lines, grep: grep)
         | 
| 184 | 
            +
                    exec KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, lines: lines, grep: grep)
         | 
| 215 185 | 
             
                  end
         | 
| 216 186 | 
             
                else
         | 
| 217 | 
            -
                  since = options[:since]
         | 
| 218 187 | 
             
                  lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
         | 
| 219 188 |  | 
| 220 189 | 
             
                  on(KAMAL.hosts) do |host|
         | 
| @@ -310,18 +279,7 @@ class Kamal::Cli::App < Kamal::Cli::Base | |
| 310 279 | 
             
                  version.presence
         | 
| 311 280 | 
             
                end
         | 
| 312 281 |  | 
| 313 | 
            -
                def stale_versions(host:, role:)
         | 
| 314 | 
            -
                  versions = nil
         | 
| 315 | 
            -
                  on(host) do
         | 
| 316 | 
            -
                    versions = \
         | 
| 317 | 
            -
                      capture_with_info(*KAMAL.app(role: role).list_versions, raise_on_non_zero_exit: false)
         | 
| 318 | 
            -
                      .split("\n")
         | 
| 319 | 
            -
                      .drop(1)
         | 
| 320 | 
            -
                  end
         | 
| 321 | 
            -
                  versions
         | 
| 322 | 
            -
                end
         | 
| 323 | 
            -
             | 
| 324 282 | 
             
                def version_or_latest
         | 
| 325 | 
            -
                  options[:version] ||  | 
| 283 | 
            +
                  options[:version] || KAMAL.config.latest_tag
         | 
| 326 284 | 
             
                end
         | 
| 327 285 | 
             
            end
         | 
    
        data/lib/kamal/cli/base.rb
    CHANGED
    
    | @@ -73,7 +73,7 @@ module Kamal::Cli | |
| 73 73 | 
             
                  def print_runtime
         | 
| 74 74 | 
             
                    started_at = Time.now
         | 
| 75 75 | 
             
                    yield
         | 
| 76 | 
            -
                     | 
| 76 | 
            +
                    Time.now - started_at
         | 
| 77 77 | 
             
                  ensure
         | 
| 78 78 | 
             
                    runtime = Time.now - started_at
         | 
| 79 79 | 
             
                    puts "  Finished all in #{sprintf("%.1f seconds", runtime)}"
         | 
| @@ -84,7 +84,7 @@ module Kamal::Cli | |
| 84 84 |  | 
| 85 85 | 
             
                    run_hook "pre-connect"
         | 
| 86 86 |  | 
| 87 | 
            -
                     | 
| 87 | 
            +
                    ensure_run_and_locks_directory
         | 
| 88 88 |  | 
| 89 89 | 
             
                    acquire_lock
         | 
| 90 90 |  | 
| @@ -103,6 +103,16 @@ module Kamal::Cli | |
| 103 103 | 
             
                    release_lock
         | 
| 104 104 | 
             
                  end
         | 
| 105 105 |  | 
| 106 | 
            +
                  def confirming(question)
         | 
| 107 | 
            +
                    return yield if options[:confirmed]
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                    if ask(question, limited_to: %w[ y N ], default: "N") == "y"
         | 
| 110 | 
            +
                      yield
         | 
| 111 | 
            +
                    else
         | 
| 112 | 
            +
                      say "Aborted", :red
         | 
| 113 | 
            +
                    end
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
             | 
| 106 116 | 
             
                  def acquire_lock
         | 
| 107 117 | 
             
                    raise_if_locked do
         | 
| 108 118 | 
             
                      say "Acquiring the deploy lock...", :magenta
         | 
| @@ -123,8 +133,9 @@ module Kamal::Cli | |
| 123 133 | 
             
                    yield
         | 
| 124 134 | 
             
                  rescue SSHKit::Runner::ExecuteError => e
         | 
| 125 135 | 
             
                    if e.message =~ /cannot create directory/
         | 
| 136 | 
            +
                      say "Deploy lock already in place!", :red
         | 
| 126 137 | 
             
                      on(KAMAL.primary_host) { puts capture_with_debug(*KAMAL.lock.status) }
         | 
| 127 | 
            -
                      raise LockError, "Deploy lock found"
         | 
| 138 | 
            +
                      raise LockError, "Deploy lock found. Run 'kamal lock help' for more information"
         | 
| 128 139 | 
             
                    else
         | 
| 129 140 | 
             
                      raise e
         | 
| 130 141 | 
             
                    end
         | 
| @@ -146,9 +157,9 @@ module Kamal::Cli | |
| 146 157 |  | 
| 147 158 | 
             
                      say "Running the #{hook} hook...", :magenta
         | 
| 148 159 | 
             
                      run_locally do
         | 
| 149 | 
            -
                         | 
| 150 | 
            -
                      rescue SSHKit::Command::Failed
         | 
| 151 | 
            -
                        raise HookError.new("Hook `#{hook}` failed")
         | 
| 160 | 
            +
                        execute *KAMAL.hook.run(hook, **details, **extra_details)
         | 
| 161 | 
            +
                      rescue SSHKit::Command::Failed => e
         | 
| 162 | 
            +
                        raise HookError.new("Hook `#{hook}` failed:\n#{e.message}")
         | 
| 152 163 | 
             
                      end
         | 
| 153 164 | 
             
                    end
         | 
| 154 165 | 
             
                  end
         | 
| @@ -175,10 +186,14 @@ module Kamal::Cli | |
| 175 186 | 
             
                    instance_variable_get("@_invocations").first
         | 
| 176 187 | 
             
                  end
         | 
| 177 188 |  | 
| 178 | 
            -
                  def  | 
| 189 | 
            +
                  def ensure_run_and_locks_directory
         | 
| 179 190 | 
             
                    on(KAMAL.hosts) do
         | 
| 180 191 | 
             
                      execute(*KAMAL.server.ensure_run_directory)
         | 
| 181 192 | 
             
                    end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                    on(KAMAL.primary_host) do
         | 
| 195 | 
            +
                      execute(*KAMAL.lock.ensure_locks_directory)
         | 
| 196 | 
            +
                    end
         | 
| 182 197 | 
             
                  end
         | 
| 183 | 
            -
             | 
| 198 | 
            +
              end
         | 
| 184 199 | 
             
            end
         | 
    
        data/lib/kamal/cli/env.rb
    CHANGED
    
    | @@ -8,22 +8,21 @@ class Kamal::Cli::Env < Kamal::Cli::Base | |
| 8 8 | 
             
                    execute *KAMAL.auditor.record("Pushed env files"), verbosity: :debug
         | 
| 9 9 |  | 
| 10 10 | 
             
                    KAMAL.roles_on(host).each do |role|
         | 
| 11 | 
            -
                      role_config = KAMAL.config.role(role)
         | 
| 12 11 | 
             
                      execute *KAMAL.app(role: role).make_env_directory
         | 
| 13 | 
            -
                      upload!  | 
| 12 | 
            +
                      upload! role.env.secrets_io, role.env.secrets_file, mode: 400
         | 
| 14 13 | 
             
                    end
         | 
| 15 14 | 
             
                  end
         | 
| 16 15 |  | 
| 17 16 | 
             
                  on(KAMAL.traefik_hosts) do
         | 
| 18 17 | 
             
                    execute *KAMAL.traefik.make_env_directory
         | 
| 19 | 
            -
                    upload!  | 
| 18 | 
            +
                    upload! KAMAL.traefik.env.secrets_io, KAMAL.traefik.env.secrets_file, mode: 400
         | 
| 20 19 | 
             
                  end
         | 
| 21 20 |  | 
| 22 21 | 
             
                  on(KAMAL.accessory_hosts) do
         | 
| 23 22 | 
             
                    KAMAL.accessories_on(host).each do |accessory|
         | 
| 24 23 | 
             
                      accessory_config = KAMAL.config.accessory(accessory)
         | 
| 25 24 | 
             
                      execute *KAMAL.accessory(accessory).make_env_directory
         | 
| 26 | 
            -
                      upload!  | 
| 25 | 
            +
                      upload! accessory_config.env.secrets_io, accessory_config.env.secrets_file, mode: 400
         | 
| 27 26 | 
             
                    end
         | 
| 28 27 | 
             
                  end
         | 
| 29 28 | 
             
                end
         | 
| @@ -36,7 +35,6 @@ class Kamal::Cli::Env < Kamal::Cli::Base | |
| 36 35 | 
             
                    execute *KAMAL.auditor.record("Deleted env files"), verbosity: :debug
         | 
| 37 36 |  | 
| 38 37 | 
             
                    KAMAL.roles_on(host).each do |role|
         | 
| 39 | 
            -
                      role_config = KAMAL.config.role(role)
         | 
| 40 38 | 
             
                      execute *KAMAL.app(role: role).remove_env_file
         | 
| 41 39 | 
             
                    end
         | 
| 42 40 | 
             
                  end
         | 
    
        data/lib/kamal/cli/main.rb
    CHANGED
    
    | @@ -1,15 +1,18 @@ | |
| 1 1 | 
             
            class Kamal::Cli::Main < Kamal::Cli::Base
         | 
| 2 2 | 
             
              desc "setup", "Setup all accessories, push the env, and deploy app to servers"
         | 
| 3 | 
            +
              option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
         | 
| 3 4 | 
             
              def setup
         | 
| 4 5 | 
             
                print_runtime do
         | 
| 5 6 | 
             
                  mutating do
         | 
| 7 | 
            +
                    invoke_options = deploy_options
         | 
| 8 | 
            +
             | 
| 6 9 | 
             
                    say "Ensure Docker is installed...", :magenta
         | 
| 7 | 
            -
                    invoke "kamal:cli:server:bootstrap"
         | 
| 10 | 
            +
                    invoke "kamal:cli:server:bootstrap", [], invoke_options
         | 
| 8 11 |  | 
| 9 12 | 
             
                    say "Push env files...", :magenta
         | 
| 10 | 
            -
                    invoke "kamal:cli:env:push"
         | 
| 13 | 
            +
                    invoke "kamal:cli:env:push", [], invoke_options
         | 
| 11 14 |  | 
| 12 | 
            -
                    invoke "kamal:cli:accessory:boot", [ "all" ]
         | 
| 15 | 
            +
                    invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options
         | 
| 13 16 | 
             
                    deploy
         | 
| 14 17 | 
             
                  end
         | 
| 15 18 | 
             
                end
         | 
| @@ -194,7 +197,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base | |
| 194 197 | 
             
              option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
         | 
| 195 198 | 
             
              def remove
         | 
| 196 199 | 
             
                mutating do
         | 
| 197 | 
            -
                   | 
| 200 | 
            +
                  confirming "This will remove all containers and images. Are you sure?" do
         | 
| 198 201 | 
             
                    invoke "kamal:cli:traefik:remove", [], options.without(:confirmed)
         | 
| 199 202 | 
             
                    invoke "kamal:cli:app:remove", [], options.without(:confirmed)
         | 
| 200 203 | 
             
                    invoke "kamal:cli:accessory:remove", [ "all" ], options
         | 
    
        data/lib/kamal/cli/prune.rb
    CHANGED
    
    | @@ -18,12 +18,16 @@ class Kamal::Cli::Prune < Kamal::Cli::Base | |
| 18 18 | 
             
                end
         | 
| 19 19 | 
             
              end
         | 
| 20 20 |  | 
| 21 | 
            -
              desc "containers", "Prune all stopped containers, except the last 5"
         | 
| 21 | 
            +
              desc "containers", "Prune all stopped containers, except the last n (default 5)"
         | 
| 22 | 
            +
              option :retain, type: :numeric, default: nil, desc: "Number of containers to retain"
         | 
| 22 23 | 
             
              def containers
         | 
| 24 | 
            +
                retain = options.fetch(:retain, KAMAL.config.retain_containers)
         | 
| 25 | 
            +
                raise "retain must be at least 1" if retain < 1
         | 
| 26 | 
            +
             | 
| 23 27 | 
             
                mutating do
         | 
| 24 28 | 
             
                  on(KAMAL.hosts) do
         | 
| 25 29 | 
             
                    execute *KAMAL.auditor.record("Pruned containers"), verbosity: :debug
         | 
| 26 | 
            -
                    execute *KAMAL.prune.app_containers
         | 
| 30 | 
            +
                    execute *KAMAL.prune.app_containers(retain: retain)
         | 
| 27 31 | 
             
                    execute *KAMAL.prune.healthcheck_containers
         | 
| 28 32 | 
             
                  end
         | 
| 29 33 | 
             
                end
         | 
    
        data/lib/kamal/cli/server.rb
    CHANGED
    
    | @@ -17,7 +17,9 @@ class Kamal::Cli::Server < Kamal::Cli::Base | |
| 17 17 | 
             
                end
         | 
| 18 18 |  | 
| 19 19 | 
             
                if missing.any?
         | 
| 20 | 
            -
                  raise "Docker is not installed on #{missing.join(", ")} and can't be automatically installed without having root access and  | 
| 20 | 
            +
                  raise "Docker is not installed on #{missing.join(", ")} and can't be automatically installed without having root access and either `wget` or `curl`. Install Docker manually: https://docs.docker.com/engine/install/"
         | 
| 21 21 | 
             
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                run_hook "docker-setup"
         | 
| 22 24 | 
             
              end
         | 
| 23 25 | 
             
            end
         | 
| @@ -63,7 +63,7 @@ registry: | |
| 63 63 | 
             
            #     directories:
         | 
| 64 64 | 
             
            #       - data:/data
         | 
| 65 65 |  | 
| 66 | 
            -
            # Configure custom arguments for Traefik
         | 
| 66 | 
            +
            # Configure custom arguments for Traefik. Be sure to reboot traefik when you modify it.
         | 
| 67 67 | 
             
            # traefik:
         | 
| 68 68 | 
             
            #   args:
         | 
| 69 69 | 
             
            #     accesslog: true
         | 
| @@ -77,6 +77,10 @@ registry: | |
| 77 77 | 
             
            # Bridge fingerprinted assets, like JS and CSS, between versions to avoid
         | 
| 78 78 | 
             
            # hitting 404 on in-flight requests. Combines all files from new and old
         | 
| 79 79 | 
             
            # version inside the asset_path.
         | 
| 80 | 
            +
            #
         | 
| 81 | 
            +
            # If your app is using the Sprockets gem, ensure it sets `config.assets.manifest`.
         | 
| 82 | 
            +
            # See https://github.com/basecamp/kamal/issues/626 for details
         | 
| 83 | 
            +
            #
         | 
| 80 84 | 
             
            # asset_path: /rails/public/assets
         | 
| 81 85 |  | 
| 82 86 | 
             
            # Configure rolling deploys by setting a wait time between batches of restarts.
         |