@mriekki/lean 0.0.0-stage → 0.1.1
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.
- package/README.md +66 -2
- package/assets/components.json +33 -0
- package/assets/core/distribution.json +51 -0
- package/assets/deployment/distribution.json +34 -0
- package/assets/generated/ASSEMBLY.json +382 -0
- package/assets/generated/core/core/agents/coach.agent.md +69 -0
- package/assets/generated/core/core/agents/reviewer.agent.md +85 -0
- package/assets/generated/core/core/instructions/lean-docs.instructions.md +14 -0
- package/assets/generated/core/core/instructions/lean-workflow.instructions.md +35 -0
- package/assets/generated/core/core/learning/CODING_DRILLS.md +35 -0
- package/assets/generated/core/core/learning/DIAGRAM_GUIDE.md +19 -0
- package/assets/generated/core/core/learning/ENGINEERING_LEARNING_WORKBOOK.md +82 -0
- package/assets/generated/core/core/learning/LEARNING_MAP.md +51 -0
- package/assets/generated/core/core/learning/LEARNING_PROGRESS.md +31 -0
- package/assets/generated/core/core/learning/RESOURCE_CATALOG.md +25 -0
- package/assets/generated/core/core/learning/SQL_DRILLS.md +27 -0
- package/assets/generated/core/core/learning/SYSTEM_DESIGN_DRILLS.md +21 -0
- package/assets/generated/core/core/process/COACHING_GUIDE.md +57 -0
- package/assets/generated/core/core/process/GUIDED_NEXT_STEPS.md +48 -0
- package/assets/generated/core/core/process/HUMAN_GATES.md +81 -0
- package/assets/generated/core/core/process/INTERACTIVE_SESSION_GUIDE.md +62 -0
- package/assets/generated/core/core/process/PRODUCTION_ENABLEMENT.md +59 -0
- package/assets/generated/core/core/process/PR_WORKFLOW.md +53 -0
- package/assets/generated/core/core/process/SETUP_ACCEPTANCE_CHECKLIST.md +72 -0
- package/assets/generated/core/core/process/WORK_ITEM_MODEL.md +56 -0
- package/assets/generated/core/core/skills/cleanup-branches/SKILL.md +190 -0
- package/assets/generated/core/core/skills/coach/SKILL.md +136 -0
- package/assets/generated/core/core/skills/code-walkthrough/SKILL.md +197 -0
- package/assets/generated/core/core/skills/core-sync/SKILL.md +220 -0
- package/assets/generated/core/core/skills/define-feature/SKILL.md +144 -0
- package/assets/generated/core/core/skills/deliver/SKILL.md +315 -0
- package/assets/generated/core/core/skills/deploy/SKILL.md +193 -0
- package/assets/generated/core/core/skills/grill-me/SKILL.md +77 -0
- package/assets/generated/core/core/skills/grill-with-docs/SKILL.md +90 -0
- package/assets/generated/core/core/skills/learning-review/SKILL.md +90 -0
- package/assets/generated/core/core/skills/next-step/SKILL.md +166 -0
- package/assets/generated/core/core/skills/production-readiness/SKILL.md +357 -0
- package/assets/generated/core/core/skills/pull-latest/SKILL.md +166 -0
- package/assets/generated/core/core/skills/refine-requirements/SKILL.md +207 -0
- package/assets/generated/core/core/skills/start-work/SKILL.md +178 -0
- package/assets/generated/core/core/skills/tear-down/SKILL.md +138 -0
- package/assets/generated/core/core/skills/upstream-fix/SKILL.md +206 -0
- package/assets/generated/core/core/skills/wait-what/SKILL.md +86 -0
- package/assets/generated/core/core/templates/FEATURE_BRIEF_TEMPLATE.md +27 -0
- package/assets/generated/core/core/templates/PRODUCT_BRIEF_TEMPLATE.md +27 -0
- package/assets/generated/core/core/templates/PULL_REQUEST_TEMPLATE.md +37 -0
- package/assets/generated/core/core/templates/REQUIREMENTS_TEMPLATE.md +17 -0
- package/assets/generated/core/profiles/greenfield/lean-workspace.json +12 -0
- package/assets/generated/core/tools/package/check_baseline.py +102 -0
- package/assets/generated/core/tools/package/check_upstream.py +171 -0
- package/assets/generated/deployment/distribution/lean-poc-deploy.py +8 -0
- package/assets/generated/deployment/src/lean_poc_deployment/__init__.py +3 -0
- package/assets/generated/deployment/src/lean_poc_deployment/__main__.py +3 -0
- package/assets/generated/deployment/src/lean_poc_deployment/app_cli.py +140 -0
- package/assets/generated/deployment/src/lean_poc_deployment/app_runtime.py +455 -0
- package/assets/generated/deployment/src/lean_poc_deployment/app_runtime_remote.py +416 -0
- package/assets/generated/deployment/src/lean_poc_deployment/box_platform.py +208 -0
- package/assets/generated/deployment/src/lean_poc_deployment/box_platform_assets.py +100 -0
- package/assets/generated/deployment/src/lean_poc_deployment/box_platform_remote.py +271 -0
- package/assets/generated/deployment/src/lean_poc_deployment/cli.py +437 -0
- package/assets/generated/deployment/src/lean_poc_deployment/doctor.py +121 -0
- package/assets/generated/deployment/src/lean_poc_deployment/entrypoint.py +25 -0
- package/assets/generated/deployment/src/lean_poc_deployment/errors.py +5 -0
- package/assets/generated/deployment/src/lean_poc_deployment/keys.py +171 -0
- package/assets/generated/deployment/src/lean_poc_deployment/profiles.py +204 -0
- package/assets/generated/deployment/src/lean_poc_deployment/route_registry.py +367 -0
- package/assets/generated/deployment/src/lean_poc_deployment/route_registry_remote.py +443 -0
- package/assets/generated/deployment/src/lean_poc_deployment/security.py +290 -0
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_adapter.py +335 -0
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_adapter_remote.py +340 -0
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_cli.py +86 -0
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_import_recovery.py +437 -0
- package/assets/generated/deployment/src/lean_poc_deployment/ssh.py +118 -0
- package/assets/generated/deployment/src/lean_poc_deployment/teardown.py +286 -0
- package/assets/generated/deployment/src/lean_poc_deployment/teardown_cli.py +120 -0
- package/assets/generated/deployment/src/lean_poc_deployment/teardown_remote.py +100 -0
- package/assets/generated/deployment/src/lean_poc_deployment/trust.py +278 -0
- package/bin/lean.mjs +9 -0
- package/package.json +31 -4
- package/src/cli.mjs +131 -0
- package/src/engine.mjs +243 -0
- package/src/package-data.mjs +18 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
"""Remote script for owned route registration on a Kit-managed Ubuntu box."""
|
|
2
|
+
|
|
3
|
+
REMOTE_SCRIPT = r'''#!/usr/bin/env bash
|
|
4
|
+
set -Eeuo pipefail
|
|
5
|
+
|
|
6
|
+
ACTION="$1"
|
|
7
|
+
SPEC="${2:-}"
|
|
8
|
+
EXPECTED_SPEC="${3:-}"
|
|
9
|
+
PLATFORM_ROOT="__PLATFORM_ROOT__"
|
|
10
|
+
PROXY_ROOT="$PLATFORM_ROOT/proxy"
|
|
11
|
+
ROUTE_ROOT="$PLATFORM_ROOT/routes"
|
|
12
|
+
MARKER="$PLATFORM_ROOT/platform.env"
|
|
13
|
+
LOCK_FILE="/run/lock/lean-poc-platform.lock"
|
|
14
|
+
NETWORK_NAME="__NETWORK_NAME__"
|
|
15
|
+
CADDY_CONTAINER="__CADDY_CONTAINER__"
|
|
16
|
+
CADDY_IMAGE="__CADDY_IMAGE__"
|
|
17
|
+
PLATFORM_SCHEMA_VERSION="__PLATFORM_SCHEMA_VERSION__"
|
|
18
|
+
PLATFORM_VERSION="__PLATFORM_VERSION__"
|
|
19
|
+
PLATFORM_DISTRIBUTION="__PLATFORM_DISTRIBUTION__"
|
|
20
|
+
ROUTE_SCHEMA_VERSION="__ROUTE_SCHEMA_VERSION__"
|
|
21
|
+
ALIAS_ROOT="/etc/lean-poc-platform/aliases"
|
|
22
|
+
ALIAS_SERVICE="lean-poc-avahi-alias@"
|
|
23
|
+
|
|
24
|
+
platform_value() {
|
|
25
|
+
local key="$1"
|
|
26
|
+
[[ -f "$MARKER" ]] || return 1
|
|
27
|
+
sed -n "s/^${key}=//p" "$MARKER" | head -n1
|
|
28
|
+
}
|
|
29
|
+
platform_state() {
|
|
30
|
+
if [[ ! -f "$MARKER" ]]; then
|
|
31
|
+
printf 'missing-platform'
|
|
32
|
+
return
|
|
33
|
+
fi
|
|
34
|
+
if [[ "$(platform_value SCHEMA_VERSION || true)" != "$PLATFORM_SCHEMA_VERSION" || \
|
|
35
|
+
"$(platform_value DISTRIBUTION || true)" != "$PLATFORM_DISTRIBUTION" ]]; then
|
|
36
|
+
printf 'incompatible'
|
|
37
|
+
return
|
|
38
|
+
fi
|
|
39
|
+
if [[ "$(platform_value PLATFORM_VERSION || true)" != "$PLATFORM_VERSION" ]]; then
|
|
40
|
+
printf 'upgrade-required'
|
|
41
|
+
return
|
|
42
|
+
fi
|
|
43
|
+
if [[ "$(docker inspect --format '{{.State.Status}}' "$CADDY_CONTAINER" 2>/dev/null || true)" != "running" ]]; then
|
|
44
|
+
printf 'drifted'
|
|
45
|
+
return
|
|
46
|
+
fi
|
|
47
|
+
if ! docker network inspect "$NETWORK_NAME" >/dev/null 2>&1; then
|
|
48
|
+
printf 'drifted'
|
|
49
|
+
return
|
|
50
|
+
fi
|
|
51
|
+
if [[ ! -f "$PROXY_ROOT/Caddyfile" || ! -d "$PROXY_ROOT/conf.d" || ! -d "$ROUTE_ROOT" ]]; then
|
|
52
|
+
printf 'drifted'
|
|
53
|
+
return
|
|
54
|
+
fi
|
|
55
|
+
printf 'ready'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
valid_slug() { [[ "$1" =~ ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ ]]; }
|
|
59
|
+
valid_repo() { [[ "$1" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; }
|
|
60
|
+
valid_container() { [[ "$1" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$ ]]; }
|
|
61
|
+
valid_port() { [[ "$1" =~ ^[0-9]+$ ]] && (( 1 <= 10#$1 && 10#$1 <= 65535 )); }
|
|
62
|
+
valid_hostname() {
|
|
63
|
+
local host="$1" label
|
|
64
|
+
[[ "$host" == *.local ]] || return 1
|
|
65
|
+
[[ ${#host} -le 253 ]] || return 1
|
|
66
|
+
[[ "$host" != *:* && "$host" != *" "* && "$host" != */* && "$host" != *,* ]] || return 1
|
|
67
|
+
IFS='.' read -r -a LABELS <<< "$host"
|
|
68
|
+
(( ${#LABELS[@]} >= 2 )) || return 1
|
|
69
|
+
for label in "${LABELS[@]}"; do
|
|
70
|
+
[[ "$label" =~ ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ ]] || return 1
|
|
71
|
+
done
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
parse_spec() {
|
|
75
|
+
IFS=$'\t' read -r S_SCHEMA S_OWNER S_ENV S_APP S_ROUTE S_UPSTREAM S_PORT S_HOSTS extra <<< "$SPEC"
|
|
76
|
+
[[ -z "${extra:-}" ]] || { echo "Route spec has too many fields." >&2; exit 20; }
|
|
77
|
+
[[ "$S_SCHEMA" == "$ROUTE_SCHEMA_VERSION" ]] || { echo "Unsupported route schema." >&2; exit 20; }
|
|
78
|
+
valid_repo "$S_OWNER" || { echo "Invalid owner repo." >&2; exit 20; }
|
|
79
|
+
valid_slug "$S_ENV" || { echo "Invalid environment slug." >&2; exit 20; }
|
|
80
|
+
valid_slug "$S_APP" || { echo "Invalid app slug." >&2; exit 20; }
|
|
81
|
+
[[ "$S_ROUTE" == "$S_APP--$S_ENV" ]] || { echo "Route ID mismatch." >&2; exit 20; }
|
|
82
|
+
valid_container "$S_UPSTREAM" || { echo "Invalid upstream container." >&2; exit 20; }
|
|
83
|
+
valid_port "$S_PORT" || { echo "Invalid upstream port." >&2; exit 20; }
|
|
84
|
+
[[ -n "$S_HOSTS" ]] || { echo "No route hostnames supplied." >&2; exit 20; }
|
|
85
|
+
IFS=',' read -r -a HOST_ARRAY <<< "$S_HOSTS"
|
|
86
|
+
local host seen="|"
|
|
87
|
+
for host in "${HOST_ARRAY[@]}"; do
|
|
88
|
+
valid_hostname "$host" || { echo "Invalid route hostname: $host" >&2; exit 20; }
|
|
89
|
+
[[ "$seen" != *"|$host|"* ]] || { echo "Duplicate route hostname: $host" >&2; exit 20; }
|
|
90
|
+
seen="$seen$host|"
|
|
91
|
+
done
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
metadata_path() { printf '%s/%s.route' "$ROUTE_ROOT" "$1"; }
|
|
95
|
+
route_path() { printf '%s/conf.d/20-%s.caddy' "$PROXY_ROOT" "$1"; }
|
|
96
|
+
alias_path() { printf '%s/%s.env' "$ALIAS_ROOT" "$1"; }
|
|
97
|
+
|
|
98
|
+
metadata_value() {
|
|
99
|
+
local path="$1" key="$2"
|
|
100
|
+
sed -n "s/^${key}=//p" "$path" | head -n1
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
emit_route() {
|
|
104
|
+
local path="$1" schema owner env app route upstream port hosts
|
|
105
|
+
schema="$(metadata_value "$path" SCHEMA_VERSION)"
|
|
106
|
+
owner="$(metadata_value "$path" OWNER_REPO)"
|
|
107
|
+
env="$(metadata_value "$path" ENVIRONMENT)"
|
|
108
|
+
app="$(metadata_value "$path" APP_SLUG)"
|
|
109
|
+
route="$(metadata_value "$path" ROUTE_ID)"
|
|
110
|
+
upstream="$(metadata_value "$path" UPSTREAM_CONTAINER)"
|
|
111
|
+
port="$(metadata_value "$path" UPSTREAM_PORT)"
|
|
112
|
+
hosts="$(metadata_value "$path" HOSTNAMES)"
|
|
113
|
+
printf 'ROUTE\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$schema" "$owner" "$env" "$app" "$route" "$upstream" "$port" "$hosts"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
inventory() {
|
|
117
|
+
local state count=0 path
|
|
118
|
+
state="$(platform_state)"
|
|
119
|
+
if [[ -d "$ROUTE_ROOT" ]]; then
|
|
120
|
+
while IFS= read -r path; do
|
|
121
|
+
[[ -n "$path" ]] || continue
|
|
122
|
+
count=$((count + 1))
|
|
123
|
+
done < <(find "$ROUTE_ROOT" -maxdepth 1 -type f -name '*.route' -print 2>/dev/null | sort)
|
|
124
|
+
fi
|
|
125
|
+
printf 'SUMMARY\tplatformState\t%s\n' "$state"
|
|
126
|
+
printf 'SUMMARY\tmanagedRouteCount\t%s\n' "$count"
|
|
127
|
+
if [[ -d "$ROUTE_ROOT" ]]; then
|
|
128
|
+
while IFS= read -r path; do
|
|
129
|
+
[[ -n "$path" ]] || continue
|
|
130
|
+
emit_route "$path"
|
|
131
|
+
done < <(find "$ROUTE_ROOT" -maxdepth 1 -type f -name '*.route' -print 2>/dev/null | sort)
|
|
132
|
+
fi
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
require_ready() {
|
|
136
|
+
local state
|
|
137
|
+
state="$(platform_state)"
|
|
138
|
+
[[ "$state" == ready ]] || { echo "Kit-owned shared platform is not ready: $state" >&2; exit 21; }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
metadata_matches_spec() {
|
|
142
|
+
local path="$1" encoded="$2"
|
|
143
|
+
local e_schema e_owner e_env e_app e_route e_upstream e_port e_hosts e_extra
|
|
144
|
+
IFS=$'\t' read -r e_schema e_owner e_env e_app e_route e_upstream e_port e_hosts e_extra <<< "$encoded"
|
|
145
|
+
[[ -z "${e_extra:-}" ]] || return 1
|
|
146
|
+
[[ "$(metadata_value "$path" SCHEMA_VERSION)" == "$e_schema" && \
|
|
147
|
+
"$(metadata_value "$path" OWNER_REPO)" == "$e_owner" && \
|
|
148
|
+
"$(metadata_value "$path" ENVIRONMENT)" == "$e_env" && \
|
|
149
|
+
"$(metadata_value "$path" APP_SLUG)" == "$e_app" && \
|
|
150
|
+
"$(metadata_value "$path" ROUTE_ID)" == "$e_route" && \
|
|
151
|
+
"$(metadata_value "$path" UPSTREAM_CONTAINER)" == "$e_upstream" && \
|
|
152
|
+
"$(metadata_value "$path" UPSTREAM_PORT)" == "$e_port" && \
|
|
153
|
+
"$(metadata_value "$path" HOSTNAMES)" == "$e_hosts" ]]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
assert_expected_previous() {
|
|
157
|
+
local meta
|
|
158
|
+
meta="$(metadata_path "$S_ROUTE")"
|
|
159
|
+
if [[ "$EXPECTED_SPEC" == "NONE" ]]; then
|
|
160
|
+
[[ ! -e "$meta" ]] || { echo "Route appeared after planning; create a fresh plan." >&2; exit 28; }
|
|
161
|
+
return 0
|
|
162
|
+
fi
|
|
163
|
+
[[ -f "$meta" && ! -L "$meta" ]] || { echo "Expected previous route metadata is missing or unsafe." >&2; exit 28; }
|
|
164
|
+
metadata_matches_spec "$meta" "$EXPECTED_SPEC" || {
|
|
165
|
+
echo "Registered route changed after planning; create a fresh plan." >&2
|
|
166
|
+
exit 28
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
check_alias_collisions() {
|
|
171
|
+
local meta current_hosts="" host apath
|
|
172
|
+
meta="$(metadata_path "$S_ROUTE")"
|
|
173
|
+
if [[ -f "$meta" ]]; then current_hosts="$(metadata_value "$meta" HOSTNAMES)"; fi
|
|
174
|
+
for host in "${HOST_ARRAY[@]}"; do
|
|
175
|
+
apath="$(alias_path "$host")"
|
|
176
|
+
if [[ -L "$apath" ]]; then
|
|
177
|
+
echo "Alias registration path is a symbolic link: $apath" >&2
|
|
178
|
+
exit 29
|
|
179
|
+
fi
|
|
180
|
+
if [[ -e "$apath" && ",${current_hosts}," != *",${host},"* ]]; then
|
|
181
|
+
echo "Alias collision: $host already has unmanaged or differently owned alias state." >&2
|
|
182
|
+
exit 29
|
|
183
|
+
fi
|
|
184
|
+
if systemctl is-active --quiet "$ALIAS_SERVICE$host.service" 2>/dev/null && [[ ",${current_hosts}," != *",${host},"* ]]; then
|
|
185
|
+
echo "Alias collision: active service already publishes $host outside this route." >&2
|
|
186
|
+
exit 29
|
|
187
|
+
fi
|
|
188
|
+
done
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
check_collisions() {
|
|
192
|
+
local current="$1" path hosts host other_route
|
|
193
|
+
for path in "$ROUTE_ROOT"/*.route; do
|
|
194
|
+
[[ -f "$path" ]] || continue
|
|
195
|
+
other_route="$(metadata_value "$path" ROUTE_ID)"
|
|
196
|
+
[[ "$other_route" == "$current" ]] && continue
|
|
197
|
+
hosts="$(metadata_value "$path" HOSTNAMES)"
|
|
198
|
+
IFS=',' read -r -a EXISTING_HOSTS <<< "$hosts"
|
|
199
|
+
for host in "${HOST_ARRAY[@]}"; do
|
|
200
|
+
local existing
|
|
201
|
+
for existing in "${EXISTING_HOSTS[@]}"; do
|
|
202
|
+
if [[ "$host" == "$existing" ]]; then
|
|
203
|
+
echo "Hostname collision: $host is already owned by route $other_route" >&2
|
|
204
|
+
exit 22
|
|
205
|
+
fi
|
|
206
|
+
done
|
|
207
|
+
done
|
|
208
|
+
done
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
assert_same_owner_if_existing() {
|
|
212
|
+
local meta
|
|
213
|
+
meta="$(metadata_path "$S_ROUTE")"
|
|
214
|
+
[[ -f "$meta" ]] || return 0
|
|
215
|
+
[[ "$(metadata_value "$meta" OWNER_REPO)" == "$S_OWNER" && \
|
|
216
|
+
"$(metadata_value "$meta" ENVIRONMENT)" == "$S_ENV" && \
|
|
217
|
+
"$(metadata_value "$meta" APP_SLUG)" == "$S_APP" ]] || {
|
|
218
|
+
echo "Existing route ownership does not match requested owner/environment/app." >&2
|
|
219
|
+
exit 23
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
render_route() {
|
|
224
|
+
local dest="$1" host first=true
|
|
225
|
+
: > "$dest"
|
|
226
|
+
for host in "${HOST_ARRAY[@]}"; do
|
|
227
|
+
if [[ "$first" == true ]]; then
|
|
228
|
+
printf 'http://%s' "$host" >> "$dest"
|
|
229
|
+
first=false
|
|
230
|
+
else
|
|
231
|
+
printf ', http://%s' "$host" >> "$dest"
|
|
232
|
+
fi
|
|
233
|
+
done
|
|
234
|
+
cat >> "$dest" <<EOF
|
|
235
|
+
{
|
|
236
|
+
reverse_proxy $S_UPSTREAM:$S_PORT
|
|
237
|
+
}
|
|
238
|
+
EOF
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
write_metadata() {
|
|
242
|
+
local dest="$1"
|
|
243
|
+
cat > "$dest" <<EOF
|
|
244
|
+
SCHEMA_VERSION=$S_SCHEMA
|
|
245
|
+
OWNER_REPO=$S_OWNER
|
|
246
|
+
ENVIRONMENT=$S_ENV
|
|
247
|
+
APP_SLUG=$S_APP
|
|
248
|
+
ROUTE_ID=$S_ROUTE
|
|
249
|
+
UPSTREAM_CONTAINER=$S_UPSTREAM
|
|
250
|
+
UPSTREAM_PORT=$S_PORT
|
|
251
|
+
HOSTNAMES=$S_HOSTS
|
|
252
|
+
EOF
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
validate_candidate_conf() {
|
|
256
|
+
local candidate_conf="$1"
|
|
257
|
+
docker run --rm \
|
|
258
|
+
-v "$PROXY_ROOT/Caddyfile:/etc/caddy/Caddyfile:ro" \
|
|
259
|
+
-v "$candidate_conf:/etc/caddy/conf.d:ro" \
|
|
260
|
+
"$CADDY_IMAGE" caddy validate --config /etc/caddy/Caddyfile >/dev/null
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
copy_conf_for_candidate() {
|
|
264
|
+
local candidate="$1" target_route="$2"
|
|
265
|
+
install -d -m 0755 "$candidate"
|
|
266
|
+
local path
|
|
267
|
+
for path in "$PROXY_ROOT/conf.d"/*.caddy; do
|
|
268
|
+
[[ -f "$path" ]] || continue
|
|
269
|
+
[[ "$path" == "$target_route" ]] && continue
|
|
270
|
+
cp -a "$path" "$candidate/$(basename "$path")"
|
|
271
|
+
done
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
require_upstream_ready() {
|
|
275
|
+
local state
|
|
276
|
+
state="$(docker inspect --format '{{.State.Status}}' "$S_UPSTREAM" 2>/dev/null || true)"
|
|
277
|
+
[[ "$state" == running ]] || { echo "Upstream container is not running: $S_UPSTREAM" >&2; exit 24; }
|
|
278
|
+
if ! docker network inspect "$NETWORK_NAME" --format '{{range .Containers}}{{println .Name}}{{end}}' | grep -Fxq "$S_UPSTREAM"; then
|
|
279
|
+
echo "Upstream container is not attached to $NETWORK_NAME; application deployment must attach it before route registration." >&2
|
|
280
|
+
exit 27
|
|
281
|
+
fi
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
register_route() {
|
|
285
|
+
require_ready
|
|
286
|
+
parse_spec
|
|
287
|
+
install -d -m 0750 "$ROUTE_ROOT"
|
|
288
|
+
install -d -m 0755 "$ALIAS_ROOT"
|
|
289
|
+
assert_expected_previous
|
|
290
|
+
assert_same_owner_if_existing
|
|
291
|
+
check_alias_collisions
|
|
292
|
+
check_collisions "$S_ROUTE"
|
|
293
|
+
|
|
294
|
+
local route_file meta_file candidate_dir candidate_route temp_meta backup_dir old_hosts=""
|
|
295
|
+
route_file="$(route_path "$S_ROUTE")"
|
|
296
|
+
meta_file="$(metadata_path "$S_ROUTE")"
|
|
297
|
+
candidate_dir="$(mktemp -d)"
|
|
298
|
+
backup_dir="$(mktemp -d)"
|
|
299
|
+
candidate_route="$candidate_dir/$(basename "$route_file")"
|
|
300
|
+
copy_conf_for_candidate "$candidate_dir" "$route_file"
|
|
301
|
+
render_route "$candidate_route"
|
|
302
|
+
validate_candidate_conf "$candidate_dir"
|
|
303
|
+
|
|
304
|
+
if [[ -f "$meta_file" ]]; then old_hosts="$(metadata_value "$meta_file" HOSTNAMES)"; fi
|
|
305
|
+
[[ -f "$route_file" ]] && cp -a "$route_file" "$backup_dir/route.caddy"
|
|
306
|
+
[[ -f "$meta_file" ]] && cp -a "$meta_file" "$backup_dir/route.meta"
|
|
307
|
+
IFS=',' read -r -a OLD_HOST_ARRAY <<< "$old_hosts"
|
|
308
|
+
local host
|
|
309
|
+
for host in "${OLD_HOST_ARRAY[@]}" "${HOST_ARRAY[@]}"; do
|
|
310
|
+
[[ -n "$host" ]] || continue
|
|
311
|
+
local apath
|
|
312
|
+
apath="$(alias_path "$host")"
|
|
313
|
+
if [[ -f "$apath" && ! -f "$backup_dir/alias-$host.env" ]]; then cp -a "$apath" "$backup_dir/alias-$host.env"; fi
|
|
314
|
+
done
|
|
315
|
+
|
|
316
|
+
rollback() {
|
|
317
|
+
echo "Route registration failed; restoring previous route/alias state." >&2
|
|
318
|
+
if [[ -f "$backup_dir/route.caddy" ]]; then cp -f "$backup_dir/route.caddy" "$route_file"; else rm -f "$route_file"; fi
|
|
319
|
+
if [[ -f "$backup_dir/route.meta" ]]; then cp -f "$backup_dir/route.meta" "$meta_file"; else rm -f "$meta_file"; fi
|
|
320
|
+
local rollback_host
|
|
321
|
+
for rollback_host in "${OLD_HOST_ARRAY[@]}" "${HOST_ARRAY[@]}"; do
|
|
322
|
+
[[ -n "$rollback_host" ]] || continue
|
|
323
|
+
if [[ -f "$backup_dir/alias-$rollback_host.env" ]]; then
|
|
324
|
+
cp -f "$backup_dir/alias-$rollback_host.env" "$(alias_path "$rollback_host")"
|
|
325
|
+
systemctl enable --now "$ALIAS_SERVICE$rollback_host.service" >/dev/null 2>&1 || true
|
|
326
|
+
systemctl restart "$ALIAS_SERVICE$rollback_host.service" >/dev/null 2>&1 || true
|
|
327
|
+
else
|
|
328
|
+
systemctl disable --now "$ALIAS_SERVICE$rollback_host.service" >/dev/null 2>&1 || true
|
|
329
|
+
rm -f "$(alias_path "$rollback_host")"
|
|
330
|
+
fi
|
|
331
|
+
done
|
|
332
|
+
docker exec "$CADDY_CONTAINER" caddy reload --config /etc/caddy/Caddyfile >/dev/null 2>&1 || true
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
require_upstream_ready
|
|
336
|
+
local tmp_route="${route_file}.tmp.$$" tmp_meta="${meta_file}.tmp.$$"
|
|
337
|
+
cp -f "$candidate_route" "$tmp_route" && chmod 0644 "$tmp_route" && mv -f "$tmp_route" "$route_file"
|
|
338
|
+
write_metadata "$tmp_meta" && chmod 0644 "$tmp_meta" && mv -f "$tmp_meta" "$meta_file"
|
|
339
|
+
|
|
340
|
+
local rc=0
|
|
341
|
+
set +e
|
|
342
|
+
for host in "${HOST_ARRAY[@]}"; do
|
|
343
|
+
printf 'ALIAS_HOSTNAME=%s\n' "$host" > "$(alias_path "$host")"
|
|
344
|
+
chmod 0644 "$(alias_path "$host")"
|
|
345
|
+
systemctl enable --now "$ALIAS_SERVICE$host.service" >/dev/null
|
|
346
|
+
rc=$?; [[ $rc -eq 0 ]] || break
|
|
347
|
+
systemctl restart "$ALIAS_SERVICE$host.service" >/dev/null
|
|
348
|
+
rc=$?; [[ $rc -eq 0 ]] || break
|
|
349
|
+
done
|
|
350
|
+
[[ $rc -eq 0 ]] && docker exec "$CADDY_CONTAINER" caddy reload --config /etc/caddy/Caddyfile >/dev/null
|
|
351
|
+
rc=$(( rc == 0 ? $? : rc ))
|
|
352
|
+
set -e
|
|
353
|
+
if [[ $rc -ne 0 ]]; then
|
|
354
|
+
rollback
|
|
355
|
+
rm -rf "$candidate_dir" "$backup_dir"
|
|
356
|
+
exit "$rc"
|
|
357
|
+
fi
|
|
358
|
+
|
|
359
|
+
for host in "${OLD_HOST_ARRAY[@]}"; do
|
|
360
|
+
[[ -n "$host" ]] || continue
|
|
361
|
+
if [[ ",${S_HOSTS}," != *",${host},"* ]]; then
|
|
362
|
+
systemctl disable --now "$ALIAS_SERVICE$host.service" >/dev/null 2>&1 || true
|
|
363
|
+
rm -f "$(alias_path "$host")"
|
|
364
|
+
fi
|
|
365
|
+
done
|
|
366
|
+
rm -rf "$candidate_dir" "$backup_dir"
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
remove_route() {
|
|
370
|
+
require_ready
|
|
371
|
+
parse_spec
|
|
372
|
+
local route_file meta_file existing_hosts host candidate_dir
|
|
373
|
+
route_file="$(route_path "$S_ROUTE")"
|
|
374
|
+
meta_file="$(metadata_path "$S_ROUTE")"
|
|
375
|
+
[[ -f "$meta_file" ]] || { echo "Managed route is missing: $S_ROUTE" >&2; exit 25; }
|
|
376
|
+
assert_same_owner_if_existing
|
|
377
|
+
existing_hosts="$(metadata_value "$meta_file" HOSTNAMES)"
|
|
378
|
+
[[ "$existing_hosts" == "$S_HOSTS" && "$(metadata_value "$meta_file" UPSTREAM_CONTAINER)" == "$S_UPSTREAM" && "$(metadata_value "$meta_file" UPSTREAM_PORT)" == "$S_PORT" ]] || {
|
|
379
|
+
echo "Registered route changed since the authorized plan." >&2; exit 26;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
candidate_dir="$(mktemp -d)"
|
|
383
|
+
copy_conf_for_candidate "$candidate_dir" "$route_file"
|
|
384
|
+
validate_candidate_conf "$candidate_dir"
|
|
385
|
+
|
|
386
|
+
local backup_dir
|
|
387
|
+
backup_dir="$(mktemp -d)"
|
|
388
|
+
cp -a "$route_file" "$backup_dir/route.caddy"
|
|
389
|
+
cp -a "$meta_file" "$backup_dir/route.meta"
|
|
390
|
+
IFS=',' read -r -a OLD_HOST_ARRAY <<< "$existing_hosts"
|
|
391
|
+
for host in "${OLD_HOST_ARRAY[@]}"; do
|
|
392
|
+
[[ -f "$(alias_path "$host")" ]] && cp -a "$(alias_path "$host")" "$backup_dir/alias-$host.env"
|
|
393
|
+
done
|
|
394
|
+
|
|
395
|
+
rollback() {
|
|
396
|
+
cp -f "$backup_dir/route.caddy" "$route_file"
|
|
397
|
+
cp -f "$backup_dir/route.meta" "$meta_file"
|
|
398
|
+
local rollback_host
|
|
399
|
+
for rollback_host in "${OLD_HOST_ARRAY[@]}"; do
|
|
400
|
+
[[ -f "$backup_dir/alias-$rollback_host.env" ]] || continue
|
|
401
|
+
cp -f "$backup_dir/alias-$rollback_host.env" "$(alias_path "$rollback_host")"
|
|
402
|
+
systemctl enable --now "$ALIAS_SERVICE$rollback_host.service" >/dev/null 2>&1 || true
|
|
403
|
+
systemctl restart "$ALIAS_SERVICE$rollback_host.service" >/dev/null 2>&1 || true
|
|
404
|
+
done
|
|
405
|
+
docker exec "$CADDY_CONTAINER" caddy reload --config /etc/caddy/Caddyfile >/dev/null 2>&1 || true
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
rm -f "$route_file" "$meta_file"
|
|
409
|
+
local rc=0
|
|
410
|
+
set +e
|
|
411
|
+
docker exec "$CADDY_CONTAINER" caddy reload --config /etc/caddy/Caddyfile >/dev/null
|
|
412
|
+
rc=$?
|
|
413
|
+
if [[ $rc -eq 0 ]]; then
|
|
414
|
+
for host in "${OLD_HOST_ARRAY[@]}"; do
|
|
415
|
+
systemctl disable --now "$ALIAS_SERVICE$host.service" >/dev/null 2>&1 || true
|
|
416
|
+
rm -f "$(alias_path "$host")"
|
|
417
|
+
done
|
|
418
|
+
fi
|
|
419
|
+
set -e
|
|
420
|
+
if [[ $rc -ne 0 ]]; then
|
|
421
|
+
rollback
|
|
422
|
+
rm -rf "$candidate_dir" "$backup_dir"
|
|
423
|
+
exit "$rc"
|
|
424
|
+
fi
|
|
425
|
+
rm -rf "$candidate_dir" "$backup_dir"
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
case "$ACTION" in
|
|
429
|
+
inventory)
|
|
430
|
+
inventory
|
|
431
|
+
;;
|
|
432
|
+
register|remove)
|
|
433
|
+
exec 9>>"$LOCK_FILE"
|
|
434
|
+
if ! flock -n 9; then echo "Another platform/route operation holds the lock." >&2; exit 75; fi
|
|
435
|
+
if [[ "$ACTION" == register ]]; then register_route; else remove_route; fi
|
|
436
|
+
inventory
|
|
437
|
+
;;
|
|
438
|
+
*)
|
|
439
|
+
echo "Unsupported route-registry action: $ACTION" >&2
|
|
440
|
+
exit 2
|
|
441
|
+
;;
|
|
442
|
+
esac
|
|
443
|
+
'''
|