kamal 2.7.0 → 2.11.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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/kamal/cli/accessory.rb +27 -7
- data/lib/kamal/cli/alias/command.rb +2 -2
- data/lib/kamal/cli/app/boot.rb +1 -1
- data/lib/kamal/cli/app.rb +74 -115
- data/lib/kamal/cli/base.rb +19 -6
- data/lib/kamal/cli/build/clone.rb +0 -2
- data/lib/kamal/cli/build/port_forwarding.rb +66 -0
- data/lib/kamal/cli/build.rb +70 -35
- data/lib/kamal/cli/healthcheck/poller.rb +1 -1
- data/lib/kamal/cli/main.rb +9 -3
- data/lib/kamal/cli/proxy.rb +42 -35
- data/lib/kamal/cli/registry.rb +37 -7
- data/lib/kamal/cli/secrets.rb +2 -1
- data/lib/kamal/cli/server.rb +12 -1
- data/lib/kamal/cli/templates/deploy.yml +4 -3
- data/lib/kamal/cli/templates/secrets +2 -1
- data/lib/kamal/commander.rb +21 -19
- data/lib/kamal/commands/accessory.rb +5 -0
- data/lib/kamal/commands/app/execution.rb +7 -1
- data/lib/kamal/commands/app.rb +1 -0
- data/lib/kamal/commands/base.rb +15 -2
- data/lib/kamal/commands/builder/base.rb +20 -1
- data/lib/kamal/commands/builder/hybrid.rb +3 -3
- data/lib/kamal/commands/builder/local.rb +8 -2
- data/lib/kamal/commands/builder/pack.rb +5 -5
- data/lib/kamal/commands/builder/remote.rb +15 -3
- data/lib/kamal/commands/builder.rb +8 -2
- data/lib/kamal/commands/docker.rb +17 -1
- data/lib/kamal/commands/proxy.rb +22 -3
- data/lib/kamal/commands/registry.rb +22 -0
- data/lib/kamal/configuration/accessory.rb +56 -25
- data/lib/kamal/configuration/boot.rb +4 -0
- data/lib/kamal/configuration/builder.rb +10 -3
- data/lib/kamal/configuration/docs/accessory.yml +37 -5
- data/lib/kamal/configuration/docs/alias.yml +3 -0
- data/lib/kamal/configuration/docs/boot.yml +12 -10
- data/lib/kamal/configuration/docs/configuration.yml +30 -1
- data/lib/kamal/configuration/docs/proxy.yml +48 -16
- data/lib/kamal/configuration/docs/registry.yml +12 -4
- data/lib/kamal/configuration/docs/ssh.yml +7 -4
- data/lib/kamal/configuration/docs/sshkit.yml +8 -0
- data/lib/kamal/configuration/env.rb +7 -3
- data/lib/kamal/configuration/proxy/boot.rb +4 -9
- data/lib/kamal/configuration/proxy/run.rb +143 -0
- data/lib/kamal/configuration/proxy.rb +7 -3
- data/lib/kamal/configuration/registry.rb +8 -0
- data/lib/kamal/configuration/role.rb +15 -3
- data/lib/kamal/configuration/ssh.rb +18 -3
- data/lib/kamal/configuration/sshkit.rb +4 -0
- data/lib/kamal/configuration/validator/proxy.rb +20 -0
- data/lib/kamal/configuration/validator/registry.rb +5 -3
- data/lib/kamal/configuration/validator.rb +52 -4
- data/lib/kamal/configuration/volume.rb +11 -4
- data/lib/kamal/configuration.rb +89 -5
- data/lib/kamal/secrets/adapters/one_password.rb +1 -1
- data/lib/kamal/secrets/adapters/passbolt.rb +1 -2
- data/lib/kamal/secrets/adapters/test.rb +3 -1
- data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +15 -1
- data/lib/kamal/secrets.rb +17 -6
- data/lib/kamal/sshkit_with_ext.rb +135 -10
- data/lib/kamal/utils.rb +3 -3
- data/lib/kamal/version.rb +1 -1
- data/lib/kamal.rb +1 -0
- metadata +18 -2
|
@@ -73,7 +73,10 @@ env:
|
|
|
73
73
|
# This requires that file names change when the contents change
|
|
74
74
|
# (e.g., by including a hash of the contents in the name).
|
|
75
75
|
#
|
|
76
|
-
# To configure this, set the path to the assets
|
|
76
|
+
# To configure this, set the path to the assets.
|
|
77
|
+
#
|
|
78
|
+
# You can also specify mount options after a colon, such as `ro` for read-only
|
|
79
|
+
# or `z`/`Z` for SELinux labels
|
|
77
80
|
asset_path: /path/to/assets
|
|
78
81
|
|
|
79
82
|
# Hooks path
|
|
@@ -82,6 +85,32 @@ asset_path: /path/to/assets
|
|
|
82
85
|
# See https://kamal-deploy.org/docs/hooks for more information:
|
|
83
86
|
hooks_path: /user_home/kamal/hooks
|
|
84
87
|
|
|
88
|
+
# Hook output
|
|
89
|
+
#
|
|
90
|
+
# Hook output visibility. Can be set globally or per-hook.
|
|
91
|
+
# CLI flags (`-v`, `-q`) override these settings.
|
|
92
|
+
#
|
|
93
|
+
# - `:quiet` - hook output is hidden
|
|
94
|
+
# - `:verbose` - hook output is shown
|
|
95
|
+
#
|
|
96
|
+
# With no setting, hook output follows CLI verbosity flags.
|
|
97
|
+
#
|
|
98
|
+
# Note: Failed hooks always show output in the error message regardless of setting.
|
|
99
|
+
#
|
|
100
|
+
# Global setting for all hooks:
|
|
101
|
+
hooks_output: :verbose
|
|
102
|
+
|
|
103
|
+
# Or per-hook settings:
|
|
104
|
+
hooks_output:
|
|
105
|
+
pre-deploy: :verbose
|
|
106
|
+
pre-build: :quiet
|
|
107
|
+
|
|
108
|
+
# Secrets path
|
|
109
|
+
#
|
|
110
|
+
# Path to secrets, defaults to `.kamal/secrets`.
|
|
111
|
+
# Kamal will look for `<secrets_path>-common` and `<secrets_path>` (or `<secrets_path>.<destination>` when using destinations):
|
|
112
|
+
secrets_path: /user_home/kamal/secrets
|
|
113
|
+
|
|
85
114
|
# Error pages
|
|
86
115
|
#
|
|
87
116
|
# A directory relative to the app root to find error pages for the proxy to serve.
|
|
@@ -45,27 +45,23 @@ proxy:
|
|
|
45
45
|
# unless you explicitly set `forward_headers: true`
|
|
46
46
|
#
|
|
47
47
|
# Defaults to `false`:
|
|
48
|
-
ssl:
|
|
48
|
+
ssl: true
|
|
49
49
|
|
|
50
50
|
# Custom SSL certificate
|
|
51
51
|
#
|
|
52
52
|
# In some cases, using Let's Encrypt for automatic certificate management is not an
|
|
53
|
-
# option, for example if you are running from
|
|
54
|
-
# have SSL certificates issued by a different Certificate Authority (CA).
|
|
55
|
-
# Kamal supports loading custom SSL certificates
|
|
56
|
-
# directly from secrets.
|
|
57
|
-
#
|
|
58
|
-
# Examples:
|
|
59
|
-
# ssl: true # Enable SSL with Let's Encrypt
|
|
60
|
-
# ssl: false # Disable SSL
|
|
61
|
-
# ssl: # Enable custom SSL
|
|
62
|
-
# certificate_pem: CERTIFICATE_PEM
|
|
63
|
-
# private_key_pem: PRIVATE_KEY_PEM
|
|
53
|
+
# option, for example if you are running from more than one host.
|
|
64
54
|
#
|
|
55
|
+
# Or you may already have SSL certificates issued by a different Certificate Authority (CA).
|
|
56
|
+
#
|
|
57
|
+
# Kamal supports loading custom SSL certificates directly from secrets. You should
|
|
58
|
+
# pass a hash mapping the `certificate_pem` and `private_key_pem` to the secret names.
|
|
59
|
+
ssl:
|
|
60
|
+
certificate_pem: CERTIFICATE_PEM
|
|
61
|
+
private_key_pem: PRIVATE_KEY_PEM
|
|
65
62
|
# ### Notes
|
|
66
|
-
# - If the certificate or key is missing or invalid,
|
|
67
|
-
# - Always handle SSL certificates and private keys securely. Avoid hard-coding them in
|
|
68
|
-
# - For automated certificate management, consider using the built-in Let's Encrypt integration instead.
|
|
63
|
+
# - If the certificate or key is missing or invalid, deployments will fail.
|
|
64
|
+
# - Always handle SSL certificates and private keys securely. Avoid hard-coding them in source control.
|
|
69
65
|
|
|
70
66
|
# SSL redirect
|
|
71
67
|
#
|
|
@@ -93,9 +89,21 @@ proxy:
|
|
|
93
89
|
#
|
|
94
90
|
# For applications that split their traffic to different services based on the request path,
|
|
95
91
|
# you can use path-based routing to mount services under different path prefixes.
|
|
96
|
-
path_prefix: '/api'
|
|
92
|
+
# Usage sample: path_prefix: '/api'
|
|
93
|
+
#
|
|
94
|
+
# You can also specify multiple paths in two ways.
|
|
95
|
+
#
|
|
96
|
+
# When using path_prefix you can supply multiple routes separated by commas.
|
|
97
|
+
path_prefix: "/api,/oauth_callback"
|
|
98
|
+
# You can also specify paths as a list of paths, the configuration will be
|
|
99
|
+
# rolled together into a comma separated string.
|
|
100
|
+
path_prefixes:
|
|
101
|
+
- "/api"
|
|
102
|
+
- "/oauth_callback"
|
|
97
103
|
# By default, the path prefix will be stripped from the request before it is forwarded upstream.
|
|
104
|
+
#
|
|
98
105
|
# So in the example above, a request to /api/users/123 will be forwarded to web-1 as /users/123.
|
|
106
|
+
#
|
|
99
107
|
# To instead forward the request with the original path (including the prefix),
|
|
100
108
|
# specify --strip-path-prefix=false
|
|
101
109
|
strip_path_prefix: false
|
|
@@ -140,6 +148,30 @@ proxy:
|
|
|
140
148
|
- X-Request-ID
|
|
141
149
|
- X-Request-Start
|
|
142
150
|
|
|
151
|
+
# Run configuration
|
|
152
|
+
#
|
|
153
|
+
# These options are used when booting the proxy container.
|
|
154
|
+
#
|
|
155
|
+
run:
|
|
156
|
+
http_port: 8080 # HTTP port to use (default 80)
|
|
157
|
+
https_port: 8443 # HTTPS port to use (default 443)
|
|
158
|
+
metrics_port: 9090 # Port for Prometheus metrics
|
|
159
|
+
debug: true # Debug logging (default: false)
|
|
160
|
+
log_max_size: "30m" # Maximum log file size (default: "10m")
|
|
161
|
+
publish: false # Publish ports to the host (default: true)
|
|
162
|
+
bind_ips: # List of IPs to bind to when publishing ports
|
|
163
|
+
- 0.0.0.0
|
|
164
|
+
registry: registry:4443 # Container registry for the kamal-proxy image
|
|
165
|
+
# (defaults to Docker Hub)
|
|
166
|
+
repository: myrepo/kamal-proxy # Container repository for the kamal-proxy image
|
|
167
|
+
# (defaults to `basecamp/kamal-proxy`)
|
|
168
|
+
version: v0.8.0 # Version tag of the kamal-proxy image to use
|
|
169
|
+
options: # Additional options to pass to `docker run`
|
|
170
|
+
label:
|
|
171
|
+
- custom.label=kamal-proxy
|
|
172
|
+
memory: 512m
|
|
173
|
+
cpus: 0.5
|
|
174
|
+
|
|
143
175
|
# Enabling/disabling the proxy on roles
|
|
144
176
|
#
|
|
145
177
|
# The proxy is enabled by default on the primary role but can be disabled by
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
# Registry
|
|
2
2
|
#
|
|
3
3
|
# The default registry is Docker Hub, but you can change it using `registry/server`.
|
|
4
|
+
|
|
5
|
+
# Using a local container registry
|
|
6
|
+
#
|
|
7
|
+
# If the registry server starts with `localhost`, Kamal will start a local Docker registry
|
|
8
|
+
# on that port and push the app image to it.
|
|
9
|
+
registry:
|
|
10
|
+
server: localhost:5555
|
|
11
|
+
|
|
12
|
+
# Using Docker Hub as the container registry
|
|
4
13
|
#
|
|
5
14
|
# By default, Docker Hub creates public repositories. To avoid making your images public,
|
|
6
15
|
# set up a private repository before deploying, or change the default repository privacy
|
|
7
16
|
# settings to private in your [Docker Hub settings](https://hub.docker.com/repository-settings/default-privacy).
|
|
8
17
|
#
|
|
9
|
-
# A reference to a secret (in this case, `
|
|
18
|
+
# A reference to a secret (in this case, `KAMAL_REGISTRY_PASSWORD`) will look up the secret
|
|
10
19
|
# in the local environment:
|
|
11
20
|
registry:
|
|
12
|
-
server: registry.digitalocean.com
|
|
13
21
|
username:
|
|
14
|
-
-
|
|
22
|
+
- <your docker hub username>
|
|
15
23
|
password:
|
|
16
|
-
-
|
|
24
|
+
- KAMAL_REGISTRY_PASSWORD
|
|
17
25
|
|
|
18
26
|
# Using AWS ECR as the container registry
|
|
19
27
|
#
|
|
@@ -58,13 +58,16 @@ ssh:
|
|
|
58
58
|
|
|
59
59
|
# Key data
|
|
60
60
|
#
|
|
61
|
-
# An array of strings, with each element of the array being
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
# An array of strings, with each element of the array being a secret name.
|
|
62
|
+
key_data:
|
|
63
|
+
- SSH_PRIVATE_KEY
|
|
64
|
+
# You can also provide raw private key in PEM format, but this is deprecated.
|
|
65
|
+
key_data:
|
|
66
|
+
- "-----BEGIN OPENSSH PRIVATE KEY----- ..."
|
|
64
67
|
|
|
65
68
|
# Config
|
|
66
69
|
#
|
|
67
70
|
# Set to true to load the default OpenSSH config files (~/.ssh/config,
|
|
68
71
|
# /etc/ssh_config), to false ignore config files, or to a file path
|
|
69
72
|
# (or array of paths) to load specific configuration. Defaults to true.
|
|
70
|
-
config:
|
|
73
|
+
config: [ "~/.ssh/myconfig" ]
|
|
@@ -21,3 +21,11 @@ sshkit:
|
|
|
21
21
|
# Kamal sets a long idle timeout of 900 seconds on connections to try to avoid
|
|
22
22
|
# re-connection storms after an idle period, such as building an image or waiting for CI.
|
|
23
23
|
pool_idle_timeout: 300
|
|
24
|
+
|
|
25
|
+
# DNS retry settings
|
|
26
|
+
#
|
|
27
|
+
# Some resolvers (mDNSResponder, systemd-resolved, Tailscale) can drop lookups during
|
|
28
|
+
# bursts of concurrent SSH starts. Kamal will retry DNS failures automatically.
|
|
29
|
+
#
|
|
30
|
+
# Number of retries after the initial attempt. Set to 0 to disable.
|
|
31
|
+
dns_retries: 3
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
class Kamal::Configuration::Env
|
|
2
2
|
include Kamal::Configuration::Validation
|
|
3
3
|
|
|
4
|
-
attr_reader :context, :clear, :secret_keys
|
|
4
|
+
attr_reader :context, :clear, :secrets, :secret_keys
|
|
5
5
|
delegate :argumentize, to: Kamal::Utils
|
|
6
6
|
|
|
7
7
|
def initialize(config:, secrets:, context: "env")
|
|
@@ -23,12 +23,16 @@ class Kamal::Configuration::Env
|
|
|
23
23
|
def merge(other)
|
|
24
24
|
self.class.new \
|
|
25
25
|
config: { "clear" => clear.merge(other.clear), "secret" => secret_keys | other.secret_keys },
|
|
26
|
-
secrets:
|
|
26
|
+
secrets: secrets
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_h
|
|
30
|
+
clear.merge(aliased_secrets)
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
private
|
|
30
34
|
def aliased_secrets
|
|
31
|
-
secret_keys.to_h { |key| extract_alias(key) }.transform_values { |secret_key|
|
|
35
|
+
secret_keys.to_h { |key| extract_alias(key) }.transform_values { |secret_key| secrets[secret_key] }
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
def extract_alias(key)
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
class Kamal::Configuration::Proxy::Boot
|
|
2
|
-
MINIMUM_VERSION = "v0.9.0"
|
|
3
|
-
DEFAULT_HTTP_PORT = 80
|
|
4
|
-
DEFAULT_HTTPS_PORT = 443
|
|
5
|
-
DEFAULT_LOG_MAX_SIZE = "10m"
|
|
6
|
-
|
|
7
2
|
attr_reader :config
|
|
8
3
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
|
9
4
|
|
|
@@ -16,8 +11,8 @@ class Kamal::Configuration::Proxy::Boot
|
|
|
16
11
|
|
|
17
12
|
(bind_ips || [ nil ]).map do |bind_ip|
|
|
18
13
|
bind_ip = format_bind_ip(bind_ip)
|
|
19
|
-
publish_http = [ bind_ip, http_port, DEFAULT_HTTP_PORT ].compact.join(":")
|
|
20
|
-
publish_https = [ bind_ip, https_port, DEFAULT_HTTPS_PORT ].compact.join(":")
|
|
14
|
+
publish_http = [ bind_ip, http_port, Kamal::Configuration::Proxy::Run::DEFAULT_HTTP_PORT ].compact.join(":")
|
|
15
|
+
publish_https = [ bind_ip, https_port, Kamal::Configuration::Proxy::Run::DEFAULT_HTTPS_PORT ].compact.join(":")
|
|
21
16
|
|
|
22
17
|
argumentize "--publish", [ publish_http, publish_https ]
|
|
23
18
|
end.join(" ")
|
|
@@ -29,8 +24,8 @@ class Kamal::Configuration::Proxy::Boot
|
|
|
29
24
|
|
|
30
25
|
def default_boot_options
|
|
31
26
|
[
|
|
32
|
-
*(publish_args(DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT, nil)),
|
|
33
|
-
*(logging_args(DEFAULT_LOG_MAX_SIZE))
|
|
27
|
+
*(publish_args(Kamal::Configuration::Proxy::Run::DEFAULT_HTTP_PORT, Kamal::Configuration::Proxy::Run::DEFAULT_HTTPS_PORT, nil)),
|
|
28
|
+
*(logging_args(Kamal::Configuration::Proxy::Run::DEFAULT_LOG_MAX_SIZE))
|
|
34
29
|
]
|
|
35
30
|
end
|
|
36
31
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
class Kamal::Configuration::Proxy::Run
|
|
2
|
+
MINIMUM_VERSION = "v0.9.2"
|
|
3
|
+
DEFAULT_HTTP_PORT = 80
|
|
4
|
+
DEFAULT_HTTPS_PORT = 443
|
|
5
|
+
DEFAULT_LOG_MAX_SIZE = "10m"
|
|
6
|
+
|
|
7
|
+
attr_reader :config, :run_config
|
|
8
|
+
delegate :argumentize, :optionize, to: Kamal::Utils
|
|
9
|
+
|
|
10
|
+
def initialize(config, run_config:, context: "proxy/run")
|
|
11
|
+
@config = config
|
|
12
|
+
@run_config = run_config
|
|
13
|
+
@context = context
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def debug?
|
|
17
|
+
run_config.fetch("debug", nil)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def publish?
|
|
21
|
+
run_config.fetch("publish", true)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def http_port
|
|
25
|
+
run_config.fetch("http_port", DEFAULT_HTTP_PORT)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def https_port
|
|
29
|
+
run_config.fetch("https_port", DEFAULT_HTTPS_PORT)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def bind_ips
|
|
33
|
+
run_config.fetch("bind_ips", nil)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def publish_args
|
|
37
|
+
if publish?
|
|
38
|
+
(bind_ips || [ nil ]).map do |bind_ip|
|
|
39
|
+
bind_ip = format_bind_ip(bind_ip)
|
|
40
|
+
publish_http = [ bind_ip, http_port, DEFAULT_HTTP_PORT ].compact.join(":")
|
|
41
|
+
publish_https = [ bind_ip, https_port, DEFAULT_HTTPS_PORT ].compact.join(":")
|
|
42
|
+
|
|
43
|
+
argumentize "--publish", [ publish_http, publish_https ]
|
|
44
|
+
end.join(" ")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def log_max_size
|
|
49
|
+
run_config.fetch("log_max_size", DEFAULT_LOG_MAX_SIZE)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def logging_args
|
|
53
|
+
argumentize "--log-opt", "max-size=#{log_max_size}" if log_max_size.present?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def version
|
|
57
|
+
run_config.fetch("version", MINIMUM_VERSION)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def registry
|
|
61
|
+
run_config.fetch("registry", nil)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def repository
|
|
65
|
+
run_config.fetch("repository", "basecamp/kamal-proxy")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def image
|
|
69
|
+
"#{[ registry, repository ].compact.join("/")}:#{version}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def container_name
|
|
73
|
+
"kamal-proxy"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def options_args
|
|
77
|
+
if args = run_config["options"]
|
|
78
|
+
optionize args
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def run_command
|
|
83
|
+
[ "kamal-proxy", "run", *optionize(run_command_options) ].join(" ")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def metrics_port
|
|
87
|
+
run_config["metrics_port"]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def run_command_options
|
|
91
|
+
{ debug: debug? || nil, "metrics-port": metrics_port }.compact
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def docker_options_args
|
|
95
|
+
[
|
|
96
|
+
*apps_volume_args,
|
|
97
|
+
*publish_args,
|
|
98
|
+
*logging_args,
|
|
99
|
+
*("--expose=#{metrics_port}" if metrics_port.present?),
|
|
100
|
+
*options_args
|
|
101
|
+
].compact
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def host_directory
|
|
105
|
+
File.join config.run_directory, "proxy"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def apps_directory
|
|
109
|
+
File.join host_directory, "apps-config"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def apps_container_directory
|
|
113
|
+
"/home/kamal-proxy/.apps-config"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def apps_volume
|
|
117
|
+
Kamal::Configuration::Volume.new \
|
|
118
|
+
host_path: apps_directory,
|
|
119
|
+
container_path: apps_container_directory
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def apps_volume_args
|
|
123
|
+
[ apps_volume.docker_args ]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def app_directory
|
|
127
|
+
File.join apps_directory, config.service_and_destination
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def app_container_directory
|
|
131
|
+
File.join apps_container_directory, config.service_and_destination
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
def format_bind_ip(ip)
|
|
136
|
+
# Ensure IPv6 address inside square brackets - e.g. [::1]
|
|
137
|
+
if ip =~ Resolv::IPv6::Regex && ip !~ /\A\[.*\]\z/
|
|
138
|
+
"[#{ip}]"
|
|
139
|
+
else
|
|
140
|
+
ip
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -6,8 +6,7 @@ class Kamal::Configuration::Proxy
|
|
|
6
6
|
|
|
7
7
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
|
8
8
|
|
|
9
|
-
attr_reader :config, :proxy_config, :role_name, :secrets
|
|
10
|
-
|
|
9
|
+
attr_reader :config, :proxy_config, :role_name, :run, :secrets
|
|
11
10
|
def initialize(config:, proxy_config:, role_name: nil, secrets:, context: "proxy")
|
|
12
11
|
@config = config
|
|
13
12
|
@proxy_config = proxy_config
|
|
@@ -15,6 +14,7 @@ class Kamal::Configuration::Proxy
|
|
|
15
14
|
@role_name = role_name
|
|
16
15
|
@secrets = secrets
|
|
17
16
|
validate! @proxy_config, with: Kamal::Configuration::Validator::Proxy, context: context
|
|
17
|
+
@run = Kamal::Configuration::Proxy::Run.new(config, run_config: @proxy_config["run"], context: "#{context}/run") if @proxy_config && @proxy_config["run"].present?
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def app_port
|
|
@@ -63,6 +63,10 @@ class Kamal::Configuration::Proxy
|
|
|
63
63
|
tls_path(config.proxy_boot.tls_container_directory, "key.pem") if custom_ssl_certificate?
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
+
def path_prefixes
|
|
67
|
+
proxy_config["path_prefixes"] || proxy_config["path_prefix"]&.split(",") || []
|
|
68
|
+
end
|
|
69
|
+
|
|
66
70
|
def deploy_options
|
|
67
71
|
{
|
|
68
72
|
host: hosts,
|
|
@@ -80,7 +84,7 @@ class Kamal::Configuration::Proxy
|
|
|
80
84
|
"buffer-memory": proxy_config.dig("buffering", "memory"),
|
|
81
85
|
"max-request-body": proxy_config.dig("buffering", "max_request_body"),
|
|
82
86
|
"max-response-body": proxy_config.dig("buffering", "max_response_body"),
|
|
83
|
-
"path-prefix":
|
|
87
|
+
"path-prefix": path_prefixes,
|
|
84
88
|
"strip-path-prefix": proxy_config.dig("strip_path_prefix"),
|
|
85
89
|
"forward-headers": proxy_config.dig("forward_headers"),
|
|
86
90
|
"tls-redirect": proxy_config.dig("ssl_redirect"),
|
|
@@ -19,6 +19,14 @@ class Kamal::Configuration::Registry
|
|
|
19
19
|
lookup("password")
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def local?
|
|
23
|
+
server.to_s.match?("^localhost[:$]")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def local_port
|
|
27
|
+
local? ? (server.split(":").last.to_i || 80) : nil
|
|
28
|
+
end
|
|
29
|
+
|
|
22
30
|
private
|
|
23
31
|
attr_reader :registry_config, :secrets
|
|
24
32
|
|
|
@@ -36,7 +36,7 @@ class Kamal::Configuration::Role
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def env_tags(host)
|
|
39
|
-
tagged_hosts.fetch(host).collect { |tag| config.env_tag(tag) }
|
|
39
|
+
tagged_hosts.fetch(host).collect { |tag| config.env_tag(tag) }.compact
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def cmd
|
|
@@ -127,7 +127,7 @@ class Kamal::Configuration::Role
|
|
|
127
127
|
|
|
128
128
|
|
|
129
129
|
def asset_path
|
|
130
|
-
|
|
130
|
+
asset_path_config&.dig(0)
|
|
131
131
|
end
|
|
132
132
|
|
|
133
133
|
def assets?
|
|
@@ -137,10 +137,14 @@ class Kamal::Configuration::Role
|
|
|
137
137
|
def asset_volume(version = config.version)
|
|
138
138
|
if assets?
|
|
139
139
|
Kamal::Configuration::Volume.new \
|
|
140
|
-
host_path: asset_volume_directory(version), container_path: asset_path
|
|
140
|
+
host_path: asset_volume_directory(version), container_path: asset_path, options: asset_path_options
|
|
141
141
|
end
|
|
142
142
|
end
|
|
143
143
|
|
|
144
|
+
def asset_path_options
|
|
145
|
+
asset_path_config&.dig(1)
|
|
146
|
+
end
|
|
147
|
+
|
|
144
148
|
def asset_extracted_directory(version = config.version)
|
|
145
149
|
File.join config.assets_directory, "extracted", [ name, version ].join("-")
|
|
146
150
|
end
|
|
@@ -219,4 +223,12 @@ class Kamal::Configuration::Role
|
|
|
219
223
|
labels.merge!(specializations["labels"]) if specializations["labels"].present?
|
|
220
224
|
end
|
|
221
225
|
end
|
|
226
|
+
|
|
227
|
+
def asset_path_config
|
|
228
|
+
raw_path = specializations["asset_path"] || config.asset_path
|
|
229
|
+
return nil unless raw_path.present?
|
|
230
|
+
|
|
231
|
+
parts = raw_path.split(":", 2)
|
|
232
|
+
[ parts[0], parts[1] ]
|
|
233
|
+
end
|
|
222
234
|
end
|
|
@@ -3,10 +3,11 @@ class Kamal::Configuration::Ssh
|
|
|
3
3
|
|
|
4
4
|
include Kamal::Configuration::Validation
|
|
5
5
|
|
|
6
|
-
attr_reader :ssh_config
|
|
6
|
+
attr_reader :ssh_config, :secrets
|
|
7
7
|
|
|
8
8
|
def initialize(config:)
|
|
9
9
|
@ssh_config = config.raw_config.ssh || {}
|
|
10
|
+
@secrets = config.secrets
|
|
10
11
|
validate! ssh_config
|
|
11
12
|
end
|
|
12
13
|
|
|
@@ -35,11 +36,25 @@ class Kamal::Configuration::Ssh
|
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def key_data
|
|
38
|
-
ssh_config["key_data"]
|
|
39
|
+
key_data = ssh_config["key_data"]
|
|
40
|
+
return unless key_data
|
|
41
|
+
|
|
42
|
+
key_data.map do |k|
|
|
43
|
+
if secrets.key?(k)
|
|
44
|
+
secrets[k]
|
|
45
|
+
else
|
|
46
|
+
warn "Inline key_data usage is deprecated and will be removed in Kamal 3. Please store your key_data in a secret."
|
|
47
|
+
k
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def config
|
|
53
|
+
ssh_config["config"]
|
|
39
54
|
end
|
|
40
55
|
|
|
41
56
|
def options
|
|
42
|
-
{ user: user, port: port, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30, keys_only: keys_only, keys: keys, key_data: key_data }.compact
|
|
57
|
+
{ user: user, port: port, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30, keys_only: keys_only, keys: keys, key_data: key_data, config: config }.compact
|
|
43
58
|
end
|
|
44
59
|
|
|
45
60
|
def to_h
|
|
@@ -20,6 +20,26 @@ class Kamal::Configuration::Validator::Proxy < Kamal::Configuration::Validator
|
|
|
20
20
|
error "Missing certificate_pem setting (required when private_key_pem is present)"
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
if run_config = config["run"]
|
|
25
|
+
if run_config["bind_ips"].present?
|
|
26
|
+
ensure_valid_bind_ips(config["bind_ips"])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if run_config["publish"] == false
|
|
30
|
+
if run_config["bind_ips"].present? || run_config["http_port"].present? || run_config["https_port"].present?
|
|
31
|
+
error "Cannot set http_port, https_port or bind_ips when publish is false"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
23
35
|
end
|
|
24
36
|
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
def ensure_valid_bind_ips(bind_ips)
|
|
40
|
+
bind_ips.present? && bind_ips.each do |ip|
|
|
41
|
+
next if ip =~ Resolv::IPv4::Regex || ip =~ Resolv::IPv6::Regex
|
|
42
|
+
error "Invalid publish IP address: #{ip}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
25
45
|
end
|
|
@@ -15,10 +15,12 @@ class Kamal::Configuration::Validator::Registry < Kamal::Configuration::Validato
|
|
|
15
15
|
with_context(key) do
|
|
16
16
|
value = config[key]
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
unless config["server"]&.match?("^localhost[:$]")
|
|
19
|
+
error "is required" unless value.present?
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
unless value.is_a?(String) || (value.is_a?(Array) && value.size == 1 && value.first.is_a?(String))
|
|
22
|
+
error "should be a string or an array with one string (for secret lookup)"
|
|
23
|
+
end
|
|
22
24
|
end
|
|
23
25
|
end
|
|
24
26
|
end
|