Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions lib/kamal/cli/proxy/reboot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def pull_image
def replace_container
execute *proxy.ensure_proxy_directory
execute *proxy.ensure_apps_config_directory
sync_proxy_secrets

if proxy.port_holder?
replace_generation
Expand All @@ -46,18 +47,26 @@ def replace_container
end
end

# Phase 1 replacement: stop the old container, start the new one. Brief gap.
def stop_and_replace
info "Stopping and removing kamal-proxy on #{host}, if running..."
execute *proxy.stop(timeout: KAMAL.config.drain_timeout + 10), raise_on_non_zero_exit: false
execute *proxy.remove_container

# Every replacement path below launches a container that reads
# --env-file at docker run, so the secrets must be on disk before ANY
# generation starts. This used to live inside stop_and_replace only, and
# the port-holder paths launched their generation without it — a
# port-holder reboot on a host without the file died with
# "open .kamal/proxy/secrets.env: no such file or directory".
def sync_proxy_secrets
if (run_config = proxy.proxy_run_config)&.secrets?
upload! run_config.secrets_io, run_config.secrets_path, mode: "0600"
else
# A host keeps no secrets it no longer needs.
execute *proxy.remove_proxy_secrets_file, raise_on_non_zero_exit: false
end
end

# Phase 1 replacement: stop the old container, start the new one. Brief gap.
def stop_and_replace
info "Stopping and removing kamal-proxy on #{host}, if running..."
execute *proxy.stop(timeout: KAMAL.config.drain_timeout + 10), raise_on_non_zero_exit: false
execute *proxy.remove_container

execute *proxy.run(digest: drift.expected_digest)
end
Expand Down
17 changes: 17 additions & 0 deletions test/cli/proxy_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ class CliProxyTest < CliTestCase
end
end

# Regression: the port-holder replacement paths start a generation that
# reads --env-file at docker run, but only the legacy stop_and_replace path
# uploaded the secrets file — a port-holder reboot on a fresh host died with
# "open .kamal/proxy/secrets.env: no such file or directory".
test "port-holder reboot uploads the acme credentials before starting the new generation" do
with_test_secrets("secrets" => "CF_API_TOKEN=zone-rewriting-token") do
uploads = capture_uploads

run_command("reboot", "-y", fixture: :with_proxy_acme_port_holder).tap do |output|
assert_match "--env-file .kamal/proxy/secrets.env", output
assert_no_match(/zone-rewriting-token/, output)
end

assert_equal [ [ "CF_API_TOKEN=zone-rewriting-token\n", ".kamal/proxy/secrets.env", "0600" ] ], uploads
end
end

test "boot delivers the cache store through the secrets env file, never printing the URL" do
uploads = capture_uploads

Expand Down
22 changes: 22 additions & 0 deletions test/fixtures/deploy_with_proxy_acme_port_holder.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
service: app
image: dhh/app
servers:
web:
- "1.1.1.1"
registry:
username: user
password: pw
builder:
arch: amd64
proxy:
host: example.com
ssl: true
run:
port_holder: true
acme:
email: admin@example.com
dns_provider: cloudflare
prefer_wildcard: true
http_fallback: false
credentials:
- CF_API_TOKEN