kamal 2.0.0.alpha → 2.0.0.beta1

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 (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)