kamal 1.8.2 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/kamal/cli/accessory.rb +44 -20
  4. data/lib/kamal/cli/alias/command.rb +9 -0
  5. data/lib/kamal/cli/app/boot.rb +22 -16
  6. data/lib/kamal/cli/app.rb +40 -5
  7. data/lib/kamal/cli/base.rb +19 -51
  8. data/lib/kamal/cli/build.rb +12 -13
  9. data/lib/kamal/cli/healthcheck/barrier.rb +2 -0
  10. data/lib/kamal/cli/healthcheck/poller.rb +18 -39
  11. data/lib/kamal/cli/lock.rb +2 -3
  12. data/lib/kamal/cli/main.rb +54 -51
  13. data/lib/kamal/cli/proxy.rb +224 -0
  14. data/lib/kamal/cli/prune.rb +0 -1
  15. data/lib/kamal/cli/secrets.rb +36 -0
  16. data/lib/kamal/cli/server.rb +2 -3
  17. data/lib/kamal/cli/templates/deploy.yml +4 -21
  18. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  19. data/lib/kamal/cli/templates/secrets +16 -0
  20. data/lib/kamal/cli.rb +1 -0
  21. data/lib/kamal/commander/specifics.rb +3 -3
  22. data/lib/kamal/commander.rb +24 -8
  23. data/lib/kamal/commands/accessory.rb +7 -7
  24. data/lib/kamal/commands/app/assets.rb +8 -8
  25. data/lib/kamal/commands/app/proxy.rb +16 -0
  26. data/lib/kamal/commands/app.rb +7 -15
  27. data/lib/kamal/commands/auditor.rb +6 -3
  28. data/lib/kamal/commands/base.rb +8 -0
  29. data/lib/kamal/commands/builder/base.rb +26 -13
  30. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  31. data/lib/kamal/commands/builder/local.rb +14 -0
  32. data/lib/kamal/commands/builder/remote.rb +63 -0
  33. data/lib/kamal/commands/builder.rb +13 -29
  34. data/lib/kamal/commands/docker.rb +4 -0
  35. data/lib/kamal/commands/hook.rb +5 -2
  36. data/lib/kamal/commands/lock.rb +2 -6
  37. data/lib/kamal/commands/proxy.rb +77 -0
  38. data/lib/kamal/commands/prune.rb +1 -9
  39. data/lib/kamal/commands/server.rb +11 -1
  40. data/lib/kamal/configuration/accessory.rb +14 -2
  41. data/lib/kamal/configuration/alias.rb +15 -0
  42. data/lib/kamal/configuration/builder.rb +52 -18
  43. data/lib/kamal/configuration/docs/alias.yml +26 -0
  44. data/lib/kamal/configuration/docs/builder.yml +26 -23
  45. data/lib/kamal/configuration/docs/configuration.yml +22 -16
  46. data/lib/kamal/configuration/docs/env.yml +10 -11
  47. data/lib/kamal/configuration/docs/proxy.yml +100 -0
  48. data/lib/kamal/configuration/docs/registry.yml +4 -2
  49. data/lib/kamal/configuration/docs/role.yml +3 -5
  50. data/lib/kamal/configuration/env/tag.rb +4 -3
  51. data/lib/kamal/configuration/env.rb +10 -17
  52. data/lib/kamal/configuration/proxy.rb +66 -0
  53. data/lib/kamal/configuration/registry.rb +3 -2
  54. data/lib/kamal/configuration/role.rb +63 -94
  55. data/lib/kamal/configuration/validator/alias.rb +15 -0
  56. data/lib/kamal/configuration/validator/builder.rb +4 -0
  57. data/lib/kamal/configuration/validator/proxy.rb +11 -0
  58. data/lib/kamal/configuration/validator.rb +42 -24
  59. data/lib/kamal/configuration.rb +91 -33
  60. data/lib/kamal/env_file.rb +4 -0
  61. data/lib/kamal/secrets/adapters/base.rb +18 -0
  62. data/lib/kamal/secrets/adapters/bitwarden.rb +64 -0
  63. data/lib/kamal/secrets/adapters/last_pass.rb +30 -0
  64. data/lib/kamal/secrets/adapters/one_password.rb +61 -0
  65. data/lib/kamal/secrets/adapters/test.rb +10 -0
  66. data/lib/kamal/secrets/adapters.rb +14 -0
  67. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +32 -0
  68. data/lib/kamal/secrets.rb +37 -0
  69. data/lib/kamal/sshkit_with_ext.rb +1 -0
  70. data/lib/kamal/utils.rb +28 -0
  71. data/lib/kamal/version.rb +1 -1
  72. data/lib/kamal.rb +3 -1
  73. metadata +32 -23
  74. data/lib/kamal/cli/env.rb +0 -54
  75. data/lib/kamal/cli/templates/sample_hooks/post-traefik-reboot.sample +0 -3
  76. data/lib/kamal/cli/templates/template.env +0 -2
  77. data/lib/kamal/cli/traefik.rb +0 -122
  78. data/lib/kamal/commands/app/cord.rb +0 -22
  79. data/lib/kamal/commands/builder/multiarch/remote.rb +0 -61
  80. data/lib/kamal/commands/builder/multiarch.rb +0 -41
  81. data/lib/kamal/commands/builder/native/cached.rb +0 -25
  82. data/lib/kamal/commands/builder/native/remote.rb +0 -67
  83. data/lib/kamal/commands/builder/native.rb +0 -20
  84. data/lib/kamal/commands/traefik.rb +0 -85
  85. data/lib/kamal/configuration/docs/healthcheck.yml +0 -59
  86. data/lib/kamal/configuration/docs/traefik.yml +0 -62
  87. data/lib/kamal/configuration/healthcheck.rb +0 -63
  88. data/lib/kamal/configuration/traefik.rb +0 -60
  89. /data/lib/kamal/cli/templates/sample_hooks/{pre-traefik-reboot.sample → pre-proxy-reboot.sample} +0 -0
@@ -9,10 +9,6 @@ class Kamal::Cli::Main < Kamal::Cli::Base
9
9
  say "Ensure Docker is installed...", :magenta
10
10
  invoke "kamal:cli:server:bootstrap", [], invoke_options
11
11
 
12
- say "Evaluate and push env files...", :magenta
13
- invoke "kamal:cli:main:envify", [], invoke_options
14
- invoke "kamal:cli:env:push", [], invoke_options
15
-
16
12
  invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options
17
13
  deploy
18
14
  end
@@ -37,10 +33,10 @@ class Kamal::Cli::Main < Kamal::Cli::Base
37
33
  end
38
34
 
39
35
  with_lock do
40
- run_hook "pre-deploy"
36
+ run_hook "pre-deploy", secrets: true
41
37
 
42
- say "Ensure Traefik is running...", :magenta
43
- invoke "kamal:cli:traefik:boot", [], invoke_options
38
+ say "Ensure kamal-proxy is running...", :magenta
39
+ invoke "kamal:cli:proxy:boot", [], invoke_options
44
40
 
45
41
  say "Detect stale containers...", :magenta
46
42
  invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
@@ -52,10 +48,10 @@ class Kamal::Cli::Main < Kamal::Cli::Base
52
48
  end
53
49
  end
54
50
 
55
- run_hook "post-deploy", runtime: runtime.round
51
+ run_hook "post-deploy", secrets: true, runtime: runtime.round
56
52
  end
57
53
 
58
- desc "redeploy", "Deploy app to servers without bootstrapping servers, starting Traefik, pruning, and registry login"
54
+ desc "redeploy", "Deploy app to servers without bootstrapping servers, starting kamal-proxy, pruning, and registry login"
59
55
  option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
60
56
  def redeploy
61
57
  runtime = print_runtime do
@@ -70,7 +66,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
70
66
  end
71
67
 
72
68
  with_lock do
73
- run_hook "pre-deploy"
69
+ run_hook "pre-deploy", secrets: true
74
70
 
75
71
  say "Detect stale containers...", :magenta
76
72
  invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
@@ -79,7 +75,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
79
75
  end
80
76
  end
81
77
 
82
- run_hook "post-deploy", runtime: runtime.round
78
+ run_hook "post-deploy", secrets: true, runtime: runtime.round
83
79
  end
84
80
 
85
81
  desc "rollback [VERSION]", "Rollback app to VERSION"
@@ -93,7 +89,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
93
89
  old_version = nil
94
90
 
95
91
  if container_available?(version)
96
- run_hook "pre-deploy"
92
+ run_hook "pre-deploy", secrets: true
97
93
 
98
94
  invoke "kamal:cli:app:boot", [], invoke_options.merge(version: version)
99
95
  rolled_back = true
@@ -103,12 +99,12 @@ class Kamal::Cli::Main < Kamal::Cli::Base
103
99
  end
104
100
  end
105
101
 
106
- run_hook "post-deploy", runtime: runtime.round if rolled_back
102
+ run_hook "post-deploy", secrets: true, runtime: runtime.round if rolled_back
107
103
  end
108
104
 
109
105
  desc "details", "Show details about all containers"
110
106
  def details
111
- invoke "kamal:cli:traefik:details"
107
+ invoke "kamal:cli:proxy:details"
112
108
  invoke "kamal:cli:app:details"
113
109
  invoke "kamal:cli:accessory:details", [ "all" ]
114
110
  end
@@ -127,7 +123,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
127
123
  end
128
124
  end
129
125
 
130
- desc "docs", "Show Kamal documentation for configuration setting"
126
+ desc "docs [SECTION]", "Show Kamal configuration documentation"
131
127
  def docs(section = nil)
132
128
  case section
133
129
  when NilClass
@@ -152,9 +148,10 @@ class Kamal::Cli::Main < Kamal::Cli::Base
152
148
  puts "Created configuration file in config/deploy.yml"
153
149
  end
154
150
 
155
- unless (deploy_file = Pathname.new(File.expand_path(".env"))).exist?
156
- FileUtils.cp_r Pathname.new(File.expand_path("templates/template.env", __dir__)), deploy_file
157
- puts "Created .env file"
151
+ unless (secrets_file = Pathname.new(File.expand_path(".kamal/secrets"))).exist?
152
+ FileUtils.mkdir_p secrets_file.dirname
153
+ FileUtils.cp_r Pathname.new(File.expand_path("templates/secrets", __dir__)), secrets_file
154
+ puts "Created .kamal/secrets file"
158
155
  end
159
156
 
160
157
  unless (hooks_dir = Pathname.new(File.expand_path(".kamal/hooks"))).exist?
@@ -179,44 +176,50 @@ class Kamal::Cli::Main < Kamal::Cli::Base
179
176
  end
180
177
  end
181
178
 
182
- desc "envify", "Create .env by evaluating .env.erb (or .env.staging.erb -> .env.staging when using -d staging)"
183
- option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip .env file push"
184
- def envify
185
- if destination = options[:destination]
186
- env_template_path = ".env.#{destination}.erb"
187
- env_path = ".env.#{destination}"
188
- else
189
- env_template_path = ".env.erb"
190
- env_path = ".env"
191
- end
192
-
193
- if Pathname.new(File.expand_path(env_template_path)).exist?
194
- # Ensure existing env doesn't pollute template evaluation
195
- content = with_original_env { ERB.new(File.read(env_template_path), trim_mode: "-").result }
196
- File.write(env_path, content, perm: 0600)
197
-
198
- unless options[:skip_push]
199
- reload_env
200
- invoke "kamal:cli:env:push", options
201
- end
202
- else
203
- puts "Skipping envify (no #{env_template_path} exist)"
204
- end
205
- end
206
-
207
- desc "remove", "Remove Traefik, app, accessories, and registry session from servers"
179
+ desc "remove", "Remove kamal-proxy, app, accessories, and registry session from servers"
208
180
  option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
209
181
  def remove
210
182
  confirming "This will remove all containers and images. Are you sure?" do
211
183
  with_lock do
212
- invoke "kamal:cli:traefik:remove", [], options.without(:confirmed)
213
184
  invoke "kamal:cli:app:remove", [], options.without(:confirmed)
185
+ invoke "kamal:cli:proxy:remove", [], options.without(:confirmed)
214
186
  invoke "kamal:cli:accessory:remove", [ "all" ], options
215
187
  invoke "kamal:cli:registry:logout", [], options.without(:confirmed).merge(skip_local: true)
216
188
  end
217
189
  end
218
190
  end
219
191
 
192
+ desc "upgrade", "Upgrade from Kamal 1.x to 2.0"
193
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
194
+ option :rolling, type: :boolean, default: false, desc: "Upgrade one host at a time"
195
+ def upgrade
196
+ confirming "This will replace Traefik with kamal-proxy and restart all accessories" do
197
+ with_lock do
198
+ if options[:rolling]
199
+ (KAMAL.hosts | KAMAL.accessory_hosts).each do |host|
200
+ KAMAL.with_specific_hosts(host) do
201
+ say "Upgrading #{host}...", :magenta
202
+ if KAMAL.hosts.include?(host)
203
+ invoke "kamal:cli:proxy:upgrade", [], options.merge(confirmed: true, rolling: false)
204
+ reset_invocation(Kamal::Cli::Proxy)
205
+ end
206
+ if KAMAL.accessory_hosts.include?(host)
207
+ invoke "kamal:cli:accessory:upgrade", [ "all" ], options.merge(confirmed: true, rolling: false)
208
+ reset_invocation(Kamal::Cli::Accessory)
209
+ end
210
+ say "Upgraded #{host}", :magenta
211
+ end
212
+ end
213
+ else
214
+ say "Upgrading all hosts...", :magenta
215
+ invoke "kamal:cli:proxy:upgrade", [], options.merge(confirmed: true)
216
+ invoke "kamal:cli:accessory:upgrade", [ "all" ], options.merge(confirmed: true)
217
+ say "Upgraded all hosts", :magenta
218
+ end
219
+ end
220
+ end
221
+ end
222
+
220
223
  desc "version", "Show Kamal version"
221
224
  def version
222
225
  puts Kamal::VERSION
@@ -231,24 +234,24 @@ class Kamal::Cli::Main < Kamal::Cli::Base
231
234
  desc "build", "Build application image"
232
235
  subcommand "build", Kamal::Cli::Build
233
236
 
234
- desc "env", "Manage environment files"
235
- subcommand "env", Kamal::Cli::Env
236
-
237
237
  desc "lock", "Manage the deploy lock"
238
238
  subcommand "lock", Kamal::Cli::Lock
239
239
 
240
+ desc "proxy", "Manage kamal-proxy"
241
+ subcommand "proxy", Kamal::Cli::Proxy
242
+
240
243
  desc "prune", "Prune old application images and containers"
241
244
  subcommand "prune", Kamal::Cli::Prune
242
245
 
243
246
  desc "registry", "Login and -out of the image registry"
244
247
  subcommand "registry", Kamal::Cli::Registry
245
248
 
249
+ desc "secrets", "Helpers for extracting secrets"
250
+ subcommand "secrets", Kamal::Cli::Secrets
251
+
246
252
  desc "server", "Bootstrap servers with curl and Docker"
247
253
  subcommand "server", Kamal::Cli::Server
248
254
 
249
- desc "traefik", "Manage Traefik load balancer"
250
- subcommand "traefik", Kamal::Cli::Traefik
251
-
252
255
  private
253
256
  def container_available?(version)
254
257
  begin
@@ -0,0 +1,224 @@
1
+ class Kamal::Cli::Proxy < Kamal::Cli::Base
2
+ desc "boot", "Boot proxy on servers"
3
+ def boot
4
+ with_lock do
5
+ on(KAMAL.hosts) do |host|
6
+ execute *KAMAL.docker.create_network
7
+ rescue SSHKit::Command::Failed => e
8
+ raise unless e.message.include?("already exists")
9
+ end
10
+
11
+ on(KAMAL.proxy_hosts) do |host|
12
+ execute *KAMAL.registry.login
13
+
14
+ version = capture_with_info(*KAMAL.proxy.version).strip.presence
15
+
16
+ if version && Kamal::Utils.older_version?(version, Kamal::Configuration::PROXY_MINIMUM_VERSION)
17
+ raise "kamal-proxy version #{version} is too old, please reboot to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}"
18
+ end
19
+ execute *KAMAL.proxy.start_or_run
20
+ end
21
+ end
22
+ end
23
+
24
+ desc "reboot", "Reboot proxy on servers (stop container, remove container, start new container)"
25
+ option :rolling, type: :boolean, default: false, desc: "Reboot proxy on hosts in sequence, rather than in parallel"
26
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
27
+ def reboot
28
+ confirming "This will cause a brief outage on each host. Are you sure?" do
29
+ with_lock do
30
+ host_groups = options[:rolling] ? KAMAL.proxy_hosts : [ KAMAL.proxy_hosts ]
31
+ host_groups.each do |hosts|
32
+ host_list = Array(hosts).join(",")
33
+ run_hook "pre-proxy-reboot", hosts: host_list
34
+ on(hosts) do |host|
35
+ execute *KAMAL.auditor.record("Rebooted proxy"), verbosity: :debug
36
+ execute *KAMAL.registry.login
37
+
38
+ "Stopping and removing Traefik on #{host}, if running..."
39
+ execute *KAMAL.proxy.cleanup_traefik
40
+
41
+ "Stopping and removing kamal-proxy on #{host}, if running..."
42
+ execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
43
+ execute *KAMAL.proxy.remove_container
44
+
45
+ execute *KAMAL.proxy.run
46
+
47
+ KAMAL.roles_on(host).select(&:running_proxy?).each do |role|
48
+ app = KAMAL.app(role: role, host: host)
49
+
50
+ version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
51
+ endpoint = capture_with_info(*app.container_id_for_version(version)).strip
52
+
53
+ if endpoint.present?
54
+ info "Deploying #{endpoint} for role `#{role}` on #{host}..."
55
+ execute *app.deploy(target: endpoint)
56
+ end
57
+ end
58
+ end
59
+ run_hook "post-proxy-reboot", hosts: host_list
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ desc "upgrade", "Upgrade to kamal-proxy on servers (stop container, remove container, start new container, reboot app)", hide: true
66
+ option :rolling, type: :boolean, default: false, desc: "Reboot proxy on hosts in sequence, rather than in parallel"
67
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
68
+ def upgrade
69
+ invoke_options = { "version" => KAMAL.config.latest_tag }.merge(options)
70
+
71
+ confirming "This will cause a brief outage on each host. Are you sure?" do
72
+ host_groups = options[:rolling] ? KAMAL.hosts : [ KAMAL.hosts ]
73
+ host_groups.each do |hosts|
74
+ host_list = Array(hosts).join(",")
75
+ say "Upgrading proxy on #{host_list}...", :magenta
76
+ run_hook "pre-proxy-reboot", hosts: host_list
77
+ on(hosts) do |host|
78
+ execute *KAMAL.auditor.record("Rebooted proxy"), verbosity: :debug
79
+ execute *KAMAL.registry.login
80
+
81
+ "Stopping and removing Traefik on #{host}, if running..."
82
+ execute *KAMAL.proxy.cleanup_traefik
83
+
84
+ "Stopping and removing kamal-proxy on #{host}, if running..."
85
+ execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
86
+ execute *KAMAL.proxy.remove_container
87
+ execute *KAMAL.proxy.remove_image
88
+ end
89
+
90
+ KAMAL.with_specific_hosts(hosts) do
91
+ invoke "kamal:cli:proxy:boot", [], invoke_options
92
+ reset_invocation(Kamal::Cli::Proxy)
93
+ invoke "kamal:cli:app:boot", [], invoke_options
94
+ reset_invocation(Kamal::Cli::App)
95
+ invoke "kamal:cli:prune:all", [], invoke_options
96
+ reset_invocation(Kamal::Cli::Prune)
97
+ end
98
+
99
+ run_hook "post-proxy-reboot", hosts: host_list
100
+ say "Upgraded proxy on #{host_list}", :magenta
101
+ end
102
+ end
103
+ end
104
+
105
+ desc "start", "Start existing proxy container on servers"
106
+ def start
107
+ with_lock do
108
+ on(KAMAL.proxy_hosts) do |host|
109
+ execute *KAMAL.auditor.record("Started proxy"), verbosity: :debug
110
+ execute *KAMAL.proxy.start
111
+ end
112
+ end
113
+ end
114
+
115
+ desc "stop", "Stop existing proxy container on servers"
116
+ def stop
117
+ with_lock do
118
+ on(KAMAL.proxy_hosts) do |host|
119
+ execute *KAMAL.auditor.record("Stopped proxy"), verbosity: :debug
120
+ execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
121
+ end
122
+ end
123
+ end
124
+
125
+ desc "restart", "Restart existing proxy container on servers"
126
+ def restart
127
+ with_lock do
128
+ stop
129
+ start
130
+ end
131
+ end
132
+
133
+ desc "details", "Show details about proxy container from servers"
134
+ def details
135
+ on(KAMAL.proxy_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.proxy.info), type: "Proxy" }
136
+ end
137
+
138
+ desc "logs", "Show log lines from proxy on servers"
139
+ option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
140
+ option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
141
+ option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
142
+ option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
143
+ def logs
144
+ grep = options[:grep]
145
+
146
+ if options[:follow]
147
+ run_locally do
148
+ info "Following logs on #{KAMAL.primary_host}..."
149
+ info KAMAL.proxy.follow_logs(host: KAMAL.primary_host, grep: grep)
150
+ exec KAMAL.proxy.follow_logs(host: KAMAL.primary_host, grep: grep)
151
+ end
152
+ else
153
+ since = options[:since]
154
+ lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
155
+
156
+ on(KAMAL.proxy_hosts) do |host|
157
+ puts_by_host host, capture(*KAMAL.proxy.logs(since: since, lines: lines, grep: grep)), type: "Proxy"
158
+ end
159
+ end
160
+ end
161
+
162
+ desc "remove", "Remove proxy container and image from servers"
163
+ option :force, type: :boolean, default: false, desc: "Force removing proxy when apps are still installed"
164
+ def remove
165
+ with_lock do
166
+ if removal_allowed?(options[:force])
167
+ stop
168
+ remove_container
169
+ remove_image
170
+ remove_host_directory
171
+ end
172
+ end
173
+ end
174
+
175
+ desc "remove_container", "Remove proxy container from servers", hide: true
176
+ def remove_container
177
+ with_lock do
178
+ on(KAMAL.proxy_hosts) do
179
+ execute *KAMAL.auditor.record("Removed proxy container"), verbosity: :debug
180
+ execute *KAMAL.proxy.remove_container
181
+ end
182
+ end
183
+ end
184
+
185
+ desc "remove_image", "Remove proxy image from servers", hide: true
186
+ def remove_image
187
+ with_lock do
188
+ on(KAMAL.proxy_hosts) do
189
+ execute *KAMAL.auditor.record("Removed proxy image"), verbosity: :debug
190
+ execute *KAMAL.proxy.remove_image
191
+ end
192
+ end
193
+ end
194
+
195
+ desc "remove_host_directory", "Remove proxy directory from servers", hide: true
196
+ def remove_host_directory
197
+ with_lock do
198
+ on(KAMAL.proxy_hosts) do
199
+ execute *KAMAL.auditor.record("Removed #{KAMAL.config.proxy_directory}"), verbosity: :debug
200
+ execute *KAMAL.proxy.remove_host_directory, raise_on_non_zero_exit: false
201
+ end
202
+ end
203
+ end
204
+
205
+ private
206
+ def removal_allowed?(force)
207
+ on(KAMAL.proxy_hosts) do |host|
208
+ app_count = capture_with_info(*KAMAL.server.app_directory_count).chomp.to_i
209
+ raise "The are other applications installed on #{host}" if app_count > 0
210
+ end
211
+
212
+ true
213
+ rescue SSHKit::Runner::ExecuteError => e
214
+ raise unless e.message.include?("The are other applications installed on")
215
+
216
+ if force
217
+ say "Forcing, so removing the proxy, even though other apps are installed", :magenta
218
+ else
219
+ say "Not removing the proxy, as other apps are installed, ignore this check with kamal proxy remove --force", :magenta
220
+ end
221
+
222
+ force
223
+ end
224
+ end
@@ -28,7 +28,6 @@ class Kamal::Cli::Prune < Kamal::Cli::Base
28
28
  on(KAMAL.hosts) do
29
29
  execute *KAMAL.auditor.record("Pruned containers"), verbosity: :debug
30
30
  execute *KAMAL.prune.app_containers(retain: retain)
31
- execute *KAMAL.prune.healthcheck_containers
32
31
  end
33
32
  end
34
33
  end
@@ -0,0 +1,36 @@
1
+ class Kamal::Cli::Secrets < Kamal::Cli::Base
2
+ desc "fetch [SECRETS...]", "Fetch secrets from a vault"
3
+ option :adapter, type: :string, aliases: "-a", required: true, desc: "Which vault adapter to use"
4
+ option :account, type: :string, required: true, desc: "The account identifier or username"
5
+ option :from, type: :string, required: false, desc: "A vault or folder to fetch the secrets from"
6
+ option :inline, type: :boolean, required: false, hidden: true
7
+ def fetch(*secrets)
8
+ results = adapter(options[:adapter]).fetch(secrets, **options.slice(:account, :from).symbolize_keys)
9
+
10
+ return_or_puts JSON.dump(results).shellescape, inline: options[:inline]
11
+ end
12
+
13
+ desc "extract", "Extract a single secret from the results of a fetch call"
14
+ option :inline, type: :boolean, required: false, hidden: true
15
+ def extract(name, secrets)
16
+ parsed_secrets = JSON.parse(secrets)
17
+ value = parsed_secrets[name] || parsed_secrets.find { |k, v| k.end_with?("/#{name}") }&.last
18
+
19
+ raise "Could not find secret #{name}" if value.nil?
20
+
21
+ return_or_puts value, inline: options[:inline]
22
+ end
23
+
24
+ private
25
+ def adapter(adapter)
26
+ Kamal::Secrets::Adapters.lookup(adapter)
27
+ end
28
+
29
+ def return_or_puts(value, inline: nil)
30
+ if inline
31
+ value
32
+ else
33
+ puts value
34
+ end
35
+ end
36
+ end
@@ -1,7 +1,8 @@
1
1
  class Kamal::Cli::Server < Kamal::Cli::Base
2
2
  desc "exec", "Run a custom command on the server (use --help to show options)"
3
3
  option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)"
4
- def exec(cmd)
4
+ def exec(*cmd)
5
+ cmd = Kamal::Utils.join_commands(cmd)
5
6
  hosts = KAMAL.hosts | KAMAL.accessory_hosts
6
7
 
7
8
  case
@@ -35,8 +36,6 @@ class Kamal::Cli::Server < Kamal::Cli::Base
35
36
  missing << host
36
37
  end
37
38
  end
38
-
39
- execute(*KAMAL.server.ensure_run_directory)
40
39
  end
41
40
 
42
41
  if missing.any?
@@ -18,6 +18,10 @@ registry:
18
18
  password:
19
19
  - KAMAL_REGISTRY_PASSWORD
20
20
 
21
+ # Configure builder setup.
22
+ builder:
23
+ arch: amd64
24
+
21
25
  # Inject ENV variables into containers (secrets come from .env).
22
26
  # Remember to run `kamal env push` after making changes!
23
27
  # env:
@@ -30,16 +34,6 @@ registry:
30
34
  # ssh:
31
35
  # user: app
32
36
 
33
- # Configure builder setup.
34
- # builder:
35
- # args:
36
- # RUBY_VERSION: 3.2.0
37
- # secrets:
38
- # - GITHUB_TOKEN
39
- # remote:
40
- # arch: amd64
41
- # host: ssh://app@192.168.0.1
42
-
43
37
  # Use accessory services (secrets come from .env).
44
38
  # accessories:
45
39
  # db:
@@ -63,17 +57,6 @@ registry:
63
57
  # directories:
64
58
  # - data:/data
65
59
 
66
- # Configure custom arguments for Traefik. Be sure to reboot traefik when you modify it.
67
- # traefik:
68
- # args:
69
- # accesslog: true
70
- # accesslog.format: json
71
-
72
- # Configure a custom healthcheck (default is /up on port 3000)
73
- # healthcheck:
74
- # path: /healthz
75
- # port: 4000
76
-
77
60
  # Bridge fingerprinted assets, like JS and CSS, between versions to avoid
78
61
  # hitting 404 on in-flight requests. Combines all files from new and old
79
62
  # version inside the asset_path.
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ echo "Rebooted kamal-proxy on $KAMAL_HOSTS"
@@ -0,0 +1,16 @@
1
+ # WARNING: Avoid adding secrets directly to this file
2
+ # If you must, then add `.kamal/secrets*` to your .gitignore file
3
+
4
+ # Option 1: Read secrets from the environment
5
+ KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
6
+
7
+ # Option 2: Read secrets via a command
8
+ # RAILS_MASTER_KEY=$(cat config/master.key)
9
+
10
+ # Option 3: Read secrets via kamal secrets helpers
11
+ # These will handle logging in and fetching the secrets in as few calls as possible
12
+ # There are adapters for 1Password, LastPass + Bitwarden
13
+ #
14
+ # SECRETS=$(kamal secrets fetch --adapter 1password --account my-account --from MyVault/MyItem KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY)
15
+ # KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD $SECRETS)
16
+ # RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY $SECRETS)
data/lib/kamal/cli.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  module Kamal::Cli
2
+ class BootError < StandardError; end
2
3
  class HookError < StandardError; end
3
4
  class LockError < StandardError; end
4
5
  end
@@ -18,12 +18,12 @@ class Kamal::Commander::Specifics
18
18
  roles.select { |role| role.hosts.include?(host.to_s) }
19
19
  end
20
20
 
21
- def traefik_hosts
22
- config.traefik_hosts & specified_hosts
21
+ def proxy_hosts
22
+ config.proxy_hosts & specified_hosts
23
23
  end
24
24
 
25
25
  def accessory_hosts
26
- specific_hosts || config.accessories.flat_map(&:hosts)
26
+ config.accessories.flat_map(&:hosts) & specified_hosts
27
27
  end
28
28
 
29
29
  private
@@ -1,9 +1,10 @@
1
1
  require "active_support/core_ext/enumerable"
2
2
  require "active_support/core_ext/module/delegation"
3
+ require "active_support/core_ext/object/blank"
3
4
 
4
5
  class Kamal::Commander
5
6
  attr_accessor :verbosity, :holding_lock, :connected
6
- delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :traefik_hosts, :accessory_hosts, to: :specifics
7
+ delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :proxy_hosts, :accessory_hosts, to: :specifics
7
8
 
8
9
  def initialize
9
10
  self.verbosity = :info
@@ -23,11 +24,19 @@ class Kamal::Commander
23
24
  @config, @config_kwargs = nil, kwargs
24
25
  end
25
26
 
27
+ def configured?
28
+ @config || @config_kwargs
29
+ end
30
+
26
31
  attr_reader :specific_roles, :specific_hosts
27
32
 
28
33
  def specific_primary!
29
34
  @specifics = nil
30
- self.specific_hosts = [ config.primary_host ]
35
+ if specific_roles.present?
36
+ self.specific_hosts = [ specific_roles.first.primary_host ]
37
+ else
38
+ self.specific_hosts = [ config.primary_host ]
39
+ end
31
40
  end
32
41
 
33
42
  def specific_roles=(role_names)
@@ -56,6 +65,13 @@ class Kamal::Commander
56
65
  end
57
66
  end
58
67
 
68
+ def with_specific_hosts(hosts)
69
+ original_hosts, self.specific_hosts = specific_hosts, hosts
70
+ yield
71
+ ensure
72
+ self.specific_hosts = original_hosts
73
+ end
74
+
59
75
  def accessory_names
60
76
  config.accessories&.collect(&:name) || []
61
77
  end
@@ -85,10 +101,6 @@ class Kamal::Commander
85
101
  @docker ||= Kamal::Commands::Docker.new(config)
86
102
  end
87
103
 
88
- def healthcheck
89
- @healthcheck ||= Kamal::Commands::Healthcheck.new(config)
90
- end
91
-
92
104
  def hook
93
105
  @hook ||= Kamal::Commands::Hook.new(config)
94
106
  end
@@ -97,6 +109,10 @@ class Kamal::Commander
97
109
  @lock ||= Kamal::Commands::Lock.new(config)
98
110
  end
99
111
 
112
+ def proxy
113
+ @proxy ||= Kamal::Commands::Proxy.new(config)
114
+ end
115
+
100
116
  def prune
101
117
  @prune ||= Kamal::Commands::Prune.new(config)
102
118
  end
@@ -109,8 +125,8 @@ class Kamal::Commander
109
125
  @server ||= Kamal::Commands::Server.new(config)
110
126
  end
111
127
 
112
- def traefik
113
- @traefik ||= Kamal::Commands::Traefik.new(config)
128
+ def alias(name)
129
+ config.aliases[name]
114
130
  end
115
131
 
116
132