kamal-insecure 2.7.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +13 -0
- data/bin/kamal +18 -0
- data/lib/kamal/cli/accessory.rb +313 -0
- data/lib/kamal/cli/alias/command.rb +10 -0
- data/lib/kamal/cli/app/assets.rb +24 -0
- data/lib/kamal/cli/app/boot.rb +126 -0
- data/lib/kamal/cli/app/error_pages.rb +33 -0
- data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
- data/lib/kamal/cli/app.rb +400 -0
- data/lib/kamal/cli/base.rb +223 -0
- data/lib/kamal/cli/build/clone.rb +61 -0
- data/lib/kamal/cli/build.rb +204 -0
- data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
- data/lib/kamal/cli/healthcheck/error.rb +2 -0
- data/lib/kamal/cli/healthcheck/poller.rb +42 -0
- data/lib/kamal/cli/lock.rb +45 -0
- data/lib/kamal/cli/main.rb +277 -0
- data/lib/kamal/cli/proxy.rb +290 -0
- data/lib/kamal/cli/prune.rb +34 -0
- data/lib/kamal/cli/registry.rb +19 -0
- data/lib/kamal/cli/secrets.rb +49 -0
- data/lib/kamal/cli/server.rb +50 -0
- data/lib/kamal/cli/templates/deploy.yml +101 -0
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
- data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +122 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/secrets +17 -0
- data/lib/kamal/cli.rb +9 -0
- data/lib/kamal/commander/specifics.rb +62 -0
- data/lib/kamal/commander.rb +167 -0
- data/lib/kamal/commands/accessory/proxy.rb +16 -0
- data/lib/kamal/commands/accessory.rb +113 -0
- data/lib/kamal/commands/app/assets.rb +51 -0
- data/lib/kamal/commands/app/containers.rb +31 -0
- data/lib/kamal/commands/app/error_pages.rb +9 -0
- data/lib/kamal/commands/app/execution.rb +32 -0
- data/lib/kamal/commands/app/images.rb +13 -0
- data/lib/kamal/commands/app/logging.rb +28 -0
- data/lib/kamal/commands/app/proxy.rb +32 -0
- data/lib/kamal/commands/app.rb +124 -0
- data/lib/kamal/commands/auditor.rb +39 -0
- data/lib/kamal/commands/base.rb +134 -0
- data/lib/kamal/commands/builder/base.rb +124 -0
- data/lib/kamal/commands/builder/clone.rb +31 -0
- data/lib/kamal/commands/builder/cloud.rb +22 -0
- data/lib/kamal/commands/builder/hybrid.rb +21 -0
- data/lib/kamal/commands/builder/local.rb +14 -0
- data/lib/kamal/commands/builder/pack.rb +46 -0
- data/lib/kamal/commands/builder/remote.rb +63 -0
- data/lib/kamal/commands/builder.rb +48 -0
- data/lib/kamal/commands/docker.rb +34 -0
- data/lib/kamal/commands/hook.rb +20 -0
- data/lib/kamal/commands/lock.rb +70 -0
- data/lib/kamal/commands/proxy.rb +127 -0
- data/lib/kamal/commands/prune.rb +38 -0
- data/lib/kamal/commands/registry.rb +16 -0
- data/lib/kamal/commands/server.rb +15 -0
- data/lib/kamal/commands.rb +2 -0
- data/lib/kamal/configuration/accessory.rb +241 -0
- data/lib/kamal/configuration/alias.rb +15 -0
- data/lib/kamal/configuration/boot.rb +25 -0
- data/lib/kamal/configuration/builder.rb +211 -0
- data/lib/kamal/configuration/docs/accessory.yml +128 -0
- data/lib/kamal/configuration/docs/alias.yml +26 -0
- data/lib/kamal/configuration/docs/boot.yml +19 -0
- data/lib/kamal/configuration/docs/builder.yml +132 -0
- data/lib/kamal/configuration/docs/configuration.yml +184 -0
- data/lib/kamal/configuration/docs/env.yml +116 -0
- data/lib/kamal/configuration/docs/logging.yml +21 -0
- data/lib/kamal/configuration/docs/proxy.yml +164 -0
- data/lib/kamal/configuration/docs/registry.yml +56 -0
- data/lib/kamal/configuration/docs/role.yml +53 -0
- data/lib/kamal/configuration/docs/servers.yml +27 -0
- data/lib/kamal/configuration/docs/ssh.yml +70 -0
- data/lib/kamal/configuration/docs/sshkit.yml +23 -0
- data/lib/kamal/configuration/env/tag.rb +13 -0
- data/lib/kamal/configuration/env.rb +38 -0
- data/lib/kamal/configuration/logging.rb +33 -0
- data/lib/kamal/configuration/proxy/boot.rb +129 -0
- data/lib/kamal/configuration/proxy.rb +124 -0
- data/lib/kamal/configuration/registry.rb +32 -0
- data/lib/kamal/configuration/role.rb +222 -0
- data/lib/kamal/configuration/servers.rb +25 -0
- data/lib/kamal/configuration/ssh.rb +57 -0
- data/lib/kamal/configuration/sshkit.rb +22 -0
- data/lib/kamal/configuration/validation.rb +27 -0
- data/lib/kamal/configuration/validator/accessory.rb +13 -0
- data/lib/kamal/configuration/validator/alias.rb +15 -0
- data/lib/kamal/configuration/validator/builder.rb +15 -0
- data/lib/kamal/configuration/validator/configuration.rb +6 -0
- data/lib/kamal/configuration/validator/env.rb +54 -0
- data/lib/kamal/configuration/validator/proxy.rb +25 -0
- data/lib/kamal/configuration/validator/registry.rb +25 -0
- data/lib/kamal/configuration/validator/role.rb +13 -0
- data/lib/kamal/configuration/validator/servers.rb +7 -0
- data/lib/kamal/configuration/validator.rb +191 -0
- data/lib/kamal/configuration/volume.rb +22 -0
- data/lib/kamal/configuration.rb +372 -0
- data/lib/kamal/docker.rb +30 -0
- data/lib/kamal/env_file.rb +44 -0
- data/lib/kamal/git.rb +37 -0
- data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +51 -0
- data/lib/kamal/secrets/adapters/base.rb +33 -0
- data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
- data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
- data/lib/kamal/secrets/adapters/doppler.rb +57 -0
- data/lib/kamal/secrets/adapters/enpass.rb +71 -0
- data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
- data/lib/kamal/secrets/adapters/last_pass.rb +40 -0
- data/lib/kamal/secrets/adapters/one_password.rb +104 -0
- data/lib/kamal/secrets/adapters/passbolt.rb +130 -0
- data/lib/kamal/secrets/adapters/test.rb +14 -0
- data/lib/kamal/secrets/adapters.rb +16 -0
- data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +33 -0
- data/lib/kamal/secrets.rb +42 -0
- data/lib/kamal/sshkit_with_ext.rb +142 -0
- data/lib/kamal/tags.rb +40 -0
- data/lib/kamal/utils/sensitive.rb +20 -0
- data/lib/kamal/utils.rb +110 -0
- data/lib/kamal/version.rb +3 -0
- data/lib/kamal.rb +14 -0
- metadata +365 -0
@@ -0,0 +1,290 @@
|
|
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::Boot::MINIMUM_VERSION)
|
17
|
+
raise "kamal-proxy version #{version} is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}"
|
18
|
+
end
|
19
|
+
execute *KAMAL.proxy.ensure_apps_config_directory
|
20
|
+
execute *KAMAL.proxy.start_or_run
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "boot_config <set|get|reset>", "Manage kamal-proxy boot configuration"
|
26
|
+
option :publish, type: :boolean, default: true, desc: "Publish the proxy ports on the host"
|
27
|
+
option :publish_host_ip, type: :string, repeatable: true, default: nil, desc: "Host IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces"
|
28
|
+
option :http_port, type: :numeric, default: Kamal::Configuration::Proxy::Boot::DEFAULT_HTTP_PORT, desc: "HTTP port to publish on the host"
|
29
|
+
option :https_port, type: :numeric, default: Kamal::Configuration::Proxy::Boot::DEFAULT_HTTPS_PORT, desc: "HTTPS port to publish on the host"
|
30
|
+
option :log_max_size, type: :string, default: Kamal::Configuration::Proxy::Boot::DEFAULT_LOG_MAX_SIZE, desc: "Max size of proxy logs"
|
31
|
+
option :registry, type: :string, default: nil, desc: "Registry to use for the proxy image"
|
32
|
+
option :repository, type: :string, default: nil, desc: "Repository for the proxy image"
|
33
|
+
option :image_version, type: :string, default: nil, desc: "Version of the proxy to run"
|
34
|
+
option :metrics_port, type: :numeric, default: nil, desc: "Port to report prometheus metrics on"
|
35
|
+
option :debug, type: :boolean, default: false, desc: "Whether to run the proxy in debug mode"
|
36
|
+
option :docker_options, type: :array, default: [], desc: "Docker options to pass to the proxy container", banner: "option=value option2=value2"
|
37
|
+
def boot_config(subcommand)
|
38
|
+
proxy_boot_config = KAMAL.config.proxy_boot
|
39
|
+
|
40
|
+
case subcommand
|
41
|
+
when "set"
|
42
|
+
boot_options = [
|
43
|
+
*(proxy_boot_config.publish_args(options[:http_port], options[:https_port], options[:publish_host_ip]) if options[:publish]),
|
44
|
+
*(proxy_boot_config.logging_args(options[:log_max_size])),
|
45
|
+
*("--expose=#{options[:metrics_port]}" if options[:metrics_port]),
|
46
|
+
*options[:docker_options].map { |option| "--#{option}" }
|
47
|
+
]
|
48
|
+
|
49
|
+
image = [
|
50
|
+
options[:registry].presence,
|
51
|
+
options[:repository].presence || proxy_boot_config.repository_name,
|
52
|
+
proxy_boot_config.image_name
|
53
|
+
].compact.join("/")
|
54
|
+
|
55
|
+
image_version = options[:image_version]
|
56
|
+
|
57
|
+
run_command_options = { debug: options[:debug] || nil, "metrics-port": options[:metrics_port] }.compact
|
58
|
+
run_command = "kamal-proxy run #{Kamal::Utils.optionize(run_command_options).join(" ")}" if run_command_options.any?
|
59
|
+
|
60
|
+
on(KAMAL.proxy_hosts) do |host|
|
61
|
+
execute(*KAMAL.proxy.ensure_proxy_directory)
|
62
|
+
if boot_options != proxy_boot_config.default_boot_options
|
63
|
+
upload! StringIO.new(boot_options.join(" ")), proxy_boot_config.options_file
|
64
|
+
else
|
65
|
+
execute *KAMAL.proxy.reset_boot_options, raise_on_non_zero_exit: false
|
66
|
+
end
|
67
|
+
|
68
|
+
if image != proxy_boot_config.image_default
|
69
|
+
upload! StringIO.new(image), proxy_boot_config.image_file
|
70
|
+
else
|
71
|
+
execute *KAMAL.proxy.reset_image, raise_on_non_zero_exit: false
|
72
|
+
end
|
73
|
+
|
74
|
+
if image_version
|
75
|
+
upload! StringIO.new(image_version), proxy_boot_config.image_version_file
|
76
|
+
else
|
77
|
+
execute *KAMAL.proxy.reset_image_version, raise_on_non_zero_exit: false
|
78
|
+
end
|
79
|
+
|
80
|
+
if run_command
|
81
|
+
upload! StringIO.new(run_command), proxy_boot_config.run_command_file
|
82
|
+
else
|
83
|
+
execute *KAMAL.proxy.reset_run_command, raise_on_non_zero_exit: false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
when "get"
|
87
|
+
|
88
|
+
on(KAMAL.proxy_hosts) do |host|
|
89
|
+
puts "Host #{host}: #{capture_with_info(*KAMAL.proxy.boot_config)}"
|
90
|
+
end
|
91
|
+
when "reset"
|
92
|
+
on(KAMAL.proxy_hosts) do |host|
|
93
|
+
execute *KAMAL.proxy.reset_boot_options, raise_on_non_zero_exit: false
|
94
|
+
execute *KAMAL.proxy.reset_image, raise_on_non_zero_exit: false
|
95
|
+
execute *KAMAL.proxy.reset_image_version, raise_on_non_zero_exit: false
|
96
|
+
execute *KAMAL.proxy.reset_run_command, raise_on_non_zero_exit: false
|
97
|
+
end
|
98
|
+
else
|
99
|
+
raise ArgumentError, "Unknown boot_config subcommand #{subcommand}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "reboot", "Reboot proxy on servers (stop container, remove container, start new container)"
|
104
|
+
option :rolling, type: :boolean, default: false, desc: "Reboot proxy on hosts in sequence, rather than in parallel"
|
105
|
+
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
106
|
+
def reboot
|
107
|
+
confirming "This will cause a brief outage on each host. Are you sure?" do
|
108
|
+
with_lock do
|
109
|
+
host_groups = options[:rolling] ? KAMAL.proxy_hosts : [ KAMAL.proxy_hosts ]
|
110
|
+
host_groups.each do |hosts|
|
111
|
+
host_list = Array(hosts).join(",")
|
112
|
+
run_hook "pre-proxy-reboot", hosts: host_list
|
113
|
+
on(hosts) do |host|
|
114
|
+
execute *KAMAL.auditor.record("Rebooted proxy"), verbosity: :debug
|
115
|
+
execute *KAMAL.registry.login
|
116
|
+
|
117
|
+
"Stopping and removing kamal-proxy on #{host}, if running..."
|
118
|
+
execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
|
119
|
+
execute *KAMAL.proxy.remove_container
|
120
|
+
execute *KAMAL.proxy.ensure_apps_config_directory
|
121
|
+
|
122
|
+
execute *KAMAL.proxy.run
|
123
|
+
end
|
124
|
+
run_hook "post-proxy-reboot", hosts: host_list
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
desc "upgrade", "Upgrade to kamal-proxy on servers (stop container, remove container, start new container, reboot app)", hide: true
|
131
|
+
option :rolling, type: :boolean, default: false, desc: "Reboot proxy on hosts in sequence, rather than in parallel"
|
132
|
+
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
133
|
+
def upgrade
|
134
|
+
invoke_options = { "version" => KAMAL.config.latest_tag }.merge(options)
|
135
|
+
|
136
|
+
confirming "This will cause a brief outage on each host. Are you sure?" do
|
137
|
+
host_groups = options[:rolling] ? KAMAL.hosts : [ KAMAL.hosts ]
|
138
|
+
host_groups.each do |hosts|
|
139
|
+
host_list = Array(hosts).join(",")
|
140
|
+
say "Upgrading proxy on #{host_list}...", :magenta
|
141
|
+
run_hook "pre-proxy-reboot", hosts: host_list
|
142
|
+
on(hosts) do |host|
|
143
|
+
execute *KAMAL.auditor.record("Rebooted proxy"), verbosity: :debug
|
144
|
+
execute *KAMAL.registry.login
|
145
|
+
|
146
|
+
"Stopping and removing Traefik on #{host}, if running..."
|
147
|
+
execute *KAMAL.proxy.cleanup_traefik
|
148
|
+
|
149
|
+
"Stopping and removing kamal-proxy on #{host}, if running..."
|
150
|
+
execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
|
151
|
+
execute *KAMAL.proxy.remove_container
|
152
|
+
execute *KAMAL.proxy.remove_image
|
153
|
+
end
|
154
|
+
|
155
|
+
KAMAL.with_specific_hosts(hosts) do
|
156
|
+
invoke "kamal:cli:proxy:boot", [], invoke_options
|
157
|
+
reset_invocation(Kamal::Cli::Proxy)
|
158
|
+
invoke "kamal:cli:app:boot", [], invoke_options
|
159
|
+
reset_invocation(Kamal::Cli::App)
|
160
|
+
invoke "kamal:cli:prune:all", [], invoke_options
|
161
|
+
reset_invocation(Kamal::Cli::Prune)
|
162
|
+
end
|
163
|
+
|
164
|
+
run_hook "post-proxy-reboot", hosts: host_list
|
165
|
+
say "Upgraded proxy on #{host_list}", :magenta
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
desc "start", "Start existing proxy container on servers"
|
171
|
+
def start
|
172
|
+
with_lock do
|
173
|
+
on(KAMAL.proxy_hosts) do |host|
|
174
|
+
execute *KAMAL.auditor.record("Started proxy"), verbosity: :debug
|
175
|
+
execute *KAMAL.proxy.start
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
desc "stop", "Stop existing proxy container on servers"
|
181
|
+
def stop
|
182
|
+
with_lock do
|
183
|
+
on(KAMAL.proxy_hosts) do |host|
|
184
|
+
execute *KAMAL.auditor.record("Stopped proxy"), verbosity: :debug
|
185
|
+
execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
desc "restart", "Restart existing proxy container on servers"
|
191
|
+
def restart
|
192
|
+
with_lock do
|
193
|
+
stop
|
194
|
+
start
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
desc "details", "Show details about proxy container from servers"
|
199
|
+
def details
|
200
|
+
on(KAMAL.proxy_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.proxy.info), type: "Proxy" }
|
201
|
+
end
|
202
|
+
|
203
|
+
desc "logs", "Show log lines from proxy on servers"
|
204
|
+
option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
205
|
+
option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
|
206
|
+
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
207
|
+
option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
|
208
|
+
option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
|
209
|
+
def logs
|
210
|
+
grep = options[:grep]
|
211
|
+
timestamps = !options[:skip_timestamps]
|
212
|
+
|
213
|
+
if options[:follow]
|
214
|
+
run_locally do
|
215
|
+
info "Following logs on #{KAMAL.primary_host}..."
|
216
|
+
info KAMAL.proxy.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, grep: grep)
|
217
|
+
exec KAMAL.proxy.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, grep: grep)
|
218
|
+
end
|
219
|
+
else
|
220
|
+
since = options[:since]
|
221
|
+
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
222
|
+
|
223
|
+
on(KAMAL.proxy_hosts) do |host|
|
224
|
+
puts_by_host host, capture(*KAMAL.proxy.logs(timestamps: timestamps, since: since, lines: lines, grep: grep)), type: "Proxy"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
desc "remove", "Remove proxy container and image from servers"
|
230
|
+
option :force, type: :boolean, default: false, desc: "Force removing proxy when apps are still installed"
|
231
|
+
def remove
|
232
|
+
with_lock do
|
233
|
+
if removal_allowed?(options[:force])
|
234
|
+
stop
|
235
|
+
remove_container
|
236
|
+
remove_image
|
237
|
+
remove_proxy_directory
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
desc "remove_container", "Remove proxy container from servers", hide: true
|
243
|
+
def remove_container
|
244
|
+
with_lock do
|
245
|
+
on(KAMAL.proxy_hosts) do
|
246
|
+
execute *KAMAL.auditor.record("Removed proxy container"), verbosity: :debug
|
247
|
+
execute *KAMAL.proxy.remove_container
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
desc "remove_image", "Remove proxy image from servers", hide: true
|
253
|
+
def remove_image
|
254
|
+
with_lock do
|
255
|
+
on(KAMAL.proxy_hosts) do
|
256
|
+
execute *KAMAL.auditor.record("Removed proxy image"), verbosity: :debug
|
257
|
+
execute *KAMAL.proxy.remove_image
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
desc "remove_proxy_directory", "Remove the proxy directory from servers", hide: true
|
263
|
+
def remove_proxy_directory
|
264
|
+
with_lock do
|
265
|
+
on(KAMAL.proxy_hosts) do
|
266
|
+
execute *KAMAL.proxy.remove_proxy_directory, raise_on_non_zero_exit: false
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
private
|
272
|
+
def removal_allowed?(force)
|
273
|
+
on(KAMAL.proxy_hosts) do |host|
|
274
|
+
app_count = capture_with_info(*KAMAL.server.app_directory_count).chomp.to_i
|
275
|
+
raise "The are other applications installed on #{host}" if app_count > 0
|
276
|
+
end
|
277
|
+
|
278
|
+
true
|
279
|
+
rescue SSHKit::Runner::ExecuteError => e
|
280
|
+
raise unless e.message.include?("The are other applications installed on")
|
281
|
+
|
282
|
+
if force
|
283
|
+
say "Forcing, so removing the proxy, even though other apps are installed", :magenta
|
284
|
+
else
|
285
|
+
say "Not removing the proxy, as other apps are installed, ignore this check with kamal proxy remove --force", :magenta
|
286
|
+
end
|
287
|
+
|
288
|
+
force
|
289
|
+
end
|
290
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Kamal::Cli::Prune < Kamal::Cli::Base
|
2
|
+
desc "all", "Prune unused images and stopped containers"
|
3
|
+
def all
|
4
|
+
with_lock do
|
5
|
+
containers
|
6
|
+
images
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "images", "Prune unused images"
|
11
|
+
def images
|
12
|
+
with_lock do
|
13
|
+
on(KAMAL.hosts) do
|
14
|
+
execute *KAMAL.auditor.record("Pruned images"), verbosity: :debug
|
15
|
+
execute *KAMAL.prune.dangling_images
|
16
|
+
execute *KAMAL.prune.tagged_images
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
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"
|
23
|
+
def containers
|
24
|
+
retain = options.fetch(:retain, KAMAL.config.retain_containers)
|
25
|
+
raise "retain must be at least 1" if retain < 1
|
26
|
+
|
27
|
+
with_lock do
|
28
|
+
on(KAMAL.hosts) do
|
29
|
+
execute *KAMAL.auditor.record("Pruned containers"), verbosity: :debug
|
30
|
+
execute *KAMAL.prune.app_containers(retain: retain)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Kamal::Cli::Registry < Kamal::Cli::Base
|
2
|
+
desc "login", "Log in to registry locally and remotely"
|
3
|
+
option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
|
4
|
+
option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
|
5
|
+
def login
|
6
|
+
ensure_docker_installed unless options[:skip_local]
|
7
|
+
|
8
|
+
run_locally { execute *KAMAL.registry.login } unless options[:skip_local]
|
9
|
+
on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote]
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "logout", "Log out of registry locally and remotely"
|
13
|
+
option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
|
14
|
+
option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
|
15
|
+
def logout
|
16
|
+
run_locally { execute *KAMAL.registry.logout } unless options[:skip_local]
|
17
|
+
on(KAMAL.hosts) { execute *KAMAL.registry.logout } unless options[:skip_remote]
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,49 @@
|
|
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: false, 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
|
+
adapter = initialize_adapter(options[:adapter])
|
9
|
+
|
10
|
+
if adapter.requires_account? && options[:account].blank?
|
11
|
+
return puts "No value provided for required options '--account'"
|
12
|
+
end
|
13
|
+
|
14
|
+
results = adapter.fetch(secrets, **options.slice(:account, :from).symbolize_keys)
|
15
|
+
|
16
|
+
return_or_puts JSON.dump(results).shellescape, inline: options[:inline]
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "extract", "Extract a single secret from the results of a fetch call"
|
20
|
+
option :inline, type: :boolean, required: false, hidden: true
|
21
|
+
def extract(name, secrets)
|
22
|
+
parsed_secrets = JSON.parse(secrets)
|
23
|
+
value = parsed_secrets[name] || parsed_secrets.find { |k, v| k.end_with?("/#{name}") }&.last
|
24
|
+
|
25
|
+
raise "Could not find secret #{name}" if value.nil?
|
26
|
+
|
27
|
+
return_or_puts value, inline: options[:inline]
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "print", "Print the secrets (for debugging)"
|
31
|
+
def print
|
32
|
+
KAMAL.config.secrets.to_h.each do |key, value|
|
33
|
+
puts "#{key}=#{value}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def initialize_adapter(adapter)
|
39
|
+
Kamal::Secrets::Adapters.lookup(adapter)
|
40
|
+
end
|
41
|
+
|
42
|
+
def return_or_puts(value, inline: nil)
|
43
|
+
if inline
|
44
|
+
value
|
45
|
+
else
|
46
|
+
puts value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class Kamal::Cli::Server < Kamal::Cli::Base
|
2
|
+
desc "exec", "Run a custom command on the server (use --help to show options)"
|
3
|
+
option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)"
|
4
|
+
def exec(*cmd)
|
5
|
+
pre_connect_if_required
|
6
|
+
|
7
|
+
cmd = Kamal::Utils.join_commands(cmd)
|
8
|
+
hosts = KAMAL.hosts
|
9
|
+
|
10
|
+
case
|
11
|
+
when options[:interactive]
|
12
|
+
host = KAMAL.primary_host
|
13
|
+
|
14
|
+
say "Running '#{cmd}' on #{host} interactively...", :magenta
|
15
|
+
|
16
|
+
run_locally { exec KAMAL.server.run_over_ssh(cmd, host: host) }
|
17
|
+
else
|
18
|
+
say "Running '#{cmd}' on #{hosts.join(', ')}...", :magenta
|
19
|
+
|
20
|
+
on(hosts) do |host|
|
21
|
+
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{host}"), verbosity: :debug
|
22
|
+
puts_by_host host, capture_with_info(cmd)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "bootstrap", "Set up Docker to run Kamal apps"
|
28
|
+
def bootstrap
|
29
|
+
with_lock do
|
30
|
+
missing = []
|
31
|
+
|
32
|
+
on(KAMAL.hosts) do |host|
|
33
|
+
unless execute(*KAMAL.docker.installed?, raise_on_non_zero_exit: false)
|
34
|
+
if execute(*KAMAL.docker.superuser?, raise_on_non_zero_exit: false)
|
35
|
+
info "Missing Docker on #{host}. Installing…"
|
36
|
+
execute *KAMAL.docker.install
|
37
|
+
else
|
38
|
+
missing << host
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
if missing.any?
|
44
|
+
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/"
|
45
|
+
end
|
46
|
+
|
47
|
+
run_hook "docker-setup"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# Name of your application. Used to uniquely configure containers.
|
2
|
+
service: my-app
|
3
|
+
|
4
|
+
# Name of the container image.
|
5
|
+
image: my-user/my-app
|
6
|
+
|
7
|
+
# Deploy to these servers.
|
8
|
+
servers:
|
9
|
+
web:
|
10
|
+
- 192.168.0.1
|
11
|
+
# job:
|
12
|
+
# hosts:
|
13
|
+
# - 192.168.0.1
|
14
|
+
# cmd: bin/jobs
|
15
|
+
|
16
|
+
# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.
|
17
|
+
# Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer.
|
18
|
+
#
|
19
|
+
# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
|
20
|
+
proxy:
|
21
|
+
ssl: true
|
22
|
+
host: app.example.com
|
23
|
+
# Proxy connects to your container on port 80 by default.
|
24
|
+
# app_port: 3000
|
25
|
+
|
26
|
+
# Credentials for your image host.
|
27
|
+
registry:
|
28
|
+
# Specify the registry server, if you're not using Docker Hub
|
29
|
+
# server: registry.digitalocean.com / ghcr.io / ...
|
30
|
+
username: my-user
|
31
|
+
|
32
|
+
# Always use an access token rather than real password (pulled from .kamal/secrets).
|
33
|
+
password:
|
34
|
+
- KAMAL_REGISTRY_PASSWORD
|
35
|
+
|
36
|
+
# Configure builder setup.
|
37
|
+
builder:
|
38
|
+
arch: amd64
|
39
|
+
# Pass in additional build args needed for your Dockerfile.
|
40
|
+
# args:
|
41
|
+
# RUBY_VERSION: <%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" %>
|
42
|
+
|
43
|
+
# Inject ENV variables into containers (secrets come from .kamal/secrets).
|
44
|
+
#
|
45
|
+
# env:
|
46
|
+
# clear:
|
47
|
+
# DB_HOST: 192.168.0.2
|
48
|
+
# secret:
|
49
|
+
# - RAILS_MASTER_KEY
|
50
|
+
|
51
|
+
# Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
|
52
|
+
# "bin/kamal app logs -r job" will tail logs from the first server in the job section.
|
53
|
+
#
|
54
|
+
# aliases:
|
55
|
+
# shell: app exec --interactive --reuse "bash"
|
56
|
+
|
57
|
+
# Use a different ssh user than root
|
58
|
+
#
|
59
|
+
# ssh:
|
60
|
+
# user: app
|
61
|
+
|
62
|
+
# Use a persistent storage volume.
|
63
|
+
#
|
64
|
+
# volumes:
|
65
|
+
# - "app_storage:/app/storage"
|
66
|
+
|
67
|
+
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
|
68
|
+
# hitting 404 on in-flight requests. Combines all files from new and old
|
69
|
+
# version inside the asset_path.
|
70
|
+
#
|
71
|
+
# asset_path: /app/public/assets
|
72
|
+
|
73
|
+
# Configure rolling deploys by setting a wait time between batches of restarts.
|
74
|
+
#
|
75
|
+
# boot:
|
76
|
+
# limit: 10 # Can also specify as a percentage of total hosts, such as "25%"
|
77
|
+
# wait: 2
|
78
|
+
|
79
|
+
# Use accessory services (secrets come from .kamal/secrets).
|
80
|
+
#
|
81
|
+
# accessories:
|
82
|
+
# db:
|
83
|
+
# image: mysql:8.0
|
84
|
+
# host: 192.168.0.2
|
85
|
+
# port: 3306
|
86
|
+
# env:
|
87
|
+
# clear:
|
88
|
+
# MYSQL_ROOT_HOST: '%'
|
89
|
+
# secret:
|
90
|
+
# - MYSQL_ROOT_PASSWORD
|
91
|
+
# files:
|
92
|
+
# - config/mysql/production.cnf:/etc/mysql/my.cnf
|
93
|
+
# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql
|
94
|
+
# directories:
|
95
|
+
# - data:/var/lib/mysql
|
96
|
+
# redis:
|
97
|
+
# image: valkey/valkey:8
|
98
|
+
# host: 192.168.0.2
|
99
|
+
# port: 6379
|
100
|
+
# directories:
|
101
|
+
# - data:/data
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# A sample post-deploy hook
|
4
|
+
#
|
5
|
+
# These environment variables are available:
|
6
|
+
# KAMAL_RECORDED_AT
|
7
|
+
# KAMAL_PERFORMER
|
8
|
+
# KAMAL_VERSION
|
9
|
+
# KAMAL_HOSTS
|
10
|
+
# KAMAL_ROLES (if set)
|
11
|
+
# KAMAL_DESTINATION (if set)
|
12
|
+
# KAMAL_RUNTIME
|
13
|
+
|
14
|
+
echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds"
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# A sample pre-build hook
|
4
|
+
#
|
5
|
+
# Checks:
|
6
|
+
# 1. We have a clean checkout
|
7
|
+
# 2. A remote is configured
|
8
|
+
# 3. The branch has been pushed to the remote
|
9
|
+
# 4. The version we are deploying matches the remote
|
10
|
+
#
|
11
|
+
# These environment variables are available:
|
12
|
+
# KAMAL_RECORDED_AT
|
13
|
+
# KAMAL_PERFORMER
|
14
|
+
# KAMAL_VERSION
|
15
|
+
# KAMAL_HOSTS
|
16
|
+
# KAMAL_ROLES (if set)
|
17
|
+
# KAMAL_DESTINATION (if set)
|
18
|
+
|
19
|
+
if [ -n "$(git status --porcelain)" ]; then
|
20
|
+
echo "Git checkout is not clean, aborting..." >&2
|
21
|
+
git status --porcelain >&2
|
22
|
+
exit 1
|
23
|
+
fi
|
24
|
+
|
25
|
+
first_remote=$(git remote)
|
26
|
+
|
27
|
+
if [ -z "$first_remote" ]; then
|
28
|
+
echo "No git remote set, aborting..." >&2
|
29
|
+
exit 1
|
30
|
+
fi
|
31
|
+
|
32
|
+
current_branch=$(git branch --show-current)
|
33
|
+
|
34
|
+
if [ -z "$current_branch" ]; then
|
35
|
+
echo "Not on a git branch, aborting..." >&2
|
36
|
+
exit 1
|
37
|
+
fi
|
38
|
+
|
39
|
+
remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1)
|
40
|
+
|
41
|
+
if [ -z "$remote_head" ]; then
|
42
|
+
echo "Branch not pushed to remote, aborting..." >&2
|
43
|
+
exit 1
|
44
|
+
fi
|
45
|
+
|
46
|
+
if [ "$KAMAL_VERSION" != "$remote_head" ]; then
|
47
|
+
echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2
|
48
|
+
exit 1
|
49
|
+
fi
|
50
|
+
|
51
|
+
exit 0
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# A sample pre-connect check
|
4
|
+
#
|
5
|
+
# Warms DNS before connecting to hosts in parallel
|
6
|
+
#
|
7
|
+
# These environment variables are available:
|
8
|
+
# KAMAL_RECORDED_AT
|
9
|
+
# KAMAL_PERFORMER
|
10
|
+
# KAMAL_VERSION
|
11
|
+
# KAMAL_HOSTS
|
12
|
+
# KAMAL_ROLES (if set)
|
13
|
+
# KAMAL_DESTINATION (if set)
|
14
|
+
# KAMAL_RUNTIME
|
15
|
+
|
16
|
+
hosts = ENV["KAMAL_HOSTS"].split(",")
|
17
|
+
results = nil
|
18
|
+
max = 3
|
19
|
+
|
20
|
+
elapsed = Benchmark.realtime do
|
21
|
+
results = hosts.map do |host|
|
22
|
+
Thread.new do
|
23
|
+
tries = 1
|
24
|
+
|
25
|
+
begin
|
26
|
+
Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME)
|
27
|
+
rescue SocketError
|
28
|
+
if tries < max
|
29
|
+
puts "Retrying DNS warmup: #{host}"
|
30
|
+
tries += 1
|
31
|
+
sleep rand
|
32
|
+
retry
|
33
|
+
else
|
34
|
+
puts "DNS warmup failed: #{host}"
|
35
|
+
host
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
tries
|
40
|
+
end
|
41
|
+
end.map(&:value)
|
42
|
+
end
|
43
|
+
|
44
|
+
retries = results.sum - hosts.size
|
45
|
+
nopes = results.count { |r| r == max }
|
46
|
+
|
47
|
+
puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ]
|