dash 3.2.0 → 4.0.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.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/bin/dash +2 -2
  4. data/lib/{kamal → dash}/cli/accessory.rb +24 -24
  5. data/lib/dash/cli/alias/command.rb +10 -0
  6. data/lib/{kamal → dash}/cli/app/assets.rb +2 -2
  7. data/lib/{kamal → dash}/cli/app/boot.rb +16 -16
  8. data/lib/{kamal → dash}/cli/app/error_pages.rb +6 -6
  9. data/lib/{kamal → dash}/cli/app/rollout_boot.rb +6 -6
  10. data/lib/{kamal → dash}/cli/app/ssl_certificates.rb +3 -3
  11. data/lib/{kamal → dash}/cli/app.rb +87 -87
  12. data/lib/{kamal → dash}/cli/base.rb +72 -48
  13. data/lib/dash/cli/build/clone.rb +59 -0
  14. data/lib/{kamal → dash}/cli/build/port_forwarding.rb +1 -1
  15. data/lib/{kamal → dash}/cli/build.rb +45 -45
  16. data/lib/{kamal → dash}/cli/doctor/config_checks.rb +3 -3
  17. data/lib/{kamal → dash}/cli/doctor/endpoint_checks.rb +26 -8
  18. data/lib/{kamal → dash}/cli/doctor/host_checks.rb +22 -22
  19. data/lib/{kamal → dash}/cli/doctor.rb +9 -9
  20. data/lib/{kamal → dash}/cli/healthcheck/barrier.rb +2 -2
  21. data/lib/{kamal → dash}/cli/healthcheck/drift_error.rb +2 -2
  22. data/lib/dash/cli/healthcheck/error.rb +2 -0
  23. data/lib/{kamal → dash}/cli/healthcheck/poller.rb +7 -7
  24. data/lib/{kamal → dash}/cli/lock.rb +1 -1
  25. data/lib/dash/cli/main/migrate.rb +77 -0
  26. data/lib/{kamal → dash}/cli/main.rb +82 -71
  27. data/lib/{kamal → dash}/cli/proxy/drift.rb +3 -3
  28. data/lib/dash/cli/proxy/legacy_rename.rb +58 -0
  29. data/lib/{kamal → dash}/cli/proxy/loadbalancer_claim.rb +3 -3
  30. data/lib/{kamal → dash}/cli/proxy/loadbalancer_reboot.rb +23 -23
  31. data/lib/{kamal → dash}/cli/proxy/reboot.rb +21 -21
  32. data/lib/{kamal → dash}/cli/proxy.rb +178 -162
  33. data/lib/{kamal → dash}/cli/prune.rb +10 -10
  34. data/lib/{kamal → dash}/cli/registry.rb +11 -11
  35. data/lib/{kamal → dash}/cli/secrets.rb +3 -3
  36. data/lib/{kamal → dash}/cli/server.rb +14 -14
  37. data/lib/{kamal → dash}/cli/templates/deploy.yml +4 -4
  38. data/lib/dash/cli/templates/sample_hooks/docker-setup.sample +3 -0
  39. data/lib/dash/cli/templates/sample_hooks/post-app-boot.sample +3 -0
  40. data/lib/dash/cli/templates/sample_hooks/post-app-stop.sample +9 -0
  41. data/lib/dash/cli/templates/sample_hooks/post-deploy.sample +14 -0
  42. data/lib/dash/cli/templates/sample_hooks/post-proxy-deploy.sample +3 -0
  43. data/lib/dash/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  44. data/lib/dash/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
  45. data/lib/{kamal → dash}/cli/templates/sample_hooks/pre-app-stop.sample +2 -2
  46. data/lib/{kamal → dash}/cli/templates/sample_hooks/pre-build.sample +8 -8
  47. data/lib/{kamal → dash}/cli/templates/sample_hooks/pre-connect.sample +8 -8
  48. data/lib/{kamal → dash}/cli/templates/sample_hooks/pre-deploy.sample +9 -9
  49. data/lib/dash/cli/templates/sample_hooks/pre-proxy-deploy.sample +3 -0
  50. data/lib/dash/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
  51. data/lib/{kamal → dash}/cli/templates/secrets +6 -6
  52. data/lib/{kamal → dash}/cli.rb +2 -2
  53. data/lib/{kamal → dash}/commander/specifics.rb +2 -2
  54. data/lib/{kamal → dash}/commander.rb +32 -24
  55. data/lib/{kamal → dash}/commands/accessory/proxy.rb +2 -2
  56. data/lib/{kamal → dash}/commands/accessory.rb +2 -2
  57. data/lib/{kamal → dash}/commands/app/assets.rb +1 -1
  58. data/lib/{kamal → dash}/commands/app/containers.rb +1 -1
  59. data/lib/{kamal → dash}/commands/app/error_pages.rb +2 -2
  60. data/lib/{kamal → dash}/commands/app/execution.rb +2 -2
  61. data/lib/{kamal → dash}/commands/app/images.rb +1 -1
  62. data/lib/{kamal → dash}/commands/app/logging.rb +1 -1
  63. data/lib/{kamal → dash}/commands/app/proxy.rb +2 -2
  64. data/lib/{kamal → dash}/commands/app.rb +2 -2
  65. data/lib/{kamal → dash}/commands/auditor.rb +3 -7
  66. data/lib/{kamal → dash}/commands/base.rb +39 -3
  67. data/lib/{kamal → dash}/commands/builder/base.rb +2 -2
  68. data/lib/{kamal → dash}/commands/builder/clone.rb +3 -3
  69. data/lib/{kamal → dash}/commands/builder/cloud.rb +1 -1
  70. data/lib/{kamal → dash}/commands/builder/hybrid.rb +1 -1
  71. data/lib/{kamal → dash}/commands/builder/local.rb +1 -1
  72. data/lib/{kamal → dash}/commands/builder/pack.rb +1 -1
  73. data/lib/{kamal → dash}/commands/builder/remote.rb +1 -1
  74. data/lib/{kamal → dash}/commands/builder.rb +7 -7
  75. data/lib/dash/commands/docker.rb +89 -0
  76. data/lib/{kamal → dash}/commands/hook.rb +1 -1
  77. data/lib/{kamal → dash}/commands/loadbalancer.rb +55 -24
  78. data/lib/{kamal → dash}/commands/lock.rb +3 -3
  79. data/lib/{kamal → dash}/commands/proxy/cert_transfer.rb +6 -6
  80. data/lib/dash/commands/proxy.rb +375 -0
  81. data/lib/{kamal → dash}/commands/prune.rb +2 -2
  82. data/lib/dash/commands/registry.rb +44 -0
  83. data/lib/{kamal → dash}/commands/server.rb +1 -5
  84. data/lib/dash/commands.rb +2 -0
  85. data/lib/{kamal → dash}/configuration/accessory.rb +13 -13
  86. data/lib/{kamal → dash}/configuration/alias.rb +3 -3
  87. data/lib/{kamal → dash}/configuration/boot.rb +2 -2
  88. data/lib/{kamal → dash}/configuration/builder.rb +7 -7
  89. data/lib/{kamal → dash}/configuration/docs/accessory.yml +3 -3
  90. data/lib/{kamal → dash}/configuration/docs/builder.yml +3 -3
  91. data/lib/{kamal → dash}/configuration/docs/configuration.yml +13 -6
  92. data/lib/{kamal → dash}/configuration/docs/env.yml +10 -10
  93. data/lib/{kamal → dash}/configuration/docs/proxy.yml +68 -45
  94. data/lib/{kamal → dash}/configuration/docs/registry.yml +5 -5
  95. data/lib/{kamal → dash}/configuration/env/tag.rb +2 -2
  96. data/lib/{kamal → dash}/configuration/env.rb +5 -5
  97. data/lib/{kamal → dash}/configuration/loadbalancer.rb +5 -5
  98. data/lib/{kamal → dash}/configuration/logging.rb +3 -3
  99. data/lib/{kamal → dash}/configuration/output.rb +4 -4
  100. data/lib/{kamal → dash}/configuration/proxy/acme.rb +9 -8
  101. data/lib/{kamal → dash}/configuration/proxy/boot.rb +9 -11
  102. data/lib/{kamal → dash}/configuration/proxy/run.rb +20 -20
  103. data/lib/{kamal → dash}/configuration/proxy.rb +51 -29
  104. data/lib/{kamal → dash}/configuration/registry.rb +3 -3
  105. data/lib/{kamal → dash}/configuration/role/healthcheck.rb +3 -3
  106. data/lib/{kamal → dash}/configuration/role.rb +14 -14
  107. data/lib/{kamal → dash}/configuration/servers.rb +4 -4
  108. data/lib/{kamal → dash}/configuration/ssh.rb +2 -2
  109. data/lib/{kamal → dash}/configuration/sshkit.rb +2 -2
  110. data/lib/{kamal → dash}/configuration/validation.rb +2 -2
  111. data/lib/{kamal → dash}/configuration/validator/accessory.rb +1 -1
  112. data/lib/{kamal → dash}/configuration/validator/alias.rb +2 -2
  113. data/lib/{kamal → dash}/configuration/validator/builder.rb +1 -1
  114. data/lib/dash/configuration/validator/configuration.rb +6 -0
  115. data/lib/{kamal → dash}/configuration/validator/env.rb +2 -2
  116. data/lib/{kamal → dash}/configuration/validator/proxy.rb +31 -31
  117. data/lib/{kamal → dash}/configuration/validator/registry.rb +1 -1
  118. data/lib/{kamal → dash}/configuration/validator/role.rb +1 -1
  119. data/lib/{kamal → dash}/configuration/validator/servers.rb +1 -1
  120. data/lib/{kamal → dash}/configuration/validator.rb +4 -4
  121. data/lib/{kamal → dash}/configuration/volume.rb +2 -2
  122. data/lib/{kamal → dash}/configuration.rb +47 -37
  123. data/lib/{kamal → dash}/docker.rb +1 -1
  124. data/lib/{kamal → dash}/env_file.rb +1 -1
  125. data/lib/{kamal → dash}/git.rb +1 -1
  126. data/lib/{kamal → dash}/otel_shipper.rb +3 -3
  127. data/lib/{kamal → dash}/output/base_logger.rb +1 -1
  128. data/lib/{kamal → dash}/output/file_logger.rb +1 -1
  129. data/lib/{kamal → dash}/output/formatter.rb +1 -1
  130. data/lib/{kamal → dash}/output/otel_logger.rb +3 -3
  131. data/lib/dash/project_directory.rb +28 -0
  132. data/lib/{kamal → dash}/secrets/adapters/aws_secrets_manager.rb +1 -1
  133. data/lib/{kamal → dash}/secrets/adapters/base.rb +2 -2
  134. data/lib/{kamal → dash}/secrets/adapters/bitwarden.rb +1 -1
  135. data/lib/{kamal → dash}/secrets/adapters/bitwarden_secrets_manager.rb +1 -1
  136. data/lib/{kamal → dash}/secrets/adapters/doppler.rb +1 -1
  137. data/lib/{kamal → dash}/secrets/adapters/enpass.rb +1 -1
  138. data/lib/{kamal → dash}/secrets/adapters/gcp_secret_manager.rb +1 -1
  139. data/lib/{kamal → dash}/secrets/adapters/last_pass.rb +1 -1
  140. data/lib/{kamal → dash}/secrets/adapters/one_password.rb +2 -2
  141. data/lib/{kamal → dash}/secrets/adapters/passbolt.rb +1 -1
  142. data/lib/{kamal → dash}/secrets/adapters/test.rb +1 -1
  143. data/lib/{kamal → dash}/secrets/adapters.rb +2 -2
  144. data/lib/{kamal → dash}/secrets/dotenv/inline_command_substitution.rb +2 -2
  145. data/lib/{kamal → dash}/secrets.rb +6 -6
  146. data/lib/{kamal → dash}/tags.rb +8 -3
  147. data/lib/{kamal → dash}/utils/sensitive.rb +1 -1
  148. data/lib/{kamal → dash}/utils.rb +16 -6
  149. data/lib/dash/version.rb +3 -0
  150. data/lib/{kamal.rb → dash.rb} +4 -4
  151. metadata +157 -154
  152. data/lib/kamal/cli/alias/command.rb +0 -10
  153. data/lib/kamal/cli/build/clone.rb +0 -59
  154. data/lib/kamal/cli/healthcheck/error.rb +0 -2
  155. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +0 -3
  156. data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +0 -3
  157. data/lib/kamal/cli/templates/sample_hooks/post-app-stop.sample +0 -9
  158. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +0 -14
  159. data/lib/kamal/cli/templates/sample_hooks/post-proxy-deploy.sample +0 -3
  160. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +0 -3
  161. data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +0 -3
  162. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-deploy.sample +0 -3
  163. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +0 -3
  164. data/lib/kamal/commands/docker.rb +0 -55
  165. data/lib/kamal/commands/proxy.rb +0 -266
  166. data/lib/kamal/commands/registry.rb +0 -38
  167. data/lib/kamal/commands.rb +0 -2
  168. data/lib/kamal/configuration/validator/configuration.rb +0 -6
  169. data/lib/kamal/version.rb +0 -3
  170. /data/lib/{kamal → dash}/configuration/docs/alias.yml +0 -0
  171. /data/lib/{kamal → dash}/configuration/docs/boot.yml +0 -0
  172. /data/lib/{kamal → dash}/configuration/docs/logging.yml +0 -0
  173. /data/lib/{kamal → dash}/configuration/docs/output.yml +0 -0
  174. /data/lib/{kamal → dash}/configuration/docs/role.yml +0 -0
  175. /data/lib/{kamal → dash}/configuration/docs/servers.yml +0 -0
  176. /data/lib/{kamal → dash}/configuration/docs/ssh.yml +0 -0
  177. /data/lib/{kamal → dash}/configuration/docs/sshkit.yml +0 -0
  178. /data/lib/{kamal → dash}/sshkit_with_ext.rb +0 -0
@@ -2,9 +2,9 @@ require "resolv"
2
2
  require "socket"
3
3
  require "openssl"
4
4
 
5
- # Local (no SSH) readiness checks for the domains kamal-proxy will serve:
5
+ # Local (no SSH) readiness checks for the domains dash-proxy will serve:
6
6
  # DNS resolution against the configured hosts and TLS certificate expiry.
7
- class Kamal::Cli::Doctor::EndpointChecks
7
+ class Dash::Cli::Doctor::EndpointChecks
8
8
  CERTIFICATE_EXPIRY_WARN_DAYS = 14
9
9
  SECONDS_PER_DAY = 86_400
10
10
  TLS_CONNECT_TIMEOUT = 5
@@ -15,22 +15,39 @@ class Kamal::Cli::Doctor::EndpointChecks
15
15
 
16
16
  private
17
17
  def result(check, target, status, detail)
18
- Kamal::Cli::Doctor::Result.new(check, target, status, detail)
18
+ Dash::Cli::Doctor::Result.new(check, target, status, detail)
19
19
  end
20
20
 
21
21
  # Roles and accessories that run behind the proxy with custom domains.
22
22
  def proxied_units
23
- @proxied_units ||= (KAMAL.roles + KAMAL.config.proxy_accessories).select do |unit|
23
+ @proxied_units ||= (DASH.roles + DASH.config.proxy_accessories).select do |unit|
24
24
  unit.running_proxy? && unit.proxy.hosts.any?
25
25
  end
26
26
  end
27
27
 
28
28
  def dns_results
29
29
  proxied_units.flat_map do |unit|
30
- unit.proxy.hosts.map { |domain| dns_check(domain, unit.hosts) }
30
+ unit.proxy.hosts.map { |domain| dns_check(domain, unit.hosts, acme_issued: acme_issued?(unit)) }
31
31
  end
32
32
  end
33
33
 
34
+ # Whether this unit's certificate comes from ACME rather than the operator.
35
+ # Only then does where the domain points decide whether a certificate can
36
+ # exist — a supplied certificate is unaffected by DNS.
37
+ def acme_issued?(unit)
38
+ unit.proxy.ssl? && !unit.proxy.custom_ssl_certificate?
39
+ end
40
+
41
+ # The two states a DNS cutover passes through — not resolving yet, and still
42
+ # resolving to the old host — are exactly when an operator wonders whether
43
+ # something is broken. Nothing is: the proxy refuses to spend an ACME order
44
+ # it cannot win, re-checks held domains, and issues once the domain arrives.
45
+ ISSUANCE_PENDING = "no certificate can be issued until it points here, and the proxy issues automatically once it does".freeze
46
+
47
+ def with_issuance_note(detail, acme_issued)
48
+ acme_issued ? "#{detail} - #{ISSUANCE_PENDING}" : detail
49
+ end
50
+
34
51
  def certificate_results
35
52
  proxied_units.flat_map do |unit|
36
53
  next [] unless unit.proxy.ssl?
@@ -41,17 +58,18 @@ class Kamal::Cli::Doctor::EndpointChecks
41
58
  end
42
59
  end
43
60
 
44
- def dns_check(domain, unit_hosts)
61
+ def dns_check(domain, unit_hosts, acme_issued: false)
45
62
  resolved = Resolv.getaddresses(domain)
46
63
  expected = expected_ips(unit_hosts)
47
64
  matching = resolved & expected
48
65
 
49
66
  if resolved.empty?
50
- result :dns, domain, :fail, "does not resolve"
67
+ result :dns, domain, :fail, with_issuance_note("does not resolve", acme_issued)
51
68
  elsif matching.any?
52
69
  result :dns, domain, :ok, "resolves to #{matching.join(", ")}"
53
70
  else
54
- result :dns, domain, :warn, "resolves to #{resolved.join(", ")}, expected one of #{expected.join(", ")} (fine when fronted by a CDN or load balancer)"
71
+ result :dns, domain, :warn,
72
+ with_issuance_note("resolves to #{resolved.join(", ")}, expected one of #{expected.join(", ")} (fine when fronted by a CDN or load balancer)", acme_issued)
55
73
  end
56
74
  rescue StandardError => e
57
75
  result :dns, domain, :fail, "could not resolve (#{e.message})"
@@ -3,7 +3,7 @@
3
3
  # Every check rescues StandardError at its boundary: the doctor's contract is
4
4
  # to report broken environments, not crash on them, and a failed connection can
5
5
  # raise anything from Net::SSH errors to Errno and DNS resolution errors.
6
- class Kamal::Cli::Doctor::HostChecks
6
+ class Dash::Cli::Doctor::HostChecks
7
7
  attr_reader :host, :sshkit, :proxy_host
8
8
  delegate :execute, :capture_with_info, to: :sshkit
9
9
 
@@ -27,7 +27,7 @@ class Kamal::Cli::Doctor::HostChecks
27
27
 
28
28
  private
29
29
  def result(check, status, detail)
30
- Kamal::Cli::Doctor::Result.new(check, host, status, detail)
30
+ Dash::Cli::Doctor::Result.new(check, host, status, detail)
31
31
  end
32
32
 
33
33
  def ssh_check
@@ -38,7 +38,7 @@ class Kamal::Cli::Doctor::HostChecks
38
38
  end
39
39
 
40
40
  def docker_check
41
- if execute(*KAMAL.docker.running?, raise_on_non_zero_exit: false)
41
+ if execute(*DASH.docker.running?, raise_on_non_zero_exit: false)
42
42
  result :docker, :ok, "docker is installed and running"
43
43
  else
44
44
  result :docker, :fail, "docker is not installed or not running (run `dash server bootstrap`)"
@@ -48,9 +48,9 @@ class Kamal::Cli::Doctor::HostChecks
48
48
  end
49
49
 
50
50
  def registry_check
51
- return result(:registry, :ok, "local registry, no login required") if KAMAL.registry.local?
51
+ return result(:registry, :ok, "local registry, no login required") if DASH.registry.local?
52
52
 
53
- if execute(*KAMAL.registry.login, raise_on_non_zero_exit: false)
53
+ if execute(*DASH.registry.login, raise_on_non_zero_exit: false)
54
54
  result :registry, :ok, "logged in to #{registry_name}"
55
55
  else
56
56
  result :registry, :fail, "docker login to #{registry_name} failed (check registry credentials)"
@@ -71,7 +71,7 @@ class Kamal::Cli::Doctor::HostChecks
71
71
  end
72
72
 
73
73
  def capture_proxy_version
74
- [ capture_with_info(*KAMAL.proxy(host).version).strip.presence, nil ]
74
+ [ capture_with_info(*DASH.proxy(host).version).strip.presence, nil ]
75
75
  rescue StandardError => e
76
76
  [ nil, e ]
77
77
  end
@@ -79,7 +79,7 @@ class Kamal::Cli::Doctor::HostChecks
79
79
  def proxy_image_check
80
80
  image = expected_proxy_image
81
81
 
82
- if execute(*KAMAL.docker.manifest_available?(image), raise_on_non_zero_exit: false)
82
+ if execute(*DASH.docker.manifest_available?(image), raise_on_non_zero_exit: false)
83
83
  result :proxy_image, :ok, "#{image} manifest is fetchable"
84
84
  else
85
85
  result :proxy_image, :fail, "cannot fetch the manifest for #{image} (registry unreachable or unauthorized)"
@@ -89,13 +89,13 @@ class Kamal::Cli::Doctor::HostChecks
89
89
  end
90
90
 
91
91
  def proxy_version_check(version, error)
92
- minimum = Kamal::Configuration::Proxy::Run::MINIMUM_VERSION
92
+ minimum = Dash::Configuration::Proxy::Run::MINIMUM_VERSION
93
93
 
94
94
  if error
95
95
  result :proxy_version, :warn, "could not determine the running version (#{error.message})"
96
96
  elsif version.nil?
97
97
  result :proxy_version, :ok, "not running (will be started on deploy)"
98
- elsif Kamal::Utils.older_version?(version, minimum)
98
+ elsif Dash::Utils.older_version?(version, minimum)
99
99
  result :proxy_version, :fail, "#{version} is older than the minimum #{minimum}, run `dash proxy reboot` to update"
100
100
  else
101
101
  result :proxy_version, :ok, "#{version} (minimum #{minimum})"
@@ -114,26 +114,26 @@ class Kamal::Cli::Doctor::HostChecks
114
114
  # mode is one hung request when a sleeping service never wakes - and one
115
115
  # from before it was removed keeps root-equivalent host access.
116
116
  def proxy_socket_check(proxy_running:)
117
- expected = KAMAL.config.proxy_run(host)&.docker_socket
117
+ expected = DASH.config.proxy_run(host)&.docker_socket
118
118
 
119
119
  unless proxy_running
120
120
  detail = expected ? "docker socket #{expected} will be mounted on boot" : "no docker socket configured"
121
121
  return result(:proxy_socket, :ok, detail)
122
122
  end
123
123
 
124
- mounted = capture_with_info(*KAMAL.proxy(host).mount_destinations, raise_on_non_zero_exit: false).split("\n").map(&:strip)
124
+ mounted = capture_with_info(*DASH.proxy(host).mount_destinations, raise_on_non_zero_exit: false).split("\n").map(&:strip)
125
125
 
126
126
  if expected
127
127
  if mounted.include?(expected)
128
128
  result :proxy_socket, :ok, "docker socket #{expected} is mounted"
129
129
  elsif sleep_configured?
130
- result :proxy_socket, :fail, "the running kamal-proxy has no #{expected} mount, so sleeping services never wake - run `dash proxy reboot`"
130
+ result :proxy_socket, :fail, "the running dash-proxy has no #{expected} mount, so sleeping services never wake - run `dash proxy reboot`"
131
131
  else
132
132
  # Nothing sleeps yet, so nothing hangs - drift rather than breakage.
133
- result :proxy_socket, :warn, "the running kamal-proxy has no #{expected} mount - run `dash proxy reboot` to apply the current configuration"
133
+ result :proxy_socket, :warn, "the running dash-proxy has no #{expected} mount - run `dash proxy reboot` to apply the current configuration"
134
134
  end
135
135
  elsif (stray = mounted.grep(DOCKER_SOCKET_PATTERN).first)
136
- result :proxy_socket, :warn, "the running kamal-proxy mounts #{stray} but the config no longer asks for it - " \
136
+ result :proxy_socket, :warn, "the running dash-proxy mounts #{stray} but the config no longer asks for it - " \
137
137
  "the socket is root-equivalent host access; `dash proxy reboot` removes it"
138
138
  else
139
139
  result :proxy_socket, :ok, "no docker socket configured or mounted"
@@ -143,18 +143,18 @@ class Kamal::Cli::Doctor::HostChecks
143
143
  end
144
144
 
145
145
  def sleep_configured?
146
- KAMAL.config.roles.any? { |role| role.running_proxy? && role.proxy.proxy_config["sleep"].present? }
146
+ DASH.config.roles.any? { |role| role.running_proxy? && role.proxy.proxy_config["sleep"].present? }
147
147
  end
148
148
 
149
149
  def ports_check(proxy_running:)
150
- run_config = KAMAL.config.proxy_run(host)
150
+ run_config = DASH.config.proxy_run(host)
151
151
  return result(:ports, :ok, "proxy ports are not published, nothing to check") if run_config && !run_config.publish?
152
152
 
153
- http_port = run_config&.http_port || Kamal::Configuration::Proxy::Run::DEFAULT_HTTP_PORT
154
- https_port = run_config&.https_port || Kamal::Configuration::Proxy::Run::DEFAULT_HTTPS_PORT
153
+ http_port = run_config&.http_port || Dash::Configuration::Proxy::Run::DEFAULT_HTTP_PORT
154
+ https_port = run_config&.https_port || Dash::Configuration::Proxy::Run::DEFAULT_HTTPS_PORT
155
155
 
156
156
  if proxy_running
157
- result :ports, :ok, "ports #{http_port}/#{https_port} held by the running kamal-proxy"
157
+ result :ports, :ok, "ports #{http_port}/#{https_port} held by the running dash-proxy"
158
158
  elsif (busy = busy_ports(http_port, https_port)).any?
159
159
  result :ports, :fail, "port(s) #{busy.join(", ")} already in use by another process"
160
160
  else
@@ -165,14 +165,14 @@ class Kamal::Cli::Doctor::HostChecks
165
165
  end
166
166
 
167
167
  def busy_ports(*ports)
168
- ports.select { |port| capture_with_info(*KAMAL.server.listeners_on(port), raise_on_non_zero_exit: false).present? }
168
+ ports.select { |port| capture_with_info(*DASH.server.listeners_on(port), raise_on_non_zero_exit: false).present? }
169
169
  end
170
170
 
171
171
  def expected_proxy_image
172
- KAMAL.config.proxy_run(host)&.image || "#{KAMAL.config.proxy_boot.image_default}:#{Kamal::Configuration::Proxy::Run::MINIMUM_VERSION}"
172
+ DASH.config.proxy_run(host)&.image || "#{DASH.config.proxy_boot.image_default}:#{Dash::Configuration::Proxy::Run::MINIMUM_VERSION}"
173
173
  end
174
174
 
175
175
  def registry_name
176
- KAMAL.config.registry.server || "Docker Hub"
176
+ DASH.config.registry.server || "Docker Hub"
177
177
  end
178
178
  end
@@ -1,8 +1,8 @@
1
- require "kamal/sshkit_with_ext"
1
+ require "dash/sshkit_with_ext"
2
2
 
3
3
  # Runs read-only deploy readiness checks and collects the results, without ever
4
4
  # raising on a broken environment - failures become failing results instead.
5
- class Kamal::Cli::Doctor
5
+ class Dash::Cli::Doctor
6
6
  include SSHKit::DSL
7
7
 
8
8
  HOST_CHECKS = %i[ ssh docker registry proxy_image proxy_version proxy_socket ports ]
@@ -71,19 +71,19 @@ class Kamal::Cli::Doctor
71
71
  results_by_host = collect_host_checks
72
72
 
73
73
  HOST_CHECKS.flat_map do |check|
74
- KAMAL.hosts.filter_map { |host| results_by_host.dig(host, check) }
74
+ DASH.hosts.filter_map { |host| results_by_host.dig(host, check) }
75
75
  end
76
76
  end
77
77
 
78
78
  def collect_host_checks
79
79
  results_by_host = {}
80
80
  mutex = Mutex.new
81
- proxy_hosts = KAMAL.proxy_hosts
81
+ proxy_hosts = DASH.proxy_hosts
82
82
  error = nil
83
83
 
84
84
  begin
85
- on(KAMAL.hosts) do |host|
86
- checks = Kamal::Cli::Doctor::HostChecks.new(host.hostname, self, proxy_host: proxy_hosts.include?(host.hostname)).run
85
+ on(DASH.hosts) do |host|
86
+ checks = Dash::Cli::Doctor::HostChecks.new(host.hostname, self, proxy_host: proxy_hosts.include?(host.hostname)).run
87
87
  mutex.synchronize { results_by_host[host.hostname] = checks }
88
88
  end
89
89
  # Only ExecuteError: sshkit 1.25 has no MultipleExecuteError, and naming
@@ -95,7 +95,7 @@ class Kamal::Cli::Doctor
95
95
  error = e
96
96
  end
97
97
 
98
- KAMAL.hosts.each do |host|
98
+ DASH.hosts.each do |host|
99
99
  results_by_host[host] ||= { ssh: Result.new(:ssh, host, :fail, "could not run checks (#{error&.message || "connection failed"})") }
100
100
  end
101
101
 
@@ -103,10 +103,10 @@ class Kamal::Cli::Doctor
103
103
  end
104
104
 
105
105
  def endpoint_check_results
106
- Kamal::Cli::Doctor::EndpointChecks.new.run
106
+ Dash::Cli::Doctor::EndpointChecks.new.run
107
107
  end
108
108
 
109
109
  def config_check_results
110
- Kamal::Cli::Doctor::ConfigChecks.new.run
110
+ Dash::Cli::Doctor::ConfigChecks.new.run
111
111
  end
112
112
  end
@@ -1,6 +1,6 @@
1
1
  require "concurrent/ivar"
2
2
 
3
- class Kamal::Cli::Healthcheck::Barrier
3
+ class Dash::Cli::Healthcheck::Barrier
4
4
  def initialize
5
5
  @ivar = Concurrent::IVar.new
6
6
  end
@@ -15,7 +15,7 @@ class Kamal::Cli::Healthcheck::Barrier
15
15
 
16
16
  def wait
17
17
  unless opened?
18
- raise Kamal::Cli::Healthcheck::Error.new("Halted at barrier")
18
+ raise Dash::Cli::Healthcheck::Error.new("Halted at barrier")
19
19
  end
20
20
  end
21
21
 
@@ -1,7 +1,7 @@
1
1
  # Raised when the config declares a healthcheck the running container does not have.
2
2
  #
3
- # Deliberately NOT a Kamal::Cli::Healthcheck::Error: the poller retries that class until
3
+ # Deliberately NOT a Dash::Cli::Healthcheck::Error: the poller retries that class until
4
4
  # deploy_timeout, and drift is deterministic — `.State.Health` exists from the moment a
5
5
  # container with a healthcheck is created, so retrying only burns the timeout before
6
6
  # reporting the same thing.
7
- class Kamal::Cli::Healthcheck::DriftError < StandardError; end
7
+ class Dash::Cli::Healthcheck::DriftError < StandardError; end
@@ -0,0 +1,2 @@
1
+ class Dash::Cli::Healthcheck::Error < StandardError
2
+ end
@@ -1,11 +1,11 @@
1
- module Kamal::Cli::Healthcheck::Poller
1
+ module Dash::Cli::Healthcheck::Poller
2
2
  extend self
3
3
 
4
- NO_HEALTHCHECK = Kamal::Commands::Base::NO_HEALTHCHECK
4
+ NO_HEALTHCHECK = Dash::Commands::Base::NO_HEALTHCHECK
5
5
 
6
6
  def wait_for_healthy(role:, &block)
7
7
  attempt = 1
8
- timeout_at = Time.now + KAMAL.config.deploy_timeout
8
+ timeout_at = Time.now + DASH.config.deploy_timeout
9
9
  readiness_delay = role.readiness_delay
10
10
 
11
11
  begin
@@ -27,13 +27,13 @@ module Kamal::Cli::Healthcheck::Poller
27
27
  end
28
28
 
29
29
  unless acceptable?(status)
30
- raise Kamal::Cli::Healthcheck::Error, "container not ready after #{KAMAL.config.deploy_timeout} seconds (#{status})"
30
+ raise Dash::Cli::Healthcheck::Error, "container not ready after #{DASH.config.deploy_timeout} seconds (#{status})"
31
31
  end
32
- rescue Kamal::Cli::Healthcheck::Error => e
32
+ rescue Dash::Cli::Healthcheck::Error => e
33
33
  time_left = timeout_at - Time.now
34
34
  if time_left > 0
35
35
  sleep_for = [ attempt, time_left ].min
36
- elapsed = KAMAL.config.deploy_timeout - time_left
36
+ elapsed = DASH.config.deploy_timeout - time_left
37
37
  info "Container not ready yet, retrying in #{sleep_for.ceil}s (#{elapsed.round}s elapsed, #{time_left.round}s left)"
38
38
  sleep sleep_for
39
39
  attempt += 1
@@ -65,7 +65,7 @@ module Kamal::Cli::Healthcheck::Poller
65
65
  def ensure_no_healthcheck_drift(role, status)
66
66
  return unless %i[ healthcheck docker_options ].include?(role.readiness_source)
67
67
 
68
- raise Kamal::Cli::Healthcheck::DriftError,
68
+ raise Dash::Cli::Healthcheck::DriftError,
69
69
  "#{role.name} declares a healthcheck but the container reports none (#{status}) — the flags never reached docker. " \
70
70
  "Compare `servers/#{role.name}` in your deploy config against `docker inspect --format '{{json .Config.Healthcheck}}'` " \
71
71
  "on the container; the deploy would otherwise be accepted without ever probing it."
@@ -1,4 +1,4 @@
1
- class Kamal::Cli::Lock < Kamal::Cli::Base
1
+ class Dash::Cli::Lock < Dash::Cli::Base
2
2
  desc "status", "Report lock status"
3
3
  def status
4
4
  handle_missing_lock do
@@ -0,0 +1,77 @@
1
+ require "fileutils"
2
+
3
+ # Moves a project from the legacy `.kamal` directory to `.dash`. Purely local -
4
+ # it never opens an SSH connection and never reads deploy.yml - and only ever
5
+ # runs when the operator asks for it, because silently rewriting someone's
6
+ # working tree is not a deploy tool's job.
7
+ #
8
+ # Returns [message, color] pairs rather than printing: output belongs to the Thor
9
+ # command, and reaching into the Thor instance for `say` couples this class to a
10
+ # shell helper whose visibility changes between Thor versions.
11
+ class Dash::Cli::Main::Migrate
12
+ CURRENT = Dash::ProjectDirectory::CURRENT
13
+ LEGACY = Dash::ProjectDirectory::LEGACY
14
+
15
+ LEGACY_ENV_PATTERN = /\bKAMAL_[A-Z0-9_]+/
16
+ EXTRA_SCANNED_FILES = [ "config/deploy.yml" ].freeze
17
+
18
+ attr_reader :dry_run
19
+
20
+ def initialize(dry_run: false)
21
+ @dry_run = dry_run
22
+ end
23
+
24
+ def run
25
+ return [ [ "No #{LEGACY} directory to migrate.", nil ] ] unless Dir.exist?(LEGACY)
26
+
27
+ if Dir.exist?(CURRENT)
28
+ return [ [ "Both #{CURRENT} and #{LEGACY} exist. Merge them by hand, then remove #{LEGACY}.", :yellow ] ]
29
+ end
30
+
31
+ if dry_run
32
+ [ [ "Would move #{LEGACY} to #{CURRENT}#{" with git mv" if tracked_by_git?}", nil ], *legacy_env_reference_lines ]
33
+ else
34
+ move
35
+ [ [ "Moved #{LEGACY} to #{CURRENT}", :green ], *legacy_env_reference_lines ]
36
+ end
37
+ end
38
+
39
+ private
40
+ # `git mv` stages the rename, so the operator is left with a clean rename in
41
+ # `git status` rather than a delete plus a pile of untracked files.
42
+ def move
43
+ if tracked_by_git?
44
+ system("git", "mv", LEGACY, CURRENT, exception: true)
45
+ else
46
+ FileUtils.mv LEGACY, CURRENT
47
+ end
48
+ end
49
+
50
+ def tracked_by_git?
51
+ return @tracked_by_git if defined?(@tracked_by_git)
52
+ @tracked_by_git = !`git ls-files -- #{LEGACY} 2> /dev/null`.strip.empty?
53
+ end
54
+
55
+ # Reported, never rewritten: both prefixes carry the same value until 5.0,
56
+ # so there is nothing to fix today and an automatic rewrite of hook scripts
57
+ # would be a much bigger promise than this command makes.
58
+ def legacy_env_reference_lines
59
+ references = scanned_files.filter_map do |file|
60
+ names = File.read(file, encoding: Encoding::BINARY).scan(LEGACY_ENV_PATTERN).uniq
61
+ [ file, names ] if names.any?
62
+ end
63
+
64
+ return [] if references.empty?
65
+
66
+ [
67
+ [ "\nFound legacy KAMAL_* references. dash sets both prefixes until 5.0, so nothing is urgent:", nil ],
68
+ *references.map { |file, names| [ " #{file}: #{names.sort.join(", ")}", nil ] }
69
+ ]
70
+ end
71
+
72
+ def scanned_files
73
+ directory = dry_run ? LEGACY : CURRENT
74
+
75
+ (Dir.glob("#{directory}/**/*") + EXTRA_SCANNED_FILES).select { |file| File.file?(file) && File.readable?(file) }
76
+ end
77
+ end