kamal 2.0.0.alpha → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) 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/app/boot.rb +22 -16
  5. data/lib/kamal/cli/app.rb +37 -3
  6. data/lib/kamal/cli/base.rb +9 -48
  7. data/lib/kamal/cli/build.rb +8 -3
  8. data/lib/kamal/cli/healthcheck/barrier.rb +2 -0
  9. data/lib/kamal/cli/healthcheck/poller.rb +18 -39
  10. data/lib/kamal/cli/lock.rb +2 -3
  11. data/lib/kamal/cli/main.rb +54 -51
  12. data/lib/kamal/cli/proxy.rb +224 -0
  13. data/lib/kamal/cli/prune.rb +0 -1
  14. data/lib/kamal/cli/secrets.rb +36 -0
  15. data/lib/kamal/cli/server.rb +0 -2
  16. data/lib/kamal/cli/templates/deploy.yml +0 -11
  17. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  18. data/lib/kamal/cli/templates/secrets +16 -0
  19. data/lib/kamal/cli.rb +1 -0
  20. data/lib/kamal/commander/specifics.rb +3 -3
  21. data/lib/kamal/commander.rb +17 -9
  22. data/lib/kamal/commands/accessory.rb +7 -7
  23. data/lib/kamal/commands/app/assets.rb +8 -8
  24. data/lib/kamal/commands/app/proxy.rb +16 -0
  25. data/lib/kamal/commands/app.rb +7 -15
  26. data/lib/kamal/commands/auditor.rb +6 -3
  27. data/lib/kamal/commands/base.rb +8 -0
  28. data/lib/kamal/commands/builder/base.rb +2 -6
  29. data/lib/kamal/commands/builder/hybrid.rb +1 -1
  30. data/lib/kamal/commands/builder/remote.rb +27 -4
  31. data/lib/kamal/commands/builder.rb +1 -1
  32. data/lib/kamal/commands/docker.rb +4 -0
  33. data/lib/kamal/commands/hook.rb +5 -2
  34. data/lib/kamal/commands/lock.rb +2 -6
  35. data/lib/kamal/commands/proxy.rb +77 -0
  36. data/lib/kamal/commands/prune.rb +1 -9
  37. data/lib/kamal/commands/server.rb +11 -1
  38. data/lib/kamal/configuration/accessory.rb +14 -2
  39. data/lib/kamal/configuration/builder.rb +9 -3
  40. data/lib/kamal/configuration/docs/builder.yml +20 -10
  41. data/lib/kamal/configuration/docs/configuration.yml +16 -16
  42. data/lib/kamal/configuration/docs/env.yml +10 -11
  43. data/lib/kamal/configuration/docs/proxy.yml +100 -0
  44. data/lib/kamal/configuration/docs/registry.yml +4 -2
  45. data/lib/kamal/configuration/docs/role.yml +3 -5
  46. data/lib/kamal/configuration/env/tag.rb +4 -3
  47. data/lib/kamal/configuration/env.rb +10 -17
  48. data/lib/kamal/configuration/proxy.rb +66 -0
  49. data/lib/kamal/configuration/registry.rb +3 -2
  50. data/lib/kamal/configuration/role.rb +63 -94
  51. data/lib/kamal/configuration/validator/builder.rb +2 -0
  52. data/lib/kamal/configuration/validator/proxy.rb +11 -0
  53. data/lib/kamal/configuration/validator.rb +3 -1
  54. data/lib/kamal/configuration.rb +90 -33
  55. data/lib/kamal/env_file.rb +4 -0
  56. data/lib/kamal/secrets/adapters/base.rb +18 -0
  57. data/lib/kamal/secrets/adapters/bitwarden.rb +64 -0
  58. data/lib/kamal/secrets/adapters/last_pass.rb +30 -0
  59. data/lib/kamal/secrets/adapters/one_password.rb +61 -0
  60. data/lib/kamal/secrets/adapters/test.rb +10 -0
  61. data/lib/kamal/secrets/adapters.rb +14 -0
  62. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +32 -0
  63. data/lib/kamal/secrets.rb +37 -0
  64. data/lib/kamal/sshkit_with_ext.rb +1 -0
  65. data/lib/kamal/utils.rb +12 -0
  66. data/lib/kamal/version.rb +1 -1
  67. data/lib/kamal.rb +3 -1
  68. metadata +23 -16
  69. data/lib/kamal/cli/env.rb +0 -54
  70. data/lib/kamal/cli/templates/sample_hooks/post-traefik-reboot.sample +0 -3
  71. data/lib/kamal/cli/templates/template.env +0 -2
  72. data/lib/kamal/cli/traefik.rb +0 -122
  73. data/lib/kamal/commands/app/cord.rb +0 -22
  74. data/lib/kamal/commands/traefik.rb +0 -85
  75. data/lib/kamal/configuration/docs/healthcheck.yml +0 -59
  76. data/lib/kamal/configuration/docs/traefik.yml +0 -62
  77. data/lib/kamal/configuration/healthcheck.rb +0 -63
  78. data/lib/kamal/configuration/traefik.rb +0 -60
  79. /data/lib/kamal/cli/templates/sample_hooks/{pre-traefik-reboot.sample → pre-proxy-reboot.sample} +0 -0
@@ -0,0 +1,100 @@
1
+ # Proxy
2
+ #
3
+ # Kamal uses [kamal-proxy](https://github.com/basecamp/kamal-proxy) to provide
4
+ # gapless deployments. It runs on ports 80 and 443 and forwards requests to the
5
+ # application container.
6
+ #
7
+ # The proxy is configured in the root configuration under `proxy`. These are
8
+ # options that are set when deploying the application, not when booting the proxy
9
+ #
10
+ # They are application specific, so are not shared when multiple applications
11
+ # run on the same proxy.
12
+ #
13
+ # The proxy is enabled by default on the primary role, but can be disabled by
14
+ # setting `proxy: false`.
15
+ #
16
+ # It is disabled by default on all other roles, but can be enabled by setting
17
+ # `proxy: true`, or providing a proxy configuration.
18
+ proxy:
19
+
20
+ # Host
21
+ #
22
+ # The hosts that will be used to serve the app. The proxy will only route requests
23
+ # to this host to your app.
24
+ #
25
+ # If no hosts are set, then all requests will be forwarded, except for matching
26
+ # requests for other apps deployed on that server that do have a host set.
27
+ host: foo.example.com
28
+
29
+ # App port
30
+ #
31
+ # The port the application container is exposed on
32
+ #
33
+ # Defaults to 80
34
+ app_port: 3000
35
+
36
+ # SSL
37
+ #
38
+ # kamal-proxy can provide automatic HTTPS for your application via Let's Encrypt.
39
+ #
40
+ # This requires that we are deploying to a one server and the host option is set.
41
+ # The host value must point to the server we are deploying to and port 443 must be
42
+ # open for the Let's Encrypt challenge to succeed.
43
+ #
44
+ # Defaults to false
45
+ ssl: true
46
+
47
+ # Response timeout
48
+ #
49
+ # How long to wait for requests to complete before timing out, defaults to 30 seconds
50
+ response_timeout: 10s
51
+
52
+ # Healthcheck
53
+ #
54
+ # When deploying, the proxy will by default hit /up once every second until we hit
55
+ # the deploy timeout, with a 5 second timeout for each request.
56
+ #
57
+ # Once the app is up, the proxy will stop hitting the healthcheck endpoint.
58
+ healthcheck:
59
+ interval: 3
60
+ path: /health
61
+ timeout: 3
62
+
63
+ # Buffering
64
+ #
65
+ # Whether to buffer request and response bodies in the proxy
66
+ #
67
+ # By default buffering is enabled with a max request body size of 1GB and no limit
68
+ # for response size.
69
+ #
70
+ # You can also set the memory limit for buffering, which defaults to 1MB, anything
71
+ # larger than that is written to disk.
72
+ buffering:
73
+ requests: true
74
+ responses: true
75
+ max_request_body: 40_000_000
76
+ max_response_body: 0
77
+ memory: 2_000_000
78
+
79
+ # Logging
80
+ #
81
+ # Configure request logging for the proxy
82
+ # You can specify request and response headers to log.
83
+ # By default, Cache-Control, Last-Modified and User-Agent request headers are logged
84
+ logging:
85
+ request_headers:
86
+ - Cache-Control
87
+ - X-Forwarded-Proto
88
+ response_headers:
89
+ - X-Request-ID
90
+ - X-Request-Start
91
+
92
+ # Forward headers
93
+ #
94
+ # Whether to forward the X-Forwarded-For and X-Forwarded-Proto headers (defaults to false)
95
+ #
96
+ # If you are behind a trusted proxy, you can set this to true to forward the headers.
97
+ #
98
+ # By default kamal-proxy will not forward the headers the ssl option is set to true, and
99
+ # will forward them if it is set to false.
100
+ forward_headers: true
@@ -27,11 +27,13 @@ registry:
27
27
  # and [set up roles and permissions](https://cloud.google.com/artifact-registry/docs/access-control#permissions).
28
28
  # Normally, assigning a roles/artifactregistry.writer role should be sufficient.
29
29
  #
30
- # Once the service account is ready, you need to generate and download a JSON key, base64 encode it and add to .env:
30
+ # Once the service account is ready, you need to generate and download a JSON key and base64 encode it:
31
31
  #
32
32
  # ```shell
33
- # echo "KAMAL_REGISTRY_PASSWORD=$(base64 -i /path/to/key.json)" | tr -d "\\n" >> .env
33
+ # base64 -i /path/to/key.json | tr -d "\\n")
34
34
  # ```
35
+ # You'll then need to set the KAMAL_REGISTRY_PASSWORD secret to that value.
36
+ #
35
37
  # Use the env variable as password along with _json_key_base64 as username.
36
38
  # Here’s the final configuration:
37
39
 
@@ -26,7 +26,7 @@ servers:
26
26
  #
27
27
  # When there are other options to set, the list of hosts goes under the `hosts` key
28
28
  #
29
- # By default only the primary role uses Traefik, but you can set `traefik` to change
29
+ # By default only the primary role uses a proxy, but you can set `proxy` to change
30
30
  # it.
31
31
  #
32
32
  # You can also set a custom cmd to run in the container, and overwrite other settings
@@ -35,18 +35,16 @@ servers:
35
35
  hosts:
36
36
  - 172.1.0.3
37
37
  - 172.1.0.4: experiment1
38
- traefik: true
39
38
  cmd: "bin/jobs"
40
39
  options:
41
40
  memory: 2g
42
41
  cpus: 4
43
- healthcheck:
44
- ...
45
42
  logging:
46
43
  ...
44
+ proxy:
45
+ ...
47
46
  labels:
48
47
  my-label: workers
49
48
  env:
50
49
  ...
51
50
  asset_path: /public
52
-
@@ -1,12 +1,13 @@
1
1
  class Kamal::Configuration::Env::Tag
2
- attr_reader :name, :config
2
+ attr_reader :name, :config, :secrets
3
3
 
4
- def initialize(name, config:)
4
+ def initialize(name, config:, secrets:)
5
5
  @name = name
6
6
  @config = config
7
+ @secrets = secrets
7
8
  end
8
9
 
9
10
  def env
10
- Kamal::Configuration::Env.new(config: config)
11
+ Kamal::Configuration::Env.new(config: config, secrets: secrets)
11
12
  end
12
13
  end
@@ -1,36 +1,29 @@
1
1
  class Kamal::Configuration::Env
2
2
  include Kamal::Configuration::Validation
3
3
 
4
- attr_reader :secrets_keys, :clear, :secrets_file, :context
4
+ attr_reader :context, :secrets
5
+ attr_reader :clear, :secret_keys
5
6
  delegate :argumentize, to: Kamal::Utils
6
7
 
7
- def initialize(config:, secrets_file: nil, context: "env")
8
+ def initialize(config:, secrets:, context: "env")
8
9
  @clear = config.fetch("clear", config.key?("secret") || config.key?("tags") ? {} : config)
9
- @secrets_keys = config.fetch("secret", [])
10
- @secrets_file = secrets_file
10
+ @secrets = secrets
11
+ @secret_keys = config.fetch("secret", [])
11
12
  @context = context
12
13
  validate! config, context: context, with: Kamal::Configuration::Validator::Env
13
14
  end
14
15
 
15
- def args
16
- [ "--env-file", secrets_file, *argumentize("--env", clear) ]
16
+ def clear_args
17
+ argumentize("--env", clear)
17
18
  end
18
19
 
19
20
  def secrets_io
20
- StringIO.new(Kamal::EnvFile.new(secrets).to_s)
21
- end
22
-
23
- def secrets
24
- @secrets ||= secrets_keys.to_h { |key| [ key, ENV.fetch(key) ] }
25
- end
26
-
27
- def secrets_directory
28
- File.dirname(secrets_file)
21
+ Kamal::EnvFile.new(secret_keys.to_h { |key| [ key, secrets[key] ] }).to_io
29
22
  end
30
23
 
31
24
  def merge(other)
32
25
  self.class.new \
33
- config: { "clear" => clear.merge(other.clear), "secret" => secrets_keys | other.secrets_keys },
34
- secrets_file: secrets_file || other.secrets_file
26
+ config: { "clear" => clear.merge(other.clear), "secret" => secret_keys | other.secret_keys },
27
+ secrets: secrets
35
28
  end
36
29
  end
@@ -0,0 +1,66 @@
1
+ class Kamal::Configuration::Proxy
2
+ include Kamal::Configuration::Validation
3
+
4
+ DEFAULT_LOG_REQUEST_HEADERS = [ "Cache-Control", "Last-Modified", "User-Agent" ]
5
+ CONTAINER_NAME = "kamal-proxy"
6
+
7
+ delegate :argumentize, :optionize, to: Kamal::Utils
8
+
9
+ attr_reader :config, :proxy_config
10
+
11
+ def initialize(config:, proxy_config:, context: "proxy")
12
+ @config = config
13
+ @proxy_config = proxy_config
14
+ validate! @proxy_config, with: Kamal::Configuration::Validator::Proxy, context: context
15
+ end
16
+
17
+ def app_port
18
+ proxy_config.fetch("app_port", 80)
19
+ end
20
+
21
+ def ssl?
22
+ proxy_config.fetch("ssl", false)
23
+ end
24
+
25
+ def host
26
+ proxy_config["host"]
27
+ end
28
+
29
+ def deploy_options
30
+ {
31
+ host: proxy_config["host"],
32
+ tls: proxy_config["ssl"],
33
+ "deploy-timeout": seconds_duration(config.deploy_timeout),
34
+ "drain-timeout": seconds_duration(config.drain_timeout),
35
+ "health-check-interval": seconds_duration(proxy_config.dig("healthcheck", "interval")),
36
+ "health-check-timeout": seconds_duration(proxy_config.dig("healthcheck", "timeout")),
37
+ "health-check-path": proxy_config.dig("healthcheck", "path"),
38
+ "target-timeout": seconds_duration(proxy_config["response_timeout"]),
39
+ "buffer-requests": proxy_config.fetch("buffering", { "requests": true }).fetch("requests", true),
40
+ "buffer-responses": proxy_config.fetch("buffering", { "responses": true }).fetch("responses", true),
41
+ "buffer-memory": proxy_config.dig("buffering", "memory"),
42
+ "max-request-body": proxy_config.dig("buffering", "max_request_body"),
43
+ "max-response-body": proxy_config.dig("buffering", "max_response_body"),
44
+ "forward-headers": proxy_config.dig("forward_headers"),
45
+ "log-request-header": proxy_config.dig("logging", "request_headers") || DEFAULT_LOG_REQUEST_HEADERS,
46
+ "log-response-header": proxy_config.dig("logging", "response_headers")
47
+ }.compact
48
+ end
49
+
50
+ def deploy_command_args(target:)
51
+ optionize ({ target: "#{target}:#{app_port}" }).merge(deploy_options)
52
+ end
53
+
54
+ def remove_command_args(target:)
55
+ optionize({ target: "#{target}:#{app_port}" })
56
+ end
57
+
58
+ def merge(other)
59
+ self.class.new config: config, proxy_config: proxy_config.deep_merge(other.proxy_config)
60
+ end
61
+
62
+ private
63
+ def seconds_duration(value)
64
+ value ? "#{value}s" : nil
65
+ end
66
+ end
@@ -1,10 +1,11 @@
1
1
  class Kamal::Configuration::Registry
2
2
  include Kamal::Configuration::Validation
3
3
 
4
- attr_reader :registry_config
4
+ attr_reader :registry_config, :secrets
5
5
 
6
6
  def initialize(config:)
7
7
  @registry_config = config.raw_config.registry || {}
8
+ @secrets = config.secrets
8
9
  validate! registry_config, with: Kamal::Configuration::Validator::Registry
9
10
  end
10
11
 
@@ -23,7 +24,7 @@ class Kamal::Configuration::Registry
23
24
  private
24
25
  def lookup(key)
25
26
  if registry_config[key].is_a?(Array)
26
- ENV.fetch(registry_config[key].first).dup
27
+ secrets[registry_config[key].first]
27
28
  else
28
29
  registry_config[key]
29
30
  end
@@ -1,10 +1,9 @@
1
1
  class Kamal::Configuration::Role
2
2
  include Kamal::Configuration::Validation
3
3
 
4
- CORD_FILE = "cord"
5
4
  delegate :argumentize, :optionize, to: Kamal::Utils
6
5
 
7
- attr_reader :name, :config, :specialized_env, :specialized_logging, :specialized_healthcheck
6
+ attr_reader :name, :config, :specialized_env, :specialized_logging, :specialized_proxy
8
7
 
9
8
  alias to_s name
10
9
 
@@ -18,16 +17,14 @@ class Kamal::Configuration::Role
18
17
 
19
18
  @specialized_env = Kamal::Configuration::Env.new \
20
19
  config: specializations.fetch("env", {}),
21
- secrets_file: File.join(config.host_env_directory, "roles", "#{container_prefix}.env"),
20
+ secrets: config.secrets,
22
21
  context: "servers/#{name}/env"
23
22
 
24
23
  @specialized_logging = Kamal::Configuration::Logging.new \
25
24
  logging_config: specializations.fetch("logging", {}),
26
25
  context: "servers/#{name}/logging"
27
26
 
28
- @specialized_healthcheck = Kamal::Configuration::Healthcheck.new \
29
- healthcheck_config: specializations.fetch("healthcheck", {}),
30
- context: "servers/#{name}/healthcheck"
27
+ initialize_specialized_proxy
31
28
  end
32
29
 
33
30
  def primary_host
@@ -55,7 +52,7 @@ class Kamal::Configuration::Role
55
52
  end
56
53
 
57
54
  def labels
58
- default_labels.merge(traefik_labels).merge(custom_labels)
55
+ default_labels.merge(custom_labels)
59
56
  end
60
57
 
61
58
  def label_args
@@ -70,87 +67,53 @@ class Kamal::Configuration::Role
70
67
  @logging ||= config.logging.merge(specialized_logging)
71
68
  end
72
69
 
73
-
74
- def env(host)
75
- @envs ||= {}
76
- @envs[host] ||= [ config.env, specialized_env, *env_tags(host).map(&:env) ].reduce(:merge)
70
+ def proxy
71
+ @proxy ||= config.proxy.merge(specialized_proxy) if running_proxy?
77
72
  end
78
73
 
79
- def env_args(host)
80
- env(host).args
74
+ def running_proxy?
75
+ @running_proxy
81
76
  end
82
77
 
83
- def asset_volume_args
84
- asset_volume&.docker_args
78
+ def ssl?
79
+ running_proxy? && proxy.ssl?
85
80
  end
86
81
 
82
+ def stop_args
83
+ # When deploying with the proxy, kamal-proxy will drain request before returning so we don't need to wait.
84
+ timeout = running_proxy? ? nil : config.drain_timeout
87
85
 
88
- def health_check_args(cord: true)
89
- if running_traefik? || healthcheck.set_port_or_path?
90
- if cord && uses_cord?
91
- optionize({ "health-cmd" => health_check_cmd_with_cord, "health-interval" => healthcheck.interval })
92
- .concat(cord_volume.docker_args)
93
- else
94
- optionize({ "health-cmd" => healthcheck.cmd, "health-interval" => healthcheck.interval })
95
- end
96
- else
97
- []
98
- end
86
+ [ *argumentize("-t", timeout) ]
99
87
  end
100
88
 
101
- def healthcheck
102
- @healthcheck ||=
103
- if running_traefik?
104
- config.healthcheck.merge(specialized_healthcheck)
105
- else
106
- specialized_healthcheck
107
- end
108
- end
109
-
110
- def health_check_cmd_with_cord
111
- "(#{healthcheck.cmd}) && (stat #{cord_container_file} > /dev/null || exit 1)"
112
- end
113
-
114
-
115
- def running_traefik?
116
- if specializations["traefik"].nil?
117
- primary?
118
- else
119
- specializations["traefik"]
120
- end
89
+ def env(host)
90
+ @envs ||= {}
91
+ @envs[host] ||= [ config.env, specialized_env, *env_tags(host).map(&:env) ].reduce(:merge)
121
92
  end
122
93
 
123
- def primary?
124
- self == @config.primary_role
94
+ def env_args(host)
95
+ [ *env(host).clear_args, *argumentize("--env-file", secrets_path) ]
125
96
  end
126
97
 
127
-
128
- def uses_cord?
129
- running_traefik? && cord_volume && healthcheck.cmd.present?
98
+ def env_directory
99
+ File.join(config.env_directory, "roles")
130
100
  end
131
101
 
132
- def cord_host_directory
133
- File.join config.run_directory_as_docker_volume, "cords", [ container_prefix, config.run_id ].join("-")
102
+ def secrets_io(host)
103
+ env(host).secrets_io
134
104
  end
135
105
 
136
- def cord_volume
137
- if (cord = healthcheck.cord)
138
- @cord_volume ||= Kamal::Configuration::Volume.new \
139
- host_path: File.join(config.run_directory, "cords", [ container_prefix, config.run_id ].join("-")),
140
- container_path: cord
141
- end
106
+ def secrets_path
107
+ File.join(config.env_directory, "roles", "#{name}.env")
142
108
  end
143
109
 
144
- def cord_host_file
145
- File.join cord_volume.host_path, CORD_FILE
110
+ def asset_volume_args
111
+ asset_volume&.docker_args
146
112
  end
147
113
 
148
- def cord_container_directory
149
- health_check_options.fetch("cord", nil)
150
- end
151
114
 
152
- def cord_container_file
153
- File.join cord_volume.container_path, CORD_FILE
115
+ def primary?
116
+ name == @config.primary_role_name
154
117
  end
155
118
 
156
119
 
@@ -168,25 +131,52 @@ class Kamal::Configuration::Role
168
131
  end
169
132
 
170
133
  def assets?
171
- asset_path.present? && running_traefik?
134
+ asset_path.present? && running_proxy?
172
135
  end
173
136
 
174
- def asset_volume(version = nil)
137
+ def asset_volume(version = config.version)
175
138
  if assets?
176
139
  Kamal::Configuration::Volume.new \
177
- host_path: asset_volume_path(version), container_path: asset_path
140
+ host_path: asset_volume_directory(version), container_path: asset_path
178
141
  end
179
142
  end
180
143
 
181
- def asset_extracted_path(version = nil)
182
- File.join config.run_directory, "assets", "extracted", container_name(version)
144
+ def asset_extracted_directory(version = config.version)
145
+ File.join config.assets_directory, "extracted", [ name, version ].join("-")
183
146
  end
184
147
 
185
- def asset_volume_path(version = nil)
186
- File.join config.run_directory, "assets", "volumes", container_name(version)
148
+ def asset_volume_directory(version = config.version)
149
+ File.join config.assets_directory, "volumes", [ name, version ].join("-")
150
+ end
151
+
152
+ def ensure_one_host_for_ssl
153
+ if running_proxy? && proxy.ssl? && hosts.size > 1
154
+ raise Kamal::ConfigurationError, "SSL is only supported on a single server, found #{hosts.size} servers for role #{name}"
155
+ end
187
156
  end
188
157
 
189
158
  private
159
+ def initialize_specialized_proxy
160
+ proxy_specializations = specializations["proxy"]
161
+
162
+ if primary?
163
+ # only false means no proxy for non-primary roles
164
+ @running_proxy = proxy_specializations != false
165
+ else
166
+ # false and nil both mean no proxy for non-primary roles
167
+ @running_proxy = !!proxy_specializations
168
+ end
169
+
170
+ if running_proxy?
171
+ proxy_config = proxy_specializations == true || proxy_specializations.nil? ? {} : proxy_specializations
172
+
173
+ @specialized_proxy = Kamal::Configuration::Proxy.new \
174
+ config: config,
175
+ proxy_config: proxy_config,
176
+ context: "servers/#{name}/proxy"
177
+ end
178
+ end
179
+
190
180
  def tagged_hosts
191
181
  {}.tap do |tagged_hosts|
192
182
  extract_hosts_from_config.map do |host_config|
@@ -221,27 +211,6 @@ class Kamal::Configuration::Role
221
211
  end
222
212
  end
223
213
 
224
- def traefik_labels
225
- if running_traefik?
226
- {
227
- # Setting a service property ensures that the generated service name will be consistent between versions
228
- "traefik.http.services.#{traefik_service}.loadbalancer.server.scheme" => "http",
229
-
230
- "traefik.http.routers.#{traefik_service}.rule" => "PathPrefix(`/`)",
231
- "traefik.http.routers.#{traefik_service}.priority" => "2",
232
- "traefik.http.middlewares.#{traefik_service}-retry.retry.attempts" => "5",
233
- "traefik.http.middlewares.#{traefik_service}-retry.retry.initialinterval" => "500ms",
234
- "traefik.http.routers.#{traefik_service}.middlewares" => "#{traefik_service}-retry@docker"
235
- }
236
- else
237
- {}
238
- end
239
- end
240
-
241
- def traefik_service
242
- container_prefix
243
- end
244
-
245
214
  def custom_labels
246
215
  Hash.new.tap do |labels|
247
216
  labels.merge!(config.labels) if config.labels.present?
@@ -7,5 +7,7 @@ class Kamal::Configuration::Validator::Builder < Kamal::Configuration::Validator
7
7
  end
8
8
 
9
9
  error "Builder arch not set" unless config["arch"].present?
10
+
11
+ error "Cannot disable local builds, no remote is set" if config["local"] == false && config["remote"].blank?
10
12
  end
11
13
  end
@@ -0,0 +1,11 @@
1
+ class Kamal::Configuration::Validator::Proxy < Kamal::Configuration::Validator
2
+ def validate!
3
+ unless config.nil?
4
+ super
5
+
6
+ if config["host"].blank? && config["ssl"]
7
+ error "Must set a host to enable automatic SSL"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -24,7 +24,9 @@ class Kamal::Configuration::Validator
24
24
  example_value = example[key]
25
25
 
26
26
  if example_value == "..."
27
- validate_type! value, *(Array if key == :servers), Hash
27
+ unless key.to_s == "proxy" && boolean?(value.class)
28
+ validate_type! value, *(Array if key == :servers), Hash
29
+ end
28
30
  elsif key == "hosts"
29
31
  validate_servers! value
30
32
  elsif example_value.is_a?(Array)