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
6 changes: 3 additions & 3 deletions bin/sync-proxy-flags
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# forgetting is loud rather than silent.
#
# bin/sync-proxy-flags # pull the image at MINIMUM_VERSION
# bin/sync-proxy-flags --binary ./kamal-proxy # use a locally built binary
# bin/sync-proxy-flags --binary ./dash-proxy # use a locally built binary
#
# The flag list comes from Cobra's own `--help` output, not from a regex over the
# Go source. That is deliberate: the source has flags registered via Int64Var,
Expand Down Expand Up @@ -43,7 +43,7 @@ def flags_for(invocation, subcommand)
help.scan(/^\s+(?:-\w, )?--([a-z0-9-]+)/).flatten.uniq.sort - BUILTIN_FLAGS
end

# The DNS providers kamal-proxy will accept for --acme-dns-provider, read off the
# The DNS providers dash-proxy will accept for --acme-dns-provider, read off the
# parenthesised list in the flag's own usage text. The gem validates
# proxy/run/acme/dns_provider against this, and rejecting a provider the proxy
# supports blocks a deploy - so the list has to move with the image, not with
Expand All @@ -69,7 +69,7 @@ invocation =
image = "ghcr.io/zoolutions/dash-proxy:#{version}"
warn "Pulling #{image}..."
run!("docker", "pull", "--quiet", image)
[ "docker", "run", "--rm", image, "kamal-proxy" ]
[ "docker", "run", "--rm", image, "dash-proxy" ]
end

manifest = {
Expand Down
4 changes: 2 additions & 2 deletions docs/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: ..
specs:
dash (3.2.0)
dash (3.2.1)
activesupport (>= 7.0, < 10)
base64 (~> 0.2)
bcrypt_pbkdf (~> 1.0)
Expand Down Expand Up @@ -445,7 +445,7 @@ CHECKSUMS
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
crass (1.0.7) sha256=94868719948664c89ddcaf0a37c65048413dfcb1c869470a5f7a7ceb5390b295
daisyui (1.2.1) sha256=597b52d19fbfd4e06d08a72ac45c62379caedd6b0d68dcbfe7ed5623009c14b3
dash (3.2.0)
dash (3.2.1)
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
Expand Down
2 changes: 1 addition & 1 deletion docs/app/views/docs/pages/config/proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
class Views::Docs::Pages::Config::Proxy < Views::Docs::Pages::ConfigPage
config_doc "proxy"

def lead = "kamal-proxy deploy options — hosts, SSL, load balancing, caching, traffic shaping, and more."
def lead = "dash-proxy deploy options — hosts, SSL, load balancing, caching, traffic shaping, and more."
end
5 changes: 3 additions & 2 deletions docs/app/views/docs/pages/from_kamal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def what_stays
[ [ :code, "config/deploy.yml" ], [ :md, "Keys unchanged — dash's config is a superset of kamal 2.x; every dash-only key is optional. ERB that calls into the gem needs `Kamal::` renamed to `Dash::` (see below)." ] ],
[ [ :code, ".kamal/secrets" ], [ :md, "Still read — the default is now `.dash/secrets`, but dash falls back to `.kamal/` when that is the only directory present. Run `dash migrate` to move it." ] ],
[ [ :md, "`.kamal/` on the servers" ], [ :md, "Renamed to `.dash/` in 3.4 — dash moves it for you, in place, the first time it takes a lock on the host. Locks, audit log, proxy boot files and app env all come along; nothing is rebooted." ] ],
[ [ :md, "`kamal-proxy` container" ], [ :md, "Same container name; dash manages the one kamal booted." ] ],
[ [ :md, "`kamal-proxy` container" ], [ :md, "Renamed to `dash-proxy` in 4.0, along with the `kamal` network and the `kamal-proxy-config` volume. dash migrates each host on the next deploy: it copies the config volume so certificates are not re-issued, joins everything on the `kamal` network to `dash`, then replaces the container. **Replacing it costs a short outage on that host** — the old container has to release ports 80/443 before the new one can bind them." ] ],
[ [ :md, "`KAMAL_*` env vars & secrets" ], [ :md, "Still set — dash writes `DASH_*` and `KAMAL_*` side by side, so existing hooks and apps keep working. The `KAMAL_*` names go away in dash 5.0." ] ],
[ [ :md, "Hooks (`.kamal/hooks/`)" ], [ :md, "Still read — same hook names, same environment, with `.dash/hooks/` as the new default location." ] ]
]
Expand All @@ -68,7 +68,8 @@ def the_proxy
[dash-proxy](https://github.com/zoolutions/dash-proxy)
(`ghcr.io/zoolutions/dash-proxy`) — a superset of kamal-proxy that adds
load balancing, SAN batching, wildcard certs, caching, and traffic
shaping. The container keeps the `kamal-proxy` name.
shaping. From 4.0 the container is named `dash-proxy`; dash renames an
existing `kamal-proxy` container for you on the next deploy.

dash reads the running proxy's version from its image tag and compares
it with the minimum version this gem requires. On your first
Expand Down
12 changes: 7 additions & 5 deletions docs/app/views/docs/pages/overview.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ def what_is_dash
merge — proxy load balancing, readiness gates, response caching, traffic
shaping, SAN certificate batching, wildcard certificates — and moves at
its own pace. Existing kamal deployments **upgrade in place**: the
`kamal-proxy` container is untouched, the server's `.kamal/` directory
renames itself to `.dash/` on the next deploy, `KAMAL_*` env vars are
still set alongside their `DASH_*` twins, and a `.kamal/` directory in
your repo is still read. See [From kamal](/docs/from-kamal) for the
migration story.
server's `.kamal/` directory renames itself to `.dash/` on the next
deploy, `KAMAL_*` env vars are still set alongside their `DASH_*`
twins, and a `.kamal/` directory in your repo is still read. In 4.0 the
proxy container, network and volumes are renamed too — dash migrates
each host itself, carrying the certificate store across, at the cost of
a short outage per host while the container is replaced. See
[From kamal](/docs/from-kamal) for the migration story.
MD
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/dash/cli/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ def maintenance
end
end

desc "rollout <deploy|set|stop>", "Manage a canary rollout of a new version through kamal-proxy"
desc "rollout <deploy|set|stop>", "Manage a canary rollout of a new version through dash-proxy"
option :percent, type: :numeric, desc: "Percentage of traffic to send to the rollout target"
option :list, type: :array, desc: "Send requests whose kamal-rollout cookie matches these values to the rollout target"
def rollout(action)
Expand Down
4 changes: 2 additions & 2 deletions lib/dash/cli/app/boot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def start_new_version
raise Dash::Cli::BootError, "Failed to get endpoint for #{role} on #{host}, did the container boot?" if endpoint.empty?

run_hook "pre-proxy-deploy", hosts: host.to_s, role: role.name
info "Deploying #{role} on #{host} via kamal-proxy (waiting up to #{DASH.config.deploy_timeout}s for it to become healthy)..."
info "Deploying #{role} on #{host} via dash-proxy (waiting up to #{DASH.config.deploy_timeout}s for it to become healthy)..."
execute *app.deploy(target: endpoint)
run_hook "post-proxy-deploy", hosts: host.to_s, role: role.name
else
Expand All @@ -84,7 +84,7 @@ def exec_probe_status
end

# Every failed boot gets the container log, and the health probe history when the
# container declares a healthcheck — non-primary roles have no kamal-proxy report to fall back on.
# container declares a healthcheck — non-primary roles have no dash-proxy report to fall back on.
def dump_diagnostics
error capture_with_info(*app.logs(container_id: app.container_id_for_version(version)))

Expand Down
2 changes: 1 addition & 1 deletion lib/dash/cli/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def with_raw_output(raw, &block)
raw ? DASH.with_verbosity(:error, &block) : block.call
end

# kamal-proxy is one container per host, shared by every destination
# dash-proxy is one container per host, shared by every destination
# deployed there, but the deploy lock is per-destination — so two
# destinations deploying at once take different locks and both mutate the
# same proxy. Anything touching the proxy takes this lock as well.
Expand Down
2 changes: 1 addition & 1 deletion lib/dash/cli/doctor/endpoint_checks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
require "socket"
require "openssl"

# Local (no SSH) readiness checks for the domains kamal-proxy will serve:
# Local (no SSH) readiness checks for the domains dash-proxy will serve:
# DNS resolution against the configured hosts and TLS certificate expiry.
class Dash::Cli::Doctor::EndpointChecks
CERTIFICATE_EXPIRY_WARN_DAYS = 14
Expand Down
8 changes: 4 additions & 4 deletions lib/dash/cli/doctor/host_checks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,13 @@ def proxy_socket_check(proxy_running:)
if mounted.include?(expected)
result :proxy_socket, :ok, "docker socket #{expected} is mounted"
elsif sleep_configured?
result :proxy_socket, :fail, "the running kamal-proxy has no #{expected} mount, so sleeping services never wake - run `dash proxy reboot`"
result :proxy_socket, :fail, "the running dash-proxy has no #{expected} mount, so sleeping services never wake - run `dash proxy reboot`"
else
# Nothing sleeps yet, so nothing hangs - drift rather than breakage.
result :proxy_socket, :warn, "the running kamal-proxy has no #{expected} mount - run `dash proxy reboot` to apply the current configuration"
result :proxy_socket, :warn, "the running dash-proxy has no #{expected} mount - run `dash proxy reboot` to apply the current configuration"
end
elsif (stray = mounted.grep(DOCKER_SOCKET_PATTERN).first)
result :proxy_socket, :warn, "the running kamal-proxy mounts #{stray} but the config no longer asks for it - " \
result :proxy_socket, :warn, "the running dash-proxy mounts #{stray} but the config no longer asks for it - " \
"the socket is root-equivalent host access; `dash proxy reboot` removes it"
else
result :proxy_socket, :ok, "no docker socket configured or mounted"
Expand All @@ -154,7 +154,7 @@ def ports_check(proxy_running:)
https_port = run_config&.https_port || Dash::Configuration::Proxy::Run::DEFAULT_HTTPS_PORT

if proxy_running
result :ports, :ok, "ports #{http_port}/#{https_port} held by the running kamal-proxy"
result :ports, :ok, "ports #{http_port}/#{https_port} held by the running dash-proxy"
elsif (busy = busy_ports(http_port, https_port)).any?
result :ports, :fail, "port(s) #{busy.join(", ")} already in use by another process"
else
Expand Down
10 changes: 5 additions & 5 deletions lib/dash/cli/main.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def deploy(boot_accessories: false)
modify(lock: true) do
run_hook "pre-deploy", secrets: true

say "Ensure kamal-proxy is running...", :magenta
say "Ensure dash-proxy is running...", :magenta
invoke "dash:cli:proxy:boot", [], invoke_options

invoke "dash:cli:accessory:boot", [ "all" ], invoke_options if boot_accessories
Expand All @@ -63,7 +63,7 @@ def deploy(boot_accessories: false)
end
end

desc "redeploy", "Deploy app to servers without bootstrapping servers, starting kamal-proxy and pruning"
desc "redeploy", "Deploy app to servers without bootstrapping servers, starting dash-proxy and pruning"
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
option :no_cache, type: :boolean, default: false, desc: "Build without using Docker's build cache"
def redeploy
Expand Down Expand Up @@ -226,7 +226,7 @@ def init
end
end

desc "remove", "Remove kamal-proxy, app, accessories, and registry session from servers"
desc "remove", "Remove dash-proxy, app, accessories, and registry session from servers"
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
def remove
confirming "This will remove all containers and images. Are you sure?" do
Expand All @@ -249,7 +249,7 @@ def migrate
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
option :rolling, type: :boolean, default: false, desc: "Upgrade one host at a time"
def upgrade
confirming "This will replace Traefik with kamal-proxy and restart all accessories" do
confirming "This will replace Traefik with dash-proxy and restart all accessories" do
modify(lock: true) do
if options[:rolling]
DASH.hosts.each do |host|
Expand Down Expand Up @@ -293,7 +293,7 @@ def version
desc "lock", "Manage the deploy lock"
subcommand "lock", Dash::Cli::Lock

desc "proxy", "Manage kamal-proxy"
desc "proxy", "Manage dash-proxy"
subcommand "proxy", Dash::Cli::Proxy

desc "prune", "Prune old application images and containers"
Expand Down
24 changes: 14 additions & 10 deletions lib/dash/cli/proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def boot
on(proxy_hosts) do |host|
execute *DASH.registry.login

# Before anything reads the new container, volume or network: bring a
# host still on pre-rename identity across. A no-op once it has been.
Dash::Cli::Proxy::LegacyRename.new(host, self).run

proxy = DASH.proxy(host)
drift = Dash::Cli::Proxy::Drift.new(host, self)

Expand All @@ -33,7 +37,7 @@ def boot
version = capture_with_info(*proxy.version).strip.presence

if version && Dash::Utils.older_version?(version, Dash::Configuration::Proxy::Run::MINIMUM_VERSION)
raise "kamal-proxy version #{version} is too old, run `dash proxy reboot` in order to update to at least #{Dash::Configuration::Proxy::Run::MINIMUM_VERSION}"
raise "dash-proxy version #{version} is too old, run `dash proxy reboot` in order to update to at least #{Dash::Configuration::Proxy::Run::MINIMUM_VERSION}"
end

if (run_config = proxy.proxy_run_config)&.secrets?
Expand All @@ -51,12 +55,12 @@ def boot
end

if stale_hosts.any?
say "kamal-proxy on #{stale_hosts.sort.join(", ")} is running with a configuration that no longer matches the deploy config. " \
say "dash-proxy on #{stale_hosts.sort.join(", ")} is running with a configuration that no longer matches the deploy config. " \
"Automatic reboot is disabled (proxy: reboot_on_deploy: false) - run `dash proxy reboot` to apply the new configuration.", :yellow
end

drifted_hosts.sort.each do |host|
say "kamal-proxy configuration changed, rebooting on #{host}...", :magenta
say "dash-proxy configuration changed, rebooting on #{host}...", :magenta
run_hook "pre-proxy-reboot", hosts: host
on(host) do |h|
Dash::Cli::Proxy::Reboot.new(h, self).run
Expand Down Expand Up @@ -129,7 +133,7 @@ def boot
end
end

desc "boot_config <set|get|reset>", "Manage kamal-proxy boot configuration"
desc "boot_config <set|get|reset>", "Manage dash-proxy boot configuration"
option :publish, type: :boolean, default: true, desc: "Publish the proxy ports on the host"
option :publish_host_ip, type: :string, repeatable: true, default: nil, desc: "Host IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces"
option :http_port, type: :numeric, default: Dash::Configuration::Proxy::Run::DEFAULT_HTTP_PORT, desc: "HTTP port to publish on the host"
Expand Down Expand Up @@ -163,7 +167,7 @@ def boot_config(subcommand)
image_version = options[:image_version]

run_command_options = { debug: options[:debug] || nil, "metrics-port": options[:metrics_port] }.compact
run_command = "kamal-proxy run #{Dash::Utils.optionize(run_command_options).join(" ")}" if run_command_options.any?
run_command = "dash-proxy run #{Dash::Utils.optionize(run_command_options).join(" ")}" if run_command_options.any?

on(DASH.proxy_hosts) do |host|
proxy = DASH.proxy(host)
Expand Down Expand Up @@ -229,7 +233,7 @@ def reboot
host_list = Array(hosts).join(",")
run_hook "pre-proxy-reboot", hosts: host_list
on(hosts) do |host|
info "Rebooting kamal-proxy on #{host}..."
info "Rebooting dash-proxy on #{host}..."
Dash::Cli::Proxy::Reboot.new(host, self).run
end
run_hook "post-proxy-reboot", hosts: host_list
Expand All @@ -249,7 +253,7 @@ def reboot
end
end

desc "upgrade", "Upgrade to kamal-proxy on servers (stop container, remove container, start new container, reboot app)", hide: true
desc "upgrade", "Upgrade to dash-proxy on servers (stop container, remove container, start new container, reboot app)", hide: true
option :rolling, type: :boolean, default: false, desc: "Reboot proxy on hosts in sequence, rather than in parallel"
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
def upgrade
Expand All @@ -269,7 +273,7 @@ def upgrade
info "Stopping and removing Traefik on #{host}, if running..."
execute *proxy.cleanup_traefik

info "Stopping and removing kamal-proxy on #{host}, if running..."
info "Stopping and removing dash-proxy on #{host}, if running..."
execute *proxy.stop, raise_on_non_zero_exit: false
execute *proxy.remove_container
execute *proxy.remove_image
Expand Down Expand Up @@ -566,7 +570,7 @@ def import_certs
on(cert_store_host) do |host|
commands = load_balancing ? DASH.loadbalancer : DASH.proxy(host)

# kamal-proxy import runs offline against the data directory - importing
# dash-proxy import runs offline against the data directory - importing
# under a live proxy risks a torn store. --verify only reads the archive.
unless verify
if capture_with_info(*commands.container_id(only_running: true), raise_on_non_zero_exit: false).strip.present?
Expand Down Expand Up @@ -652,7 +656,7 @@ def cert_store_host
DASH.config.proxy.load_balancing? ? DASH.config.proxy.effective_loadbalancer : DASH.primary_host
end

# Mirrors kamal-proxy's own flag groups (import.go), so a contradictory
# Mirrors dash-proxy's own flag groups (import.go), so a contradictory
# invocation fails before anything is uploaded.
def validate_import_certs_options!
if options[:traefik_acme].present? == options[:archive].present?
Expand Down
58 changes: 58 additions & 0 deletions lib/dash/cli/proxy/legacy_rename.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Stage 3c migration: brings a host that still carries pre-rename container
# identity onto the renamed one.
#
# Three steps, run in this order on each proxy host before the normal boot:
#
# 1. Bridge the network. Docker cannot rename one, so `dash` is created
# alongside `kamal` and everything still attached to the old network joins
# the new one. App containers would be replaced by the next deploy anyway;
# accessories are not, which is the whole reason this exists — without it a
# renamed proxy cannot reach `db` or `redis`.
#
# 2. Copy the config volume. It holds the routing table and the ACME account
# and certificate cache, so losing it means re-issuing every certificate
# and spending Let's Encrypt rate limits to get back to where we were. This
# must happen before the new container starts.
#
# 3. Replace the legacy container. A rename means the old container has to
# release ports 80/443 before the new one can claim them, and no
# port-holder handoff spans two container names — so this stage accepts a
# brief outage per host. Deliberate; see zoolutions/dash#124.
#
# Every step is idempotent and guarded on its destination not already existing,
# so a second deploy is a no-op. Nothing here removes the legacy network or
# volume: an operator who wants them gone removes them by hand, and stage 3d
# deletes this class outright.
class Dash::Cli::Proxy::LegacyRename
attr_reader :host, :sshkit
delegate :execute, to: :sshkit

def initialize(host, sshkit)
@host = host
@sshkit = sshkit
end

def run
bridge_network
adopt_config_volume
replace_legacy_container
end

private
def bridge_network
execute *DASH.docker.connect_legacy_network_containers
end

def adopt_config_volume
execute *DASH.proxy(host).copy_legacy_config_volume
end

# The drain timeout the proxy is configured with, so a busy host is not cut
# off mid-request any more abruptly than a normal reboot would.
def replace_legacy_container
proxy = DASH.proxy(host)

execute *proxy.remove_legacy_container(timeout: DASH.config.drain_timeout)
execute *proxy.remove_legacy_holder_container
end
end
Loading