odysseus-core 0.9.0 → 0.10.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/CHANGELOG.md +23 -0
- data/lib/odysseus/caddy/client.rb +56 -4
- data/lib/odysseus/core/version.rb +1 -1
- data/lib/odysseus/host_paths.rb +14 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6d1b931d263332fd0a431b4a0842949f52d78f2f366f8dbf72cb98f0ea95282
|
|
4
|
+
data.tar.gz: f3ea3a6477a88cbd38b1386f04e983bad5d7d782475f46322989568389e4fa5b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b89f611e20800ecfbdbd30fe571b6a9979877a59f52655a0c05fb3aeadc7567f6af4325711c92eb02698bbdd23c1746f6c1d150436e20dc45318a4f0fae0f1b4
|
|
7
|
+
data.tar.gz: 6db503709fb510a38980f0357a69f82f07b7b04e77b8b63c7286035f88425134039c06618164648876324a4a22f902341d4ca41fa4bb5a027a5addc07a4a7309
|
data/CHANGELOG.md
CHANGED
|
@@ -7,8 +7,31 @@ gem artifacts, so they are summaries rather than contemporaneous notes.
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.10.0] - 2026-08-28
|
|
11
|
+
|
|
10
12
|
### Fixed
|
|
11
13
|
|
|
14
|
+
- Caddy no longer loses every route when its container is replaced or the host
|
|
15
|
+
reboots. Routes are added through the admin API and live in Caddy's memory;
|
|
16
|
+
Caddy autosaves them to `$XDG_CONFIG_HOME/caddy/autosave.json`, which the
|
|
17
|
+
official image sets to `/config` -- and odysseus mounted only `/data`
|
|
18
|
+
(`XDG_DATA_HOME`), so certificates persisted and routes were discarded with
|
|
19
|
+
the container. On a host with nine services, all nine stayed unreachable until
|
|
20
|
+
nine separate deploys ran. `HostPaths#caddy_config_dir` is now mounted at
|
|
21
|
+
`/config` and Caddy starts with `--resume`.
|
|
22
|
+
|
|
23
|
+
If `--resume` fails to start the container, odysseus renames the autosave to
|
|
24
|
+
`autosave.json.rejected` and starts once without it. `CADDY_IMAGE` is the
|
|
25
|
+
moving `caddy:2-alpine` tag, so a newer Caddy can meet an autosave it will not
|
|
26
|
+
parse, and `WebDeploy` aborts when Caddy does not come up -- unhandled, one
|
|
27
|
+
bad autosave would have failed every deploy on the host. The rename runs
|
|
28
|
+
inside a container: Caddy creates `/config/caddy` as root with mode 0700, so a
|
|
29
|
+
non-root deploy user cannot move the file from the host.
|
|
30
|
+
|
|
31
|
+
A host that has never run Caddy still has no routes until each service
|
|
32
|
+
deploys; `--resume` falls back to the image's Caddyfile there, which is what
|
|
33
|
+
creates `srv0`.
|
|
34
|
+
|
|
12
35
|
- Commands echoed under `--debug`/`-v` no longer print secrets. `SSH#execute`
|
|
13
36
|
echoed the command verbatim, and registry login is built as
|
|
14
37
|
`echo '<password>' | docker login <server> -u <user> --password-stdin` --
|
|
@@ -10,6 +10,11 @@ module Odysseus
|
|
|
10
10
|
CONTAINER_NAME = 'odysseus-caddy'.freeze
|
|
11
11
|
CADDY_IMAGE = 'caddy:2-alpine'.freeze
|
|
12
12
|
|
|
13
|
+
# The image's own CMD. Repeated here because --resume has to be appended
|
|
14
|
+
# to it: passing only --resume would drop the Caddyfile fallback that
|
|
15
|
+
# creates srv0 on a host with no autosave yet.
|
|
16
|
+
CADDY_RUN_CMD = 'caddy run --config /etc/caddy/Caddyfile --adapter caddyfile'.freeze
|
|
17
|
+
|
|
13
18
|
# @param ssh [Odysseus::Deployer::SSH] SSH connection to server
|
|
14
19
|
# @param docker [Odysseus::Docker::Client] Docker client
|
|
15
20
|
def initialize(ssh:, docker:)
|
|
@@ -34,6 +39,17 @@ module Odysseus
|
|
|
34
39
|
# Nothing is lost: certificates live in the mounted volume, and routes
|
|
35
40
|
# are re-added by the deploy that follows.
|
|
36
41
|
#
|
|
42
|
+
# Started with --resume, so the routes added through the admin API by
|
|
43
|
+
# previous deploys come back with the container rather than having to be
|
|
44
|
+
# re-added by redeploying every service on the host.
|
|
45
|
+
#
|
|
46
|
+
# If that start fails the autosave is the only new thing that could have
|
|
47
|
+
# caused it -- CADDY_IMAGE is a moving tag, so a newer Caddy can meet an
|
|
48
|
+
# autosave it will not parse. Since WebDeploy aborts when Caddy does not
|
|
49
|
+
# come up, leaving that unhandled would fail every deploy on the host
|
|
50
|
+
# until someone deleted the file by hand. The retry gives up the routes,
|
|
51
|
+
# which is exactly the behaviour that existed before --resume.
|
|
52
|
+
#
|
|
37
53
|
# @return [Boolean] true if caddy is running
|
|
38
54
|
def ensure_running
|
|
39
55
|
return true if running?
|
|
@@ -41,6 +57,9 @@ module Odysseus
|
|
|
41
57
|
@docker.remove(CONTAINER_NAME) if @docker.container_exists?(CONTAINER_NAME)
|
|
42
58
|
|
|
43
59
|
start_caddy
|
|
60
|
+
return true if running?
|
|
61
|
+
|
|
62
|
+
restart_without_autosave
|
|
44
63
|
running?
|
|
45
64
|
end
|
|
46
65
|
|
|
@@ -51,12 +70,18 @@ module Odysseus
|
|
|
51
70
|
end
|
|
52
71
|
|
|
53
72
|
# Start Caddy container
|
|
54
|
-
|
|
73
|
+
# @param resume [Boolean] reload the autosaved config (see #ensure_running)
|
|
74
|
+
def start_caddy(resume: true)
|
|
55
75
|
# Create network if not exists (with label to protect from prune)
|
|
56
76
|
@ssh.execute('docker network create --label odysseus.managed=true odysseus 2>/dev/null || true')
|
|
57
77
|
|
|
58
|
-
# Create data directory for certificates
|
|
78
|
+
# Create data directory for certificates, and the config directory
|
|
79
|
+
# holding the autosave. Two directories because the image sets
|
|
80
|
+
# XDG_DATA_HOME=/data and XDG_CONFIG_HOME=/config, and Caddy writes the
|
|
81
|
+
# autosave under the latter -- mounting only /data persisted the
|
|
82
|
+
# certificates and threw the routes away with the container.
|
|
59
83
|
@ssh.execute("mkdir -p #{Shellwords.escape(host_paths.caddy_dir)}")
|
|
84
|
+
@ssh.execute("mkdir -p #{Shellwords.escape(host_paths.caddy_config_dir)}")
|
|
60
85
|
|
|
61
86
|
# Run Caddy with admin API enabled and persistent storage for certs
|
|
62
87
|
#
|
|
@@ -81,13 +106,17 @@ module Odysseus
|
|
|
81
106
|
ports: ['80:80', '443:443', "127.0.0.1:#{ADMIN_API_PORT}:#{ADMIN_API_PORT}"],
|
|
82
107
|
network: 'odysseus',
|
|
83
108
|
restart: 'unless-stopped',
|
|
84
|
-
volumes: [
|
|
109
|
+
volumes: [
|
|
110
|
+
"#{Shellwords.escape(host_paths.caddy_dir)}:/data",
|
|
111
|
+
"#{Shellwords.escape(host_paths.caddy_config_dir)}:/config"
|
|
112
|
+
],
|
|
85
113
|
env: {
|
|
86
114
|
'CADDY_ADMIN' => "0.0.0.0:#{ADMIN_API_PORT}"
|
|
87
115
|
},
|
|
88
116
|
labels: {
|
|
89
117
|
'odysseus.managed' => 'true'
|
|
90
|
-
}
|
|
118
|
+
},
|
|
119
|
+
cmd: resume ? "#{CADDY_RUN_CMD} --resume" : CADDY_RUN_CMD
|
|
91
120
|
}
|
|
92
121
|
)
|
|
93
122
|
|
|
@@ -316,6 +345,29 @@ module Odysseus
|
|
|
316
345
|
|
|
317
346
|
private
|
|
318
347
|
|
|
348
|
+
def restart_without_autosave
|
|
349
|
+
@docker.remove(CONTAINER_NAME) if @docker.container_exists?(CONTAINER_NAME)
|
|
350
|
+
discard_autosave
|
|
351
|
+
start_caddy(resume: false)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Caddy creates /config/caddy as root with mode 0700, so a non-root deploy
|
|
355
|
+
# user cannot move the file from the host -- it has to be moved from
|
|
356
|
+
# inside a container. CADDY_IMAGE is guaranteed to be on the host: it is
|
|
357
|
+
# the image that just failed to start.
|
|
358
|
+
#
|
|
359
|
+
# Renamed rather than deleted, because if --resume was not the reason
|
|
360
|
+
# Caddy failed then this file is the only copy of the host's routes.
|
|
361
|
+
# Tolerates its own failure: a Caddy that died for some other reason may
|
|
362
|
+
# have no autosave at all, and that must not abort the retry.
|
|
363
|
+
def discard_autosave
|
|
364
|
+
mount = "#{Shellwords.escape(host_paths.caddy_config_dir)}:/config"
|
|
365
|
+
@ssh.execute(
|
|
366
|
+
"docker run --rm -v #{mount} #{CADDY_IMAGE} " \
|
|
367
|
+
'mv /config/caddy/autosave.json /config/caddy/autosave.json.rejected 2>/dev/null || true'
|
|
368
|
+
)
|
|
369
|
+
end
|
|
370
|
+
|
|
319
371
|
def host_paths
|
|
320
372
|
@host_paths ||= Odysseus::HostPaths.new(@ssh)
|
|
321
373
|
end
|
data/lib/odysseus/host_paths.rb
CHANGED
|
@@ -57,6 +57,20 @@ module Odysseus
|
|
|
57
57
|
File.join(base, 'caddy')
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
# Caddy's /config mount: the autosave of its running configuration.
|
|
61
|
+
#
|
|
62
|
+
# The official image sets XDG_CONFIG_HOME=/config and XDG_DATA_HOME=/data,
|
|
63
|
+
# and Caddy writes $XDG_CONFIG_HOME/caddy/autosave.json on every admin API
|
|
64
|
+
# change. Mounting only #caddy_dir therefore persisted certificates and
|
|
65
|
+
# discarded the routes, which is why a removed or restarted Caddy came back
|
|
66
|
+
# serving nothing until every service on the host redeployed. Kept separate
|
|
67
|
+
# from #caddy_dir rather than nested inside it because the container owns
|
|
68
|
+
# the layout of both, and /data already has a meaning to Caddy.
|
|
69
|
+
# @return [String]
|
|
70
|
+
def caddy_config_dir
|
|
71
|
+
File.join(base, 'caddy-config')
|
|
72
|
+
end
|
|
73
|
+
|
|
60
74
|
# Where a root install wrote, whoever is connected now. Used to read the
|
|
61
75
|
# deploy history of a host that has since moved to a deploy user.
|
|
62
76
|
# @return [String]
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: odysseus-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Thomas
|
|
@@ -157,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
157
157
|
- !ruby/object:Gem::Version
|
|
158
158
|
version: '0'
|
|
159
159
|
requirements: []
|
|
160
|
-
rubygems_version: 4.0.
|
|
160
|
+
rubygems_version: 4.0.19
|
|
161
161
|
specification_version: 4
|
|
162
162
|
summary: Core library for Odysseus deployment tool
|
|
163
163
|
test_files: []
|