kampodine 0.3.0 → 0.4.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.
- package/CHANGELOG.md +21 -0
- package/README.md +2 -2
- package/cli.js +1 -1
- package/package.json +1 -1
- package/scripts/bluegreen.sh +57 -56
- package/scripts/deploy.sh +21 -22
- package/scripts/dns.sh +10 -8
- package/scripts/env.sh +16 -16
- package/scripts/image-import.sh +20 -19
- package/scripts/migrate.sh +4 -4
- package/scripts/vm-prepare.sh +84 -84
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 — 2026-10-07
|
|
4
|
+
|
|
5
|
+
**Breaking: project-generic naming.** Every origin-coupled default name is
|
|
6
|
+
gone; the package now speaks only for itself.
|
|
7
|
+
|
|
8
|
+
- `KAMPODINE_HOST` replaces `ESPELLAR_HOST` everywhere (deploy, env); the
|
|
9
|
+
key-resolution ladder is `--ssh-key` | `KAMPODINE_SSH_KEY` | ssh-agent /
|
|
10
|
+
ssh-config — the old `ESPELLAR_SSH_KEY` / `ESSELLAR_SSH_KEY` aliases are
|
|
11
|
+
no longer mentioned anywhere
|
|
12
|
+
- Guest paths renamed: `/etc/esellar/*` → `/etc/kampodine/*` (env file,
|
|
13
|
+
deployed-sha, anchor.conf); the anchor watcher is `kampodine-anchor`
|
|
14
|
+
- Service/container/image names: `esellar-api` → `kampodine-api`,
|
|
15
|
+
`esellar-blue`/`esellar-green` → `kampodine-blue`/`kampodine-green`,
|
|
16
|
+
golden image `esellar-alpine*` → `kampodine-alpine*`
|
|
17
|
+
- `OCI_COMPARTMENT` is now REQUIRED (no default — compartments are
|
|
18
|
+
account-specific); `OCI_PROFILE` defaults to `default` (the OCI CLI's
|
|
19
|
+
own default profile) instead of a named profile
|
|
20
|
+
- Existing guests keep booting, but flips against guests provisioned by
|
|
21
|
+
older releases need their watcher/paths updated (or re-run
|
|
22
|
+
`vm-prepare` on a fresh host) since the anchor protocol path changed
|
|
23
|
+
|
|
3
24
|
## 0.3.0 — 2026-10-07
|
|
4
25
|
|
|
5
26
|
Full-lifecycle orchestration with first-class help — the "mini vercel CLI"
|
package/README.md
CHANGED
|
@@ -48,12 +48,12 @@ kampodine deploy:
|
|
|
48
48
|
- **`kampodine deploy`** — stream deploy; `--version <sha>` restreams an
|
|
49
49
|
existing build; `--rollback [sha]` is an instant image-tag rollback (the
|
|
50
50
|
previous image stays on the VM for exactly this)
|
|
51
|
-
- **`kampodine env`** — manage the remote app env file (`/etc/
|
|
51
|
+
- **`kampodine env`** — manage the remote app env file (`/etc/kampodine/env`,
|
|
52
52
|
0600 root) without ever printing a secret: `list` shows KEY + fingerprints
|
|
53
53
|
only (value length + first 2 chars), `push --file <env>` uploads over ssh
|
|
54
54
|
stdin into a 0600 temp + atomic `mv`, `pull` streams the raw payload to
|
|
55
55
|
stdout or `--out` (written 0600). Host/key resolution is identical to
|
|
56
|
-
deploy: `--host root@<ip>` | `
|
|
56
|
+
deploy: `--host root@<ip>` | `KAMPODINE_HOST`, `--ssh-key <path>` |
|
|
57
57
|
`KAMPODINE_SSH_KEY` | ssh-agent. `env fingerprint` previews the masking
|
|
58
58
|
for any local file — values never leave stdin
|
|
59
59
|
- **`kampodine dns`** — OCI DNS records for the post-sslip.io era:
|
package/cli.js
CHANGED
|
@@ -39,7 +39,7 @@ DNS
|
|
|
39
39
|
dns OCI DNS records (oci config-file / instance-principal auth ONLY): records | add | rm
|
|
40
40
|
|
|
41
41
|
ENV
|
|
42
|
-
env remote app env file (/etc/
|
|
42
|
+
env remote app env file (/etc/kampodine/env, 0600): list | push | pull — values NEVER printed, fingerprints only
|
|
43
43
|
|
|
44
44
|
Every command supports --help with usage + examples. All further args pass through to the underlying script.
|
|
45
45
|
`;
|
package/package.json
CHANGED
package/scripts/bluegreen.sh
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# API call, rollback is the same call reversed.
|
|
9
9
|
#
|
|
10
10
|
# Colors are instance display names: <app>-blue / <app>-green (defaults:
|
|
11
|
-
#
|
|
11
|
+
# kampodine-blue / kampodine-green).
|
|
12
12
|
# The reserved IP is derived from live OCI state — no local state file.
|
|
13
13
|
#
|
|
14
14
|
# Usage:
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
#
|
|
21
21
|
# FLIP = ACME-FIRST:
|
|
22
22
|
# 1. health gate on the target's own IP
|
|
23
|
-
# 2. anchor.conf written on the TARGET guest over ssh — the
|
|
23
|
+
# 2. anchor.conf written on the TARGET guest over ssh — the kampodine-anchor
|
|
24
24
|
# watcher service (shipped by vm-prepare) configures the anchor private
|
|
25
25
|
# address within one interval; flip waits for `ip addr` to show it
|
|
26
26
|
# 3. OCI assigns the reserved IP to the target's anchor (secondary private
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
# instead — it runs the same ACME-first sequence there.
|
|
50
50
|
#
|
|
51
51
|
# provision has TWO routes:
|
|
52
|
-
# 1. NATIVE — a UEFI_64
|
|
52
|
+
# 1. NATIVE — a UEFI_64 kampodine-alpine* custom image exists in the
|
|
53
53
|
# compartment: launch it directly (the golden image boots as-is).
|
|
54
54
|
# 2. INJECT (provision-via-migrate) — OCI pins imported custom images to
|
|
55
55
|
# firmware=BIOS and A1/Ampere is UEFI-only, but the template instance
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
# 'gunzip | sudo dd', reboot, verify /etc/alpine-release). The instance
|
|
64
64
|
# record keeps the platform image metadata — exactly like the template.
|
|
65
65
|
#
|
|
66
|
-
# Env: OCI_PROFILE (default
|
|
66
|
+
# Env: OCI_PROFILE (default: "default"), OCI_COMPARTMENT (required).
|
|
67
67
|
# Injection extras: ALPINE_QCOW2 (golden disk path), OPS_SSH_PUBKEY (ops
|
|
68
68
|
# public key for the platform-image launch), PLATFORM_SSH_USER (default
|
|
69
69
|
# ubuntu), INJECT_PROBE_SLEEP / INJECT_PROBE_TRIES (ssh wait tuning).
|
|
@@ -71,8 +71,8 @@
|
|
|
71
71
|
# loopback; the reserved IP is checked over :80 with the prod Host header).
|
|
72
72
|
set -euo pipefail
|
|
73
73
|
|
|
74
|
-
PROFILE="${OCI_PROFILE:-
|
|
75
|
-
COMPARTMENT_NAME="${OCI_COMPARTMENT:-
|
|
74
|
+
PROFILE="${OCI_PROFILE:-default}"
|
|
75
|
+
COMPARTMENT_NAME="${OCI_COMPARTMENT:-kampodine}"
|
|
76
76
|
APP_HOST_HEADER="${APP_HOST_HEADER:-app.example.com}"
|
|
77
77
|
REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)}"
|
|
78
78
|
SSH_OPTS=(-o ConnectTimeout=6 -o BatchMode=yes -o StrictHostKeyChecking=accept-new)
|
|
@@ -86,7 +86,7 @@ usage() {
|
|
|
86
86
|
cat <<'EOF'
|
|
87
87
|
|
|
88
88
|
Each sub-step has its own --help: status | init | provision | flip | rollback.
|
|
89
|
-
Env: OCI_PROFILE (default
|
|
89
|
+
Env: OCI_PROFILE (default: "default"), OCI_COMPARTMENT (required).
|
|
90
90
|
|
|
91
91
|
Examples:
|
|
92
92
|
kampodine bluegreen status
|
|
@@ -181,7 +181,7 @@ compartment_ocid() {
|
|
|
181
181
|
instance_by_color() {
|
|
182
182
|
local comp="$1" color="$2" row
|
|
183
183
|
row="$(oci compute instance list -c "$comp" --profile "$PROFILE" \
|
|
184
|
-
--display-name "
|
|
184
|
+
--display-name "kampodine-$color" --lifecycle-state RUNNING \
|
|
185
185
|
--query 'sort_by(data, &"time-created")[-1] | ["id", "availability-domain"] | join('\'' '\'', @)' \
|
|
186
186
|
--raw-output 2>/dev/null || true)"
|
|
187
187
|
[[ -n "$row" ]] || return 0
|
|
@@ -240,7 +240,7 @@ reserved_anchor_ip() {
|
|
|
240
240
|
return 0
|
|
241
241
|
fi
|
|
242
242
|
oci network private-ip create --profile "$PROFILE" --vnic-id "$vnic" \
|
|
243
|
-
--display-name "
|
|
243
|
+
--display-name "kampodine-reserved-anchor" \
|
|
244
244
|
--query 'data.id' --raw-output 2>/dev/null
|
|
245
245
|
}
|
|
246
246
|
|
|
@@ -249,9 +249,9 @@ FLIP_POLL_SLEEP="${FLIP_POLL_SLEEP:-5}"
|
|
|
249
249
|
FLIP_ADDR_TRIES="${FLIP_ADDR_TRIES:-12}" # guest watcher pickup: 12 x 5s = 60s
|
|
250
250
|
FLIP_ACME_TRIES="${FLIP_ACME_TRIES:-24}" # LE HTTP-01: 24 x 5s = 120s
|
|
251
251
|
|
|
252
|
-
# Guest anchor protocol (the
|
|
253
|
-
# infra/alpine-host/ansible/roles/container-service/files/
|
|
254
|
-
# both halves of the conf path must stay in sync: /etc/
|
|
252
|
+
# Guest anchor protocol (the kampodine-anchor watcher half lives in
|
|
253
|
+
# infra/alpine-host/ansible/roles/container-service/files/kampodine-anchor.sh —
|
|
254
|
+
# both halves of the conf path must stay in sync: /etc/kampodine/anchor.conf).
|
|
255
255
|
# Remote commands are composed client-side BY DESIGN (vm-prepare convention);
|
|
256
256
|
# the interpolated values are OCI-API derived and regex-gated at the call
|
|
257
257
|
# sites, never user input.
|
|
@@ -260,7 +260,7 @@ FLIP_ACME_TRIES="${FLIP_ACME_TRIES:-24}" # LE HTTP-01: 24 x 5s = 120s
|
|
|
260
260
|
write_anchor_conf() {
|
|
261
261
|
local ip="$1" addr="$2"
|
|
262
262
|
ssh "${SSH_OPTS[@]}" "root@$ip" \
|
|
263
|
-
"umask 077; mkdir -p /etc/
|
|
263
|
+
"umask 077; mkdir -p /etc/kampodine; printf 'ANCHOR_ADDR=%s\nANCHOR_IFACE=\n' '$addr' > /etc/kampodine/anchor.conf && echo ANCHOR_CONF_WRITTEN"
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
# shellcheck disable=SC2029
|
|
@@ -274,7 +274,7 @@ anchor_addr_ready() {
|
|
|
274
274
|
read_anchor_conf_addr() {
|
|
275
275
|
local ip="$1" line addr
|
|
276
276
|
line="$(ssh "${SSH_OPTS[@]}" "root@$ip" \
|
|
277
|
-
'grep -h "^ANCHOR_ADDR=" /etc/
|
|
277
|
+
'grep -h "^ANCHOR_ADDR=" /etc/kampodine/anchor.conf 2>/dev/null | head -n 1' 2>/dev/null || true)"
|
|
278
278
|
addr="${line#ANCHOR_ADDR=}"
|
|
279
279
|
addr="${addr//\"/}"
|
|
280
280
|
addr="${addr//\'/}"
|
|
@@ -297,7 +297,7 @@ delete_guest_addr() {
|
|
|
297
297
|
# re-added the address once; retry the delete across one watcher interval.
|
|
298
298
|
cleanup_target_anchor() {
|
|
299
299
|
local ip="$1" addr="$2" i
|
|
300
|
-
ssh "${SSH_OPTS[@]}" "root@$ip" "rm -f /etc/
|
|
300
|
+
ssh "${SSH_OPTS[@]}" "root@$ip" "rm -f /etc/kampodine/anchor.conf" >/dev/null 2>&1 || true
|
|
301
301
|
for ((i = 1; i <= 3; i++)); do
|
|
302
302
|
delete_guest_addr "$ip" "$addr"
|
|
303
303
|
anchor_addr_ready "$ip" "$addr" >/dev/null 2>&1 || return 0
|
|
@@ -323,7 +323,7 @@ flip_failure_rollback() {
|
|
|
323
323
|
if [[ "$bpip" == ocid1.privateip* ]]; then
|
|
324
324
|
if oci network public-ip update --public-ip-id "$rocid" --profile "$PROFILE" \
|
|
325
325
|
--private-ip-id "$bpip" --force >/dev/null 2>&1; then
|
|
326
|
-
say "rolled back to
|
|
326
|
+
say "rolled back to kampodine-$ob (existing anchor $bpip)" >&2
|
|
327
327
|
recovered=1
|
|
328
328
|
fi
|
|
329
329
|
fi
|
|
@@ -331,14 +331,14 @@ flip_failure_rollback() {
|
|
|
331
331
|
if (( ! recovered )); then
|
|
332
332
|
if oci network public-ip update --public-ip-id "$rocid" --profile "$PROFILE" \
|
|
333
333
|
--private-ip-id "" --force --wait-for-state AVAILABLE >/dev/null 2>&1; then
|
|
334
|
-
say "reserved ip UNASSIGNED (dormant) — no existing anchor on
|
|
334
|
+
say "reserved ip UNASSIGNED (dormant) — no existing anchor on kampodine-$ob" >&2
|
|
335
335
|
else
|
|
336
336
|
cleanup_target_anchor "$tpub" "$taddr"
|
|
337
337
|
die "AUTO-ROLLBACK FAILED — reserved ip state unknown; flip manually via console: $rocid"
|
|
338
338
|
fi
|
|
339
339
|
fi
|
|
340
340
|
cleanup_target_anchor "$tpub" "$taddr"
|
|
341
|
-
say "
|
|
341
|
+
say "kampodine-$to cleaned (anchor.conf removed, anchor address deleted) — investigate before retrying" >&2
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
# instance_healthy <ephemeral_ip> -> ssh + loopback app check.
|
|
@@ -349,7 +349,7 @@ instance_healthy() {
|
|
|
349
349
|
local ip="$1"
|
|
350
350
|
[[ -n "$ip" ]] || return 1
|
|
351
351
|
ssh "${SSH_OPTS[@]}" "root@$ip" \
|
|
352
|
-
"rc-service
|
|
352
|
+
"rc-service kampodine-api status >/dev/null 2>&1 && busybox wget -q -O /dev/null http://127.0.0.1:8080/up" 2>/dev/null
|
|
353
353
|
}
|
|
354
354
|
|
|
355
355
|
# instance_wait_running <iid> — poll lifecycle-state to RUNNING. The OCI CLI's
|
|
@@ -365,7 +365,7 @@ instance_wait_running() {
|
|
|
365
365
|
--query 'data."lifecycle-state"' --raw-output 2>/dev/null || true)"
|
|
366
366
|
case "$state" in
|
|
367
367
|
RUNNING) return 0 ;;
|
|
368
|
-
FAILED | TERMINATED | TERMINATING) die "
|
|
368
|
+
FAILED | TERMINATED | TERMINATING) die "kampodine instance reached $state — nothing to inject, check the console" ;;
|
|
369
369
|
esac
|
|
370
370
|
sleep "$sleep_s"
|
|
371
371
|
done
|
|
@@ -390,7 +390,7 @@ inject_alpine() {
|
|
|
390
390
|
# the Alpine verify forever. The scrub below
|
|
391
391
|
# clears the phase file between the two boots; the user's file is never
|
|
392
392
|
# touched.
|
|
393
|
-
kh="$(mktemp "${TMPDIR:-/tmp}/
|
|
393
|
+
kh="$(mktemp "${TMPDIR:-/tmp}/kampodine-inject-kh.XXXXXX")"
|
|
394
394
|
iss() { ssh -o UserKnownHostsFile="$kh" "${SSH_OPTS[@]}" "$@"; }
|
|
395
395
|
say "inject: waiting for ssh (${ruser}@${ip}, platform-image first boot)…"
|
|
396
396
|
ssh_wait_probe() { iss "${ruser}@${ip}" true; }
|
|
@@ -400,7 +400,7 @@ inject_alpine() {
|
|
|
400
400
|
[[ "$i" == "$probe_tries" ]] && die "ssh never came up on ${ip} (${ruser}) — check the instance console connection"
|
|
401
401
|
sleep "$probe_sleep"
|
|
402
402
|
done
|
|
403
|
-
raw="$(mktemp "${TMPDIR:-/tmp}/
|
|
403
|
+
raw="$(mktemp "${TMPDIR:-/tmp}/kampodine-inject-raw.XXXXXX")"
|
|
404
404
|
say "inject: converting ${qcow2} -> raw…"
|
|
405
405
|
qemu-img convert -O raw "$qcow2" "$raw"
|
|
406
406
|
say "inject: streaming golden disk -> ${ip} boot volume (gunzip | dd, conv=fsync)…"
|
|
@@ -440,7 +440,7 @@ inject_alpine() {
|
|
|
440
440
|
done
|
|
441
441
|
rm -f "$kh"
|
|
442
442
|
[[ -n "$rel" ]] || die "injection streamed but no ALPINE 3.x ssh on ${ip} after reboot (got: '${rel:-nothing}') — check the serial console; terminate, do NOT flip to ${color}"
|
|
443
|
-
say "INJECTED
|
|
443
|
+
say "INJECTED kampodine-$color: Alpine ${rel} boots on ${ip} (instance image metadata stays the platform image — like green)"
|
|
444
444
|
say "next: kampodine vm-prepare --host root@${ip} -> kampodine deploy --host root@${ip}"
|
|
445
445
|
say "then 'bluegreen.sh flip --to ${color}' (health-gated) once its app checks green."
|
|
446
446
|
}
|
|
@@ -458,11 +458,12 @@ for help_arg in "$@"; do
|
|
|
458
458
|
;;
|
|
459
459
|
esac
|
|
460
460
|
done
|
|
461
|
+
[[ -n "${OCI_COMPARTMENT:-}" ]] || die "set OCI_COMPARTMENT=<compartment name or ocid> — no default: compartments are account-specific"
|
|
461
462
|
case "$cmd" in
|
|
462
463
|
status)
|
|
463
464
|
comp="$(compartment_ocid)"
|
|
464
465
|
rp="$(reserved_ip "$comp")"
|
|
465
|
-
say "==
|
|
466
|
+
say "== kampodine blue/green pair (compartment $COMPARTMENT_NAME) =="
|
|
466
467
|
if [[ -n "$rp" ]]; then
|
|
467
468
|
read -r rocid raddr rholder <<<"$rp"
|
|
468
469
|
say "reserved IP : $raddr ($rocid)"
|
|
@@ -475,9 +476,9 @@ case "$cmd" in
|
|
|
475
476
|
if [[ -n "$row" ]]; then
|
|
476
477
|
read -r iid pub ad <<<"$row"
|
|
477
478
|
if instance_healthy "$pub"; then verdict=HEALTHY; else verdict=UNHEALTHY/unreachable; fi
|
|
478
|
-
say "
|
|
479
|
+
say "kampodine-$color : $iid ip=${pub:-none} ad=${ad:-?} app=$verdict"
|
|
479
480
|
else
|
|
480
|
-
say "
|
|
481
|
+
say "kampodine-$color : not provisioned"
|
|
481
482
|
fi
|
|
482
483
|
done
|
|
483
484
|
;;
|
|
@@ -491,7 +492,7 @@ case "$cmd" in
|
|
|
491
492
|
exit 0
|
|
492
493
|
fi
|
|
493
494
|
addr="$(oci network public-ip create -c "$comp" --profile "$PROFILE" \
|
|
494
|
-
--lifetime RESERVED --display-name
|
|
495
|
+
--lifetime RESERVED --display-name kampodine-active \
|
|
495
496
|
--query 'data."ip-address"' --raw-output)"
|
|
496
497
|
say "created DORMANT reserved IP: $addr (unassigned — no instance attached)"
|
|
497
498
|
say "Point DNS/SSLIP at this address when the pair goes active."
|
|
@@ -502,25 +503,25 @@ case "$cmd" in
|
|
|
502
503
|
color="${1:-}"
|
|
503
504
|
[[ "$color" == blue || "$color" == green ]] || die "usage: kampodine bluegreen provision <blue|green>"
|
|
504
505
|
comp="$(compartment_ocid)"
|
|
505
|
-
instance_by_color "$comp" "$color" | grep -q . && die "
|
|
506
|
+
instance_by_color "$comp" "$color" | grep -q . && die "kampodine-$color already RUNNING"
|
|
506
507
|
# base: the OTHER color's AD + subnet (same fault domain layout), golden image
|
|
507
508
|
other=green; [[ "$color" == green ]] && other=blue
|
|
508
509
|
orow="$(instance_by_color "$comp" "$other")"
|
|
509
|
-
[[ -n "$orow" ]] || die "
|
|
510
|
+
[[ -n "$orow" ]] || die "kampodine-$other not RUNNING — need its AD/subnet as the pair template"
|
|
510
511
|
# instance_by_color rows are "<ocid> <ephemeral_public_ip> <ad>" — field 2
|
|
511
512
|
# is the IP, the AD is field 3 (launching with the IP as AD fails).
|
|
512
513
|
read -r oiid _ oad <<<"$orow"
|
|
513
514
|
subnet="$(oci compute vnic-attachment list -c "$comp" --profile "$PROFILE" \
|
|
514
515
|
--instance-id "$oiid" --query 'data[0]."subnet-id"' --raw-output)"
|
|
515
516
|
|
|
516
|
-
# Route selection. NATIVE only with a UEFI_64
|
|
517
|
+
# Route selection. NATIVE only with a UEFI_64 kampodine-alpine* custom image:
|
|
517
518
|
# OCI pins IMPORTED images to firmware=BIOS and A1 is UEFI-only, so a BIOS
|
|
518
519
|
# verdict means the import would die at launch (Shape ... is not valid for
|
|
519
520
|
# image) — skip it. Otherwise take the template's LIVE image-id from its
|
|
520
521
|
# instance record and inject the golden disk (provision-via-migrate).
|
|
521
522
|
mode="" image=""
|
|
522
523
|
custom="$(oci compute image list -c "$comp" --profile "$PROFILE" --all --sort-by TIMECREATED \
|
|
523
|
-
--query "data[?\"display-name\" != null && starts_with(\"display-name\", '
|
|
524
|
+
--query "data[?\"display-name\" != null && starts_with(\"display-name\", 'kampodine-alpine')] | [0].id" \
|
|
524
525
|
--raw-output 2>/dev/null || true)"
|
|
525
526
|
if [[ "$custom" == ocid1.image* ]]; then
|
|
526
527
|
fw="$(oci compute image get --image-id "$custom" --profile "$PROFILE" \
|
|
@@ -528,7 +529,7 @@ case "$cmd" in
|
|
|
528
529
|
if [[ "$fw" == "UEFI_64" ]]; then
|
|
529
530
|
image="$custom" mode="native"
|
|
530
531
|
else
|
|
531
|
-
say "note: newest
|
|
532
|
+
say "note: newest kampodine-alpine* custom image is firmware=${fw:-unknown} — A1 rejects BIOS-pinned imports, skipping to the platform-image + injection route"
|
|
532
533
|
fi
|
|
533
534
|
fi
|
|
534
535
|
if [[ -z "$image" ]]; then
|
|
@@ -536,7 +537,7 @@ case "$cmd" in
|
|
|
536
537
|
--query 'data."image-id"' --raw-output 2>/dev/null || true)"
|
|
537
538
|
[[ "$image" == ocid1.image* ]] || die "template instance has no resolvable image-id — cannot launch or inject"
|
|
538
539
|
mode="inject"
|
|
539
|
-
qcow2="${ALPINE_QCOW2:-${REPO_ROOT}/infra/alpine-host/build/
|
|
540
|
+
qcow2="${ALPINE_QCOW2:-${REPO_ROOT}/infra/alpine-host/build/kampodine-alpine-3.22.6-aarch64.qcow2}"
|
|
540
541
|
[[ -f "$qcow2" ]] || die "golden qcow2 not found: $qcow2 (build via packer, or set ALPINE_QCOW2)"
|
|
541
542
|
command -v qemu-img >/dev/null 2>&1 || die "qemu-img not found in PATH (brew install qemu) — required for the qcow2 -> raw conversion"
|
|
542
543
|
# ops ssh public key for the platform-image first boot. OPS_SSH_PUBKEY
|
|
@@ -549,22 +550,22 @@ case "$cmd" in
|
|
|
549
550
|
keyfile="$OPS_SSH_PUBKEY"
|
|
550
551
|
else
|
|
551
552
|
agent_keys="$(ssh-add -L 2>/dev/null | grep -v '\.pub$' || true)"
|
|
552
|
-
[[ -n "$agent_keys" ]] || die "no ssh key available: OPS_SSH_PUBKEY unset and ssh-add lists no keys (ssh-add ~/.ssh/id_ed25519-
|
|
553
|
-
keyfile="$(mktemp "${TMPDIR:-/tmp}/
|
|
553
|
+
[[ -n "$agent_keys" ]] || die "no ssh key available: OPS_SSH_PUBKEY unset and ssh-add lists no keys (ssh-add ~/.ssh/id_ed25519-kampodine, or set OPS_SSH_PUBKEY)"
|
|
554
|
+
keyfile="$(mktemp "${TMPDIR:-/tmp}/kampodine-ops-pubkey.XXXXXX")"
|
|
554
555
|
printf '%s\n' "$agent_keys" > "$keyfile"
|
|
555
556
|
chmod 600 "$keyfile"
|
|
556
557
|
fi
|
|
557
558
|
fi
|
|
558
559
|
|
|
559
|
-
say "launching
|
|
560
|
+
say "launching kampodine-$color: image=${image:0:60}… ad=$oad subnet=${subnet:0:60}… mode=$mode"
|
|
560
561
|
# NOTE: --profile stays ON the launch line (script-gates static scan reads
|
|
561
562
|
# the invocation line, not array contents).
|
|
562
563
|
launch_args=(-c "$comp" --availability-domain "$oad" --subnet-id "$subnet"
|
|
563
564
|
--image-id "$image" --shape VM.Standard.A1.Flex --shape-config '{"ocpus":2,"memoryInGBs":12}'
|
|
564
|
-
--assign-public-ip true --display-name "
|
|
565
|
+
--assign-public-ip true --display-name "kampodine-$color")
|
|
565
566
|
[[ "$mode" == "inject" ]] && launch_args+=(--ssh-authorized-keys-file "$keyfile")
|
|
566
567
|
iid="$(oci compute instance launch "${launch_args[@]}" --profile "$PROFILE" --query 'data.id' --raw-output)"
|
|
567
|
-
say "LAUNCHED
|
|
568
|
+
say "LAUNCHED kampodine-$color: $iid"
|
|
568
569
|
if [[ "$mode" == "native" ]]; then
|
|
569
570
|
say "next: wait RUNNING -> ssh in -> kampodine vm-prepare -> kampodine deploy --host root@<ephemeral-ip>"
|
|
570
571
|
say "then 'bluegreen.sh flip --to $color' (health-gated) once its app checks green."
|
|
@@ -594,16 +595,16 @@ case "$cmd" in
|
|
|
594
595
|
read -r rocid raddr _ <<<"$rp"
|
|
595
596
|
rhost="${raddr//./-}.sslip.io"
|
|
596
597
|
trow="$(instance_by_color "$comp" "$to")"
|
|
597
|
-
[[ -n "$trow" ]] || die "
|
|
598
|
+
[[ -n "$trow" ]] || die "kampodine-$to is not RUNNING — nothing to flip to"
|
|
598
599
|
read -r tiid tpub _ <<<"$trow"
|
|
599
600
|
tvnic="$(primary_vnic_of "$tiid")"
|
|
600
|
-
[[ -n "$tvnic" ]] || die "no primary VNIC on
|
|
601
|
+
[[ -n "$tvnic" ]] || die "no primary VNIC on kampodine-$to"
|
|
601
602
|
tpip="$(reserved_anchor_ip "$tvnic")"
|
|
602
|
-
[[ "$tpip" == ocid1.privateip* ]] || die "could not resolve/create the reserved-anchor secondary private ip on
|
|
603
|
+
[[ "$tpip" == ocid1.privateip* ]] || die "could not resolve/create the reserved-anchor secondary private ip on kampodine-$to"
|
|
603
604
|
if instance_healthy "$tpub"; then
|
|
604
|
-
say "target health:
|
|
605
|
+
say "target health: kampodine-$to app HEALTHY on its own IP"
|
|
605
606
|
else
|
|
606
|
-
(( force )) || die "
|
|
607
|
+
(( force )) || die "kampodine-$to app UNHEALTHY — refusing flip (override: --force)"
|
|
607
608
|
say "target health: UNHEALTHY — flipping anyway (--force)"
|
|
608
609
|
fi
|
|
609
610
|
|
|
@@ -611,19 +612,19 @@ case "$cmd" in
|
|
|
611
612
|
# once the hostname resolves to the reserved ip AND routes to the target,
|
|
612
613
|
# so the guest anchor address goes FIRST, then the assign, then the cert.
|
|
613
614
|
ob=green; [[ "$to" == green ]] && ob=blue
|
|
614
|
-
say "flip[1/4]: anchor conf ->
|
|
615
|
+
say "flip[1/4]: anchor conf -> kampodine-$to guest ($tpub), watcher configures the address"
|
|
615
616
|
tsubnet="$(oci compute vnic-attachment list -c "$comp" --profile "$PROFILE" \
|
|
616
617
|
--instance-id "$tiid" --query 'data[0]."subnet-id"' --raw-output 2>/dev/null || true)"
|
|
617
|
-
[[ "$tsubnet" == ocid1.subnet* ]] || die "could not resolve
|
|
618
|
+
[[ "$tsubnet" == ocid1.subnet* ]] || die "could not resolve kampodine-$to's subnet id"
|
|
618
619
|
tcidr="$(oci network subnet get --subnet-id "$tsubnet" --profile "$PROFILE" \
|
|
619
620
|
--query 'data."cidr-block"' --raw-output 2>/dev/null || true)"
|
|
620
|
-
[[ "$tcidr" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$ ]] || die "could not resolve
|
|
621
|
+
[[ "$tcidr" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$ ]] || die "could not resolve kampodine-$to's subnet cidr (got: ${tcidr:-none})"
|
|
621
622
|
tanchor_addr="$(oci network private-ip get --private-ip-id "$tpip" --profile "$PROFILE" \
|
|
622
623
|
--query 'data."ip-address"' --raw-output 2>/dev/null || true)"
|
|
623
|
-
[[ "$tanchor_addr" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "could not resolve the anchor private address on
|
|
624
|
+
[[ "$tanchor_addr" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "could not resolve the anchor private address on kampodine-$to"
|
|
624
625
|
taddr_cidr="${tanchor_addr}/${tcidr##*/}"
|
|
625
626
|
if ! write_anchor_conf "$tpub" "$taddr_cidr" >/dev/null 2>&1; then
|
|
626
|
-
die "anchor.conf write failed on
|
|
627
|
+
die "anchor.conf write failed on kampodine-$to ($tpub) — nothing mutated, flip aborted"
|
|
627
628
|
fi
|
|
628
629
|
addr_ok=0
|
|
629
630
|
for ((i = 1; i <= FLIP_ADDR_TRIES; i++)); do
|
|
@@ -632,15 +633,15 @@ case "$cmd" in
|
|
|
632
633
|
done
|
|
633
634
|
if (( ! addr_ok )); then
|
|
634
635
|
cleanup_target_anchor "$tpub" "$taddr_cidr"
|
|
635
|
-
die "guest watcher never configured $taddr_cidr on
|
|
636
|
+
die "guest watcher never configured $taddr_cidr on kampodine-$to — is rc-service kampodine-anchor running? (conf removed, NOTHING mutated)"
|
|
636
637
|
fi
|
|
637
|
-
say "flip[2/4]: guest answers on $taddr_cidr — reserved $raddr ->
|
|
638
|
+
say "flip[2/4]: guest answers on $taddr_cidr — reserved $raddr -> kampodine-$to (anchor $tpip)"
|
|
638
639
|
oci network public-ip update --public-ip-id "$rocid" --profile "$PROFILE" \
|
|
639
640
|
--private-ip-id "$tpip" --force --wait-for-state ASSIGNED >/dev/null \
|
|
640
641
|
|| { cleanup_target_anchor "$tpub" "$taddr_cidr"; die "OCI flip call failed — guest conf removed; run 'kampodine bluegreen status'"; }
|
|
641
|
-
say "flip[3/4]: reserved IP ASSIGNED — registering $rhost on
|
|
642
|
+
say "flip[3/4]: reserved IP ASSIGNED — registering $rhost on kampodine-$to's kamal-proxy (ACME HTTP-01 through the reserved ip)…"
|
|
642
643
|
if ! ssh "${SSH_OPTS[@]}" "root@$tpub" \
|
|
643
|
-
"podman exec kamal-proxy kamal-proxy deploy
|
|
644
|
+
"podman exec kamal-proxy kamal-proxy deploy kampodine-api --host=$rhost --target=kampodine-api:8080 --tls --health-check-path=/api/auth/ok"; then
|
|
644
645
|
flip_failure_rollback "$to" "$ob" "$comp" "$rocid" "$tpub" "$taddr_cidr"
|
|
645
646
|
exit 1
|
|
646
647
|
fi
|
|
@@ -656,14 +657,14 @@ case "$cmd" in
|
|
|
656
657
|
fi
|
|
657
658
|
served="$(curl -s -m 8 --resolve "$rhost:443:$raddr" "https://$rhost/api/auth/ok" || true)"
|
|
658
659
|
say "flip[4/4]: cert for $rhost VALID + serving through $raddr"
|
|
659
|
-
say "FLIPPED: https://$raddr/ (https://$rhost/) now serves from
|
|
660
|
+
say "FLIPPED: https://$raddr/ (https://$rhost/) now serves from kampodine-$to"
|
|
660
661
|
say "served /api/auth/ok: ${served:-<no body>}"
|
|
661
662
|
;;
|
|
662
663
|
|
|
663
664
|
rollback)
|
|
664
665
|
# DORMANT rollback: holder guest cleanup
|
|
665
666
|
# (anchor.conf removal + anchor address delete — the flip tool's explicit
|
|
666
|
-
# job; the
|
|
667
|
+
# job; the kampodine-anchor watcher is add-only) then the OCI unassign
|
|
667
668
|
# (documented CLI semantics: an empty --private-ip-id unassigns). For a
|
|
668
669
|
# real pair where traffic must land on the other color, use
|
|
669
670
|
# 'flip --to <other>' — it runs the full ACME-first sequence there.
|
|
@@ -692,10 +693,10 @@ case "$cmd" in
|
|
|
692
693
|
if [[ -z "$holder_color" ]]; then
|
|
693
694
|
say "warning: reserved $raddr held by an anchor of neither RUNNING color (terminated instance?) — unassigning without guest cleanup"
|
|
694
695
|
else
|
|
695
|
-
say "current holder:
|
|
696
|
+
say "current holder: kampodine-$holder_color ($holder_ip) — cleaning the guest anchor, then unassigning"
|
|
696
697
|
conf_addr="$(read_anchor_conf_addr "$holder_ip")"
|
|
697
698
|
cleanup_target_anchor "$holder_ip" "$conf_addr"
|
|
698
|
-
say "
|
|
699
|
+
say "kampodine-$holder_color guest cleaned (anchor.conf removed${conf_addr:+, address $conf_addr deleted})"
|
|
699
700
|
fi
|
|
700
701
|
say "unassigning reserved $raddr (-> dormant)…"
|
|
701
702
|
oci network public-ip update --public-ip-id "$rocid" --profile "$PROFILE" \
|
package/scripts/deploy.sh
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
# Mac: podman build (VITE_BUILD_ID=<sha>) -> podman save (docker-archive)
|
|
5
5
|
# streamed over SSH -> podman load on the VM
|
|
6
|
-
# VM: retag to 127.0.0.1:5000/
|
|
6
|
+
# VM: retag to 127.0.0.1:5000/kampodine-api:latest -> rc-service kampodine-api
|
|
7
7
|
# restart (the OpenRC supervise-daemon service re-runs the whole
|
|
8
8
|
# `podman run` against :latest — no pull in the run, so restarts are
|
|
9
9
|
# offline-safe) -> exec-fetch health probe with served-sha verify ->
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
set -euo pipefail
|
|
27
27
|
|
|
28
28
|
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
|
29
|
-
|
|
29
|
+
KAMPODINE_HOST="${KAMPODINE_HOST:-}"
|
|
30
30
|
PROXY_HOST="${PROXY_HOST:-app.example.com}"
|
|
31
|
-
ENV_FILE_REMOTE="/etc/
|
|
32
|
-
DEPLOYED_SHA_FILE="/etc/
|
|
31
|
+
ENV_FILE_REMOTE="/etc/kampodine/env"
|
|
32
|
+
DEPLOYED_SHA_FILE="/etc/kampodine/deployed-sha"
|
|
33
33
|
ENV_CLEAR_KEYS='^(NODE_ENV|PORT|LIBSQL_TENANT_DIR|LIBSQL_API_MOUNT|STATIC_SPA_MOUNT)='
|
|
34
|
-
IMAGE="127.0.0.1:5000/
|
|
34
|
+
IMAGE="127.0.0.1:5000/kampodine-api"
|
|
35
35
|
|
|
36
36
|
MODE="deploy"
|
|
37
37
|
VERSION=""
|
|
@@ -40,8 +40,7 @@ REFRESH_CONFIG=0
|
|
|
40
40
|
# SSH key resolution — GENERIC, no hardcoded personal paths:
|
|
41
41
|
# 1. --ssh-key flag (explicit, per-invocation)
|
|
42
42
|
# 2. KAMPODINE_SSH_KEY env (project-level: direnv / .envrc / export)
|
|
43
|
-
# 3.
|
|
44
|
-
# 4. empty → ssh-agent and/or the operator's ~/.ssh/config Host block
|
|
43
|
+
# 3. empty → ssh-agent and/or the operator's ~/.ssh/config Host block
|
|
45
44
|
# (the POSIX way: per-host IdentityFile belongs in ssh config, not here)
|
|
46
45
|
if [ -n "${KAMPODINE_SSH_KEY:-}" ]; then
|
|
47
46
|
SSH_KEY="$KAMPODINE_SSH_KEY"
|
|
@@ -66,15 +65,15 @@ EOF
|
|
|
66
65
|
grep '^# kampodine deploy' "$0" | sed 's/^# //'
|
|
67
66
|
cat <<'EOF'
|
|
68
67
|
|
|
69
|
-
Host/key resolution: --host |
|
|
70
|
-
|
|
68
|
+
Host/key resolution: --host | KAMPODINE_HOST; --ssh-key | KAMPODINE_SSH_KEY |
|
|
69
|
+
KAMPODINE_SSH_KEY | ssh-agent / ~/.ssh/config.
|
|
71
70
|
EOF
|
|
72
71
|
exit 0
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
while [[ $# -gt 0 ]]; do
|
|
76
75
|
case "$1" in
|
|
77
|
-
--host)
|
|
76
|
+
--host) KAMPODINE_HOST="$2"; shift 2 ;;
|
|
78
77
|
--ssh-key) SSH_KEY="$2"; shift 2 ;;
|
|
79
78
|
--version) VERSION="$2"; MODE="version"; shift 2 ;;
|
|
80
79
|
--rollback)
|
|
@@ -89,13 +88,13 @@ while [[ $# -gt 0 ]]; do
|
|
|
89
88
|
esac
|
|
90
89
|
done
|
|
91
90
|
[[ "$VERSION" != *..* && "$VERSION" =~ ^[0-9a-f]{4,40}$|^$ ]] || die "--version must be a git sha fragment"
|
|
92
|
-
[ -n "$
|
|
91
|
+
[ -n "$KAMPODINE_HOST" ] || die "set KAMPODINE_HOST=root@<vm-ip> (or a ~/.ssh/config Host alias via --host)"
|
|
93
92
|
|
|
94
93
|
SSH_ARGS=(-o ConnectTimeout=10 -o BatchMode=yes)
|
|
95
94
|
[[ -n "$SSH_KEY" ]] && SSH_ARGS+=(-i "$SSH_KEY")
|
|
96
95
|
# $1 is a composed remote command — client-side expansion is the design.
|
|
97
96
|
# shellcheck disable=SC2029
|
|
98
|
-
vm() { ssh "${SSH_ARGS[@]}" "$
|
|
97
|
+
vm() { ssh "${SSH_ARGS[@]}" "$KAMPODINE_HOST" "$1"; }
|
|
99
98
|
|
|
100
99
|
# --- macOS ssh-agent quirk (first deploy from a fresh machine) ---------------
|
|
101
100
|
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
@@ -141,7 +140,7 @@ vm "podman tag $IMAGE:$VER $IMAGE:latest" || die "stream retag failed"
|
|
|
141
140
|
|
|
142
141
|
# --- env file from varlock (schema-only from HEAD; secrets from pass) ----------
|
|
143
142
|
say "generating $ENV_FILE_REMOTE from varlock (schema-only from HEAD; secrets resolve from pass — no plaintext secret files)…"
|
|
144
|
-
ENV_TMP="$(mktemp /tmp/
|
|
143
|
+
ENV_TMP="$(mktemp /tmp/kampodine-env.XXXXXX)"
|
|
145
144
|
trap 'rm -f "$ENV_TMP"' EXIT
|
|
146
145
|
# Schema-only resolution: extract the COMMITTED schema into a scratch dir
|
|
147
146
|
# inside the repo (plugin resolution needs node_modules; local
|
|
@@ -158,20 +157,20 @@ git -C "$REPO_ROOT" archive HEAD apps/api/.env.schema | tar -x -C "$ENV_SCHEMA_D
|
|
|
158
157
|
| sed -E 's/^([A-Za-z_][A-Za-z0-9_]*)="(.*)"$/\1=\2/' > "$ENV_TMP" \
|
|
159
158
|
|| die "varlock env generation failed (pass store unlocked? schema committed?)"
|
|
160
159
|
printf 'API_GIT_SHA=%s\n' "$VER" >> "$ENV_TMP"
|
|
161
|
-
scp -q "${SSH_ARGS[@]}" "$ENV_TMP" "$
|
|
162
|
-
vm "mv /etc/
|
|
160
|
+
scp -q "${SSH_ARGS[@]}" "$ENV_TMP" "$KAMPODINE_HOST:/etc/kampodine/env.tmp"
|
|
161
|
+
vm "mv /etc/kampodine/env.tmp $ENV_FILE_REMOTE && chmod 600 $ENV_FILE_REMOTE"
|
|
163
162
|
rm -f "$ENV_TMP"
|
|
164
163
|
rm -rf "$ENV_SCHEMA_DIR"
|
|
165
164
|
|
|
166
165
|
if [[ $REFRESH_CONFIG -eq 1 ]]; then
|
|
167
166
|
say "ansible container-service refresh (--tags container-service — rc script/config drift repair)…"
|
|
168
167
|
(cd "$REPO_ROOT/infra/alpine-host/ansible" \
|
|
169
|
-
&& ansible-playbook -i "
|
|
168
|
+
&& ansible-playbook -i "kampodine-vm ansible_host=${KAMPODINE_HOST#*@},ansible_user=${KAMPODINE_HOST%%@*}," playbook.yml --tags container-service --private-key "${SSH_KEY:-~/.ssh/id_ed25519}") \
|
|
170
169
|
|| die "ansible playbook failed"
|
|
171
170
|
fi
|
|
172
171
|
|
|
173
|
-
say "rc-service
|
|
174
|
-
vm "rc-service
|
|
172
|
+
say "rc-service kampodine-api restart (supervise-daemon: stop-old/start-new podman run on the retagged :latest)…"
|
|
173
|
+
vm "rc-service kampodine-api restart" || die "kampodine-api restart failed (rc-service kampodine-api status)"
|
|
175
174
|
|
|
176
175
|
say "health probe (host-side wget /api/auth/ok + served-sha — works for node AND scratch images)…"
|
|
177
176
|
HEALTH_OK=0
|
|
@@ -188,17 +187,17 @@ for _ in $(seq 1 30); do
|
|
|
188
187
|
sleep 3
|
|
189
188
|
done
|
|
190
189
|
if [[ $HEALTH_OK -ne 1 ]]; then
|
|
191
|
-
vm "podman logs --tail 30
|
|
192
|
-
vm "rc-service
|
|
190
|
+
vm "podman logs --tail 30 kampodine-api 2>&1" || true
|
|
191
|
+
vm "rc-service kampodine-api status 2>&1" || true
|
|
193
192
|
PREV_HINT="$(vm "test -f $DEPLOYED_SHA_FILE && cat $DEPLOYED_SHA_FILE" 2>/dev/null || true)"
|
|
194
193
|
die "container never became healthy — rollback: kampodine deploy --rollback${PREV_HINT:+ $PREV_HINT}"
|
|
195
194
|
fi
|
|
196
|
-
CANARY="$(vm "podman logs
|
|
195
|
+
CANARY="$(vm "podman logs kampodine-api 2>&1 | grep -c ENV_IMPORT_SUSPECT || true")"
|
|
197
196
|
say "container healthy, env-canary hits: $CANARY"
|
|
198
197
|
|
|
199
198
|
say "kamal-proxy re-point (podman exec — same invocation kamal used)…"
|
|
200
199
|
if vm "podman ps --format '{{.Names}}' | grep -qx kamal-proxy"; then
|
|
201
|
-
vm "podman exec kamal-proxy kamal-proxy deploy
|
|
200
|
+
vm "podman exec kamal-proxy kamal-proxy deploy kampodine-api --host=$PROXY_HOST --target=kampodine-api:8080 --tls --health-check-path=/api/auth/ok" \
|
|
202
201
|
|| die "proxy re-point failed (podman logs kamal-proxy)"
|
|
203
202
|
else
|
|
204
203
|
say " kamal-proxy not running — skipping re-point"
|
package/scripts/dns.sh
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
# Types are pinned to A | AAAA | CNAME. `add` merges into the existing RRSet
|
|
13
13
|
# (round-robin records survive); `rm` filters it; both are idempotence-aware.
|
|
14
14
|
#
|
|
15
|
-
# Zone/compartment: OCI_PROFILE (default
|
|
16
|
-
# (
|
|
15
|
+
# Zone/compartment: OCI_PROFILE (default: "default"), OCI_COMPARTMENT (required)
|
|
16
|
+
# (required). Zone defaults to the compartment's ONLY zone; pass
|
|
17
17
|
# --zone <id-or-name> when several exist.
|
|
18
18
|
#
|
|
19
19
|
# Usage:
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
# (all: optional --zone <id-or-name>; --instance-principal for instance auth)
|
|
24
24
|
set -euo pipefail
|
|
25
25
|
|
|
26
|
-
PROFILE="${OCI_PROFILE:-
|
|
27
|
-
COMPARTMENT_NAME="${OCI_COMPARTMENT:-
|
|
26
|
+
PROFILE="${OCI_PROFILE:-default}"
|
|
27
|
+
COMPARTMENT_NAME="${OCI_COMPARTMENT:-kampodine}"
|
|
28
28
|
AUTH_ARGS=()
|
|
29
29
|
|
|
30
30
|
say() { printf '\033[1;34m[dns]\033[0m %s\n' "$*"; }
|
|
@@ -37,13 +37,13 @@ usage() {
|
|
|
37
37
|
|
|
38
38
|
Auth is the oci CLI's own — its config file (--profile) or instance
|
|
39
39
|
principal. kampodine never accepts, stores, or logs credential material.
|
|
40
|
-
Zone/compartment: OCI_PROFILE (default
|
|
41
|
-
|
|
40
|
+
Zone/compartment: OCI_PROFILE (default: "default"), OCI_COMPARTMENT (default
|
|
41
|
+
kampodine). Types are pinned to A | AAAA | CNAME; ttl range 60..172800.
|
|
42
42
|
|
|
43
43
|
Examples:
|
|
44
44
|
kampodine dns records
|
|
45
|
-
kampodine dns records --zone
|
|
46
|
-
kampodine dns add --name app --type A --value 203.0.113.10 --zone
|
|
45
|
+
kampodine dns records --zone kampodine.example.com
|
|
46
|
+
kampodine dns add --name app --type A --value 203.0.113.10 --zone kampodine.example.com
|
|
47
47
|
kampodine dns add --name www --type CNAME --value app.example.com --ttl 3600
|
|
48
48
|
kampodine dns rm --name app --type A --value 203.0.113.10
|
|
49
49
|
EOF
|
|
@@ -111,6 +111,8 @@ need_provider() {
|
|
|
111
111
|
|| die "oci CLI not found — brew install oci-cli (https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/cliinstall.htm), then: oci setup config"
|
|
112
112
|
command -v jq >/dev/null 2>&1 \
|
|
113
113
|
|| die "jq not found — brew install jq (dns.sh uses it for RRSet surgery)"
|
|
114
|
+
[[ -n "${OCI_COMPARTMENT:-}" ]] \
|
|
115
|
+
|| die "set OCI_COMPARTMENT=<compartment name or ocid> — no default: compartments are account-specific"
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
compartment_ocid() {
|
package/scripts/env.sh
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# env.sh — manage the remote app env file (/etc/
|
|
2
|
+
# env.sh — manage the remote app env file (/etc/kampodine/env, 0600 root).
|
|
3
3
|
#
|
|
4
4
|
# SECRETS NEVER PRINT. `env list`, `env push`, and the `env pull` summaries
|
|
5
5
|
# surface FINGERPRINTS ONLY: KEY + value length + first 2 characters. Raw
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
# secret ever lands in terminal output, logs, or process args.
|
|
9
9
|
#
|
|
10
10
|
# Host/key resolution is IDENTICAL to deploy.sh:
|
|
11
|
-
# --host root@<ip> |
|
|
12
|
-
# --ssh-key <path> | KAMPODINE_SSH_KEY |
|
|
11
|
+
# --host root@<ip> | KAMPODINE_HOST | ~/.ssh/config Host alias
|
|
12
|
+
# --ssh-key <path> | KAMPODINE_SSH_KEY |
|
|
13
13
|
# ssh-agent (the POSIX default; per-host IdentityFile belongs in ssh config)
|
|
14
14
|
#
|
|
15
15
|
# Usage:
|
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
# file you push unless you mean to own them here.
|
|
24
24
|
set -euo pipefail
|
|
25
25
|
|
|
26
|
-
ENV_FILE_REMOTE="${KAMPODINE_ENV_REMOTE:-/etc/
|
|
26
|
+
ENV_FILE_REMOTE="${KAMPODINE_ENV_REMOTE:-/etc/kampodine/env}"
|
|
27
27
|
ENV_TMP_BASE="$(dirname "$ENV_FILE_REMOTE")"
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
KAMPODINE_HOST="${KAMPODINE_HOST:-}"
|
|
30
30
|
# SSH key resolution — same ladder as deploy.sh:
|
|
31
31
|
# 1. --ssh-key flag (explicit, per-invocation)
|
|
32
32
|
# 2. KAMPODINE_SSH_KEY env (project-level: direnv / .envrc / export)
|
|
33
|
-
# 3.
|
|
33
|
+
# 3. KAMPODINE_SSH_KEY env (legacy alias, kept for existing setups)
|
|
34
34
|
# 4. empty → ssh-agent and/or the operator's ~/.ssh/config Host block
|
|
35
35
|
if [ -n "${KAMPODINE_SSH_KEY:-}" ]; then
|
|
36
36
|
SSH_KEY="$KAMPODINE_SSH_KEY"
|
|
@@ -47,8 +47,8 @@ usage() {
|
|
|
47
47
|
|
|
48
48
|
Fingerprints NEVER leak values: every line is KEY + value length + first 2 chars.
|
|
49
49
|
Raw values move only in push's upload stream and pull's stdout/--out payload.
|
|
50
|
-
Host/key resolution matches deploy.sh: --host |
|
|
51
|
-
KAMPODINE_SSH_KEY |
|
|
50
|
+
Host/key resolution matches deploy.sh: --host | KAMPODINE_HOST; --ssh-key |
|
|
51
|
+
KAMPODINE_SSH_KEY | ssh-agent / ~/.ssh/config.
|
|
52
52
|
|
|
53
53
|
Examples:
|
|
54
54
|
kampodine env list --host root@203.0.113.10
|
|
@@ -72,7 +72,7 @@ FILE=""
|
|
|
72
72
|
OUT=""
|
|
73
73
|
while [[ $# -gt 0 ]]; do
|
|
74
74
|
case "$1" in
|
|
75
|
-
--host)
|
|
75
|
+
--host) KAMPODINE_HOST="$2"; shift 2 ;;
|
|
76
76
|
--ssh-key) SSH_KEY="$2"; shift 2 ;;
|
|
77
77
|
--file) FILE="$2"; shift 2 ;;
|
|
78
78
|
--out) OUT="$2"; shift 2 ;;
|
|
@@ -85,7 +85,7 @@ SSH_ARGS=(-o ConnectTimeout=10 -o BatchMode=yes)
|
|
|
85
85
|
[[ -n "$SSH_KEY" ]] && SSH_ARGS+=(-i "$SSH_KEY")
|
|
86
86
|
# $1 is a composed remote command — client-side expansion is the design.
|
|
87
87
|
# shellcheck disable=SC2029
|
|
88
|
-
vm() { ssh "${SSH_ARGS[@]}" "$
|
|
88
|
+
vm() { ssh "${SSH_ARGS[@]}" "$KAMPODINE_HOST" "$1"; }
|
|
89
89
|
|
|
90
90
|
# --- macOS ssh-agent quirk (first run from a fresh machine) -------------------
|
|
91
91
|
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
@@ -94,7 +94,7 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
|
94
94
|
fi
|
|
95
95
|
|
|
96
96
|
require_host() {
|
|
97
|
-
[[ -n "$
|
|
97
|
+
[[ -n "$KAMPODINE_HOST" ]] || die "target required: --host root@<ip> or KAMPODINE_HOST=root@<ip> (resolution matches deploy.sh)"
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
# env_fp_lines — env content on stdin -> fingerprint table on stdout.
|
|
@@ -126,7 +126,7 @@ env_fp_lines() {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
fetch_remote_env() {
|
|
129
|
-
vm "cat $ENV_FILE_REMOTE" || die "cannot read $ENV_FILE_REMOTE on $
|
|
129
|
+
vm "cat $ENV_FILE_REMOTE" || die "cannot read $ENV_FILE_REMOTE on $KAMPODINE_HOST (no env file yet? run: kampodine env push --file <env>, or kampodine deploy)"
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
case "$SUB" in
|
|
@@ -142,7 +142,7 @@ case "$SUB" in
|
|
|
142
142
|
require_host
|
|
143
143
|
count="$(grep -cE '^[A-Za-z_][A-Za-z0-9_]*=' "$FILE" || true)"
|
|
144
144
|
[[ "${count:-0}" -gt 0 ]] || die "no KEY=VALUE lines in $FILE — nothing to push"
|
|
145
|
-
say "pushing $FILE -> $
|
|
145
|
+
say "pushing $FILE -> $KAMPODINE_HOST:$ENV_FILE_REMOTE (fingerprint summary below; values are NEVER printed)"
|
|
146
146
|
env_fp_lines < "$FILE"
|
|
147
147
|
tmp_remote="$ENV_TMP_BASE/env.tmp.$$"
|
|
148
148
|
# 0600 FROM CREATION: umask 077 + ssh stdin pipe — the temp is never
|
|
@@ -151,8 +151,8 @@ case "$SUB" in
|
|
|
151
151
|
vm "umask 077; cat > $tmp_remote" < "$FILE" || die "upload failed"
|
|
152
152
|
vm "chmod 600 $tmp_remote && mv -f $tmp_remote $ENV_FILE_REMOTE" \
|
|
153
153
|
|| die "atomic install failed (remote temp left at: $tmp_remote)"
|
|
154
|
-
say "installed $ENV_FILE_REMOTE (0600) on $
|
|
155
|
-
say "restart to apply: ssh $
|
|
154
|
+
say "installed $ENV_FILE_REMOTE (0600) on $KAMPODINE_HOST"
|
|
155
|
+
say "restart to apply: ssh $KAMPODINE_HOST 'rc-service kampodine-api restart' # or: kampodine deploy"
|
|
156
156
|
;;
|
|
157
157
|
|
|
158
158
|
pull)
|
|
@@ -167,7 +167,7 @@ case "$SUB" in
|
|
|
167
167
|
# stdout IS the payload (pipe into whatever needs the values);
|
|
168
168
|
# the human-readable summary goes to stderr, masked.
|
|
169
169
|
printf '%s\n' "$raw"
|
|
170
|
-
say "fingerprint summary for $ENV_FILE_REMOTE on $
|
|
170
|
+
say "fingerprint summary for $ENV_FILE_REMOTE on $KAMPODINE_HOST (payload above on stdout):" >&2
|
|
171
171
|
env_fp_lines <<<"$raw" >&2
|
|
172
172
|
fi
|
|
173
173
|
;;
|
package/scripts/image-import.sh
CHANGED
|
@@ -14,26 +14,26 @@
|
|
|
14
14
|
# Auth: ~/.oci/config (brew install oci-cli; oci setup config) by default, or
|
|
15
15
|
# --from-pass to source API credentials from the local pass store — the key
|
|
16
16
|
# NEVER enters this repo, the image, or any log (values only go into env vars).
|
|
17
|
-
# Every oci call pins --profile $PROFILE (OCI_PROFILE, default
|
|
18
|
-
# the CLI's DEFAULT profile is NOT the
|
|
17
|
+
# Every oci call pins --profile $PROFILE (OCI_PROFILE, default "default") —
|
|
18
|
+
# the CLI's DEFAULT profile is NOT the kampodine tenancy.
|
|
19
19
|
# Placeholder pass paths (create yours to match):
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
20
|
+
# kampodine/oci/user user OCID
|
|
21
|
+
# kampodine/oci/tenancy tenancy OCID
|
|
22
|
+
# kampodine/oci/fingerprint API key fingerprint
|
|
23
|
+
# kampodine/oci/api-key PEM private key
|
|
24
24
|
#
|
|
25
25
|
# Usage:
|
|
26
26
|
# kampodine image-import # defaults below
|
|
27
27
|
# kampodine image-import --from-pass
|
|
28
|
-
# kampodine image-import --image build/
|
|
28
|
+
# kampodine image-import --image build/kampodine-alpine-3.22.6-aarch64.qcow2
|
|
29
29
|
set -euo pipefail
|
|
30
30
|
|
|
31
31
|
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
|
32
32
|
IMAGE=""
|
|
33
|
-
BUCKET="${OCI_IMPORT_BUCKET:-
|
|
34
|
-
NAME_PREFIX="${OCI_IMPORT_NAME:-
|
|
35
|
-
COMPARTMENT_NAME="${OCI_COMPARTMENT:-
|
|
36
|
-
PROFILE="${OCI_PROFILE:-
|
|
33
|
+
BUCKET="${OCI_IMPORT_BUCKET:-kampodine-image-import}"
|
|
34
|
+
NAME_PREFIX="${OCI_IMPORT_NAME:-kampodine-alpine}"
|
|
35
|
+
COMPARTMENT_NAME="${OCI_COMPARTMENT:-kampodine}"
|
|
36
|
+
PROFILE="${OCI_PROFILE:-default}"
|
|
37
37
|
FROM_PASS=0
|
|
38
38
|
KEEP_OBJECT=0
|
|
39
39
|
|
|
@@ -65,18 +65,19 @@ while [[ $# -gt 0 ]]; do
|
|
|
65
65
|
done
|
|
66
66
|
|
|
67
67
|
command -v oci >/dev/null 2>&1 || die "oci CLI not found — brew install oci-cli, then: oci setup config (infra/oci/README.md § CLI setup)"
|
|
68
|
+
[[ -n "${OCI_COMPARTMENT:-}" ]] || die "set OCI_COMPARTMENT=<compartment name or ocid> — no default: compartments are account-specific"
|
|
68
69
|
command -v pass >/dev/null 2>&1 || true
|
|
69
70
|
|
|
70
71
|
if [[ $FROM_PASS -eq 1 ]]; then
|
|
71
72
|
command -v pass >/dev/null 2>&1 || die "pass not installed (brew install pass)"
|
|
72
73
|
say "sourcing OCI API credentials from pass (values never printed/logged)…"
|
|
73
74
|
for entry in user tenancy fingerprint api-key; do
|
|
74
|
-
pass show "
|
|
75
|
+
pass show "kampodine/oci/$entry" >/dev/null 2>&1 || die "missing pass entry kampodine/oci/$entry"
|
|
75
76
|
done
|
|
76
|
-
OCI_CLI_USER="$(pass show
|
|
77
|
-
OCI_CLI_TENANCY="$(pass show
|
|
78
|
-
OCI_CLI_FINGERPRINT="$(pass show
|
|
79
|
-
OCI_CLI_KEY_CONTENT="$(pass show
|
|
77
|
+
OCI_CLI_USER="$(pass show kampodine/oci/user)"
|
|
78
|
+
OCI_CLI_TENANCY="$(pass show kampodine/oci/tenancy)"
|
|
79
|
+
OCI_CLI_FINGERPRINT="$(pass show kampodine/oci/fingerprint)"
|
|
80
|
+
OCI_CLI_KEY_CONTENT="$(pass show kampodine/oci/api-key)"
|
|
80
81
|
export OCI_CLI_USER OCI_CLI_TENANCY OCI_CLI_FINGERPRINT OCI_CLI_KEY_CONTENT
|
|
81
82
|
fi
|
|
82
83
|
|
|
@@ -84,7 +85,7 @@ fi
|
|
|
84
85
|
if [[ -z "$IMAGE" ]]; then
|
|
85
86
|
# Constrained glob (our own build output names) — ls is fine here.
|
|
86
87
|
# shellcheck disable=SC2012
|
|
87
|
-
IMAGE="$(ls -t "$REPO_ROOT/infra/alpine-host/build"/
|
|
88
|
+
IMAGE="$(ls -t "$REPO_ROOT/infra/alpine-host/build"/kampodine-alpine-*.qcow2 2>/dev/null | head -1 || true)"
|
|
88
89
|
[[ -n "$IMAGE" ]] || die "no qcow2 under infra/alpine-host/build/ — run: (cd infra/alpine-host && packer build .)"
|
|
89
90
|
fi
|
|
90
91
|
[[ -f "$IMAGE" ]] || die "image not found: $IMAGE"
|
|
@@ -96,7 +97,7 @@ NAMESPACE="$(oci os ns get --profile "$PROFILE" --query data --raw-output)"
|
|
|
96
97
|
|
|
97
98
|
STAMP="$(date +%Y%m%d-%H%M%S)"
|
|
98
99
|
OBJECT_NAME="${NAME_PREFIX}-${STAMP}.qcow2"
|
|
99
|
-
IMAGE_NAME="${NAME_PREFIX}-$(basename "$IMAGE" .qcow2 | sed 's/^
|
|
100
|
+
IMAGE_NAME="${NAME_PREFIX}-$(basename "$IMAGE" .qcow2 | sed 's/^kampodine-alpine-//')-${STAMP}"
|
|
100
101
|
|
|
101
102
|
say "uploading $(basename "$IMAGE") -> os://$BUCKET/$OBJECT_NAME"
|
|
102
103
|
oci os object put -bn "$BUCKET" --profile "$PROFILE" --file "$IMAGE" --name "$OBJECT_NAME" --force \
|
|
@@ -162,7 +163,7 @@ say " display : $IMAGE_NAME"
|
|
|
162
163
|
say " firmware : $IMPORT_FIRMWARE"
|
|
163
164
|
say "next:"
|
|
164
165
|
if [[ "$IMPORT_FIRMWARE" == "UEFI_64" ]]; then
|
|
165
|
-
say " A1-ready. kampodine bluegreen provision <blue|green> picks this image (newest
|
|
166
|
+
say " A1-ready. kampodine bluegreen provision <blue|green> picks this image (newest kampodine-alpine*)."
|
|
166
167
|
else
|
|
167
168
|
say " A1 launch will REJECT this image (firmware $IMPORT_FIRMWARE) — see the WARN above."
|
|
168
169
|
fi
|
package/scripts/migrate.sh
CHANGED
|
@@ -15,7 +15,7 @@ set -euo pipefail
|
|
|
15
15
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
16
|
REPO_ROOT="${REPO_ROOT:-$(cd "${SCRIPT_DIR}/../../.." && pwd)}"
|
|
17
17
|
TENANT_DIR="${TENANT_DIR:-/data/tenants}"
|
|
18
|
-
SERVICE="${SERVICE:-
|
|
18
|
+
SERVICE="${SERVICE:-kampodine-api}"
|
|
19
19
|
|
|
20
20
|
log() { printf '[migrate-all] %s\n' "$*"; }
|
|
21
21
|
fail() { printf '[migrate-all][FAIL] %s\n' "$*" >&2; exit 1; }
|
|
@@ -25,7 +25,7 @@ Usage:
|
|
|
25
25
|
kampodine migrate [--allow-running]
|
|
26
26
|
|
|
27
27
|
Examples:
|
|
28
|
-
kampodine migrate # stop the API first (rc-service
|
|
28
|
+
kampodine migrate # stop the API first (rc-service kampodine-api stop)
|
|
29
29
|
kampodine migrate --allow-running # deliberate: migrate while the API serves (SQLITE_BUSY risk)
|
|
30
30
|
|
|
31
31
|
Runs drizzle migrations over the libSQL dbs under TENANT_DIR (default
|
|
@@ -51,11 +51,11 @@ command -v pnpm >/dev/null 2>&1 || fail "pnpm not found in PATH"
|
|
|
51
51
|
[[ -d "$TENANT_DIR" ]] || fail "tenant dir ${TENANT_DIR} does not exist"
|
|
52
52
|
|
|
53
53
|
# Writing schema while the API serves traffic risks SQLITE_BUSY on a single-writer
|
|
54
|
-
# engine — the service must be stopped first (rc-service
|
|
54
|
+
# engine — the service must be stopped first (rc-service kampodine-api stop). Override exists
|
|
55
55
|
# for deliberate local use.
|
|
56
56
|
if systemctl is-active --quiet "$SERVICE" 2>/dev/null; then
|
|
57
57
|
if [[ $ALLOW_RUNNING -ne 1 ]]; then
|
|
58
|
-
fail "${SERVICE} is running — stop it first (rc-service
|
|
58
|
+
fail "${SERVICE} is running — stop it first (rc-service kampodine-api stop) or pass --allow-running"
|
|
59
59
|
fi
|
|
60
60
|
log "WARNING: migrating while ${SERVICE} is running (--allow-running)"
|
|
61
61
|
fi
|
package/scripts/vm-prepare.sh
CHANGED
|
@@ -12,21 +12,21 @@
|
|
|
12
12
|
# 2. apk repositories: ensure the v3.22 community repo (podman lives there)
|
|
13
13
|
# 3. podman stack: podman podman-docker crun catatonit netavark
|
|
14
14
|
# aardvark-dns fuse-overlayfs
|
|
15
|
-
# 4. /etc/
|
|
15
|
+
# 4. /etc/kampodine/ (kampodine deploy writes /etc/kampodine/env here, 0600 root)
|
|
16
16
|
# 5. /etc/containers/registries.conf (insecure 127.0.0.1:5000; search docker.io)
|
|
17
17
|
# 6. sysctl net.ipv4.ip_unprivileged_port_start=80 (persisted + applied live)
|
|
18
|
-
# 7. OpenRC services /etc/init.d/
|
|
18
|
+
# 7. OpenRC services /etc/init.d/kampodine-api + /etc/init.d/kamal-proxy
|
|
19
19
|
# (supervise-daemon around plain `podman run`), rc-update'd into the
|
|
20
20
|
# default runlevel — that IS boot survival (no quadlets, no systemctl)
|
|
21
|
-
# 8.
|
|
22
|
-
# busybox watcher (no container) that polls /etc/
|
|
21
|
+
# 8. kampodine-anchor: the blue-green reserved-ip flip's GUEST half — a
|
|
22
|
+
# busybox watcher (no container) that polls /etc/kampodine/anchor.conf and
|
|
23
23
|
# `ip addr add`s the anchor address at flip time. Started+enabled on every
|
|
24
24
|
# VM, inert without the conf (flip tooling writes it over ssh).
|
|
25
25
|
# 9. kamal-proxy image pulled + service UP — the first deploy execs into it
|
|
26
26
|
# to issue the fresh ACME certificate
|
|
27
27
|
#
|
|
28
|
-
# The
|
|
29
|
-
# the first deploy over the registry tunnel) nor /etc/
|
|
28
|
+
# The kampodine-api container is NOT started here: neither its image (pulled by
|
|
29
|
+
# the first deploy over the registry tunnel) nor /etc/kampodine/env (written by
|
|
30
30
|
# kampodine deploy) exists yet on a fresh VM.
|
|
31
31
|
#
|
|
32
32
|
# Idempotent: safe to re-run. apk add is a no-op when satisfied; managed files
|
|
@@ -36,12 +36,12 @@
|
|
|
36
36
|
#
|
|
37
37
|
# kampodine vm-prepare --host root@<new-ip>
|
|
38
38
|
# ... --pull-images # + pre-pull the app image over the registry tunnel
|
|
39
|
-
# ... --ssh-key <path> # ssh identity (default: agent /
|
|
39
|
+
# ... --ssh-key <path> # ssh identity (default: agent / KAMPODINE_SSH_KEY)
|
|
40
40
|
set -euo pipefail
|
|
41
41
|
|
|
42
42
|
HOST=""
|
|
43
43
|
DO_PULL=0
|
|
44
|
-
SSH_KEY="${
|
|
44
|
+
SSH_KEY="${KAMPODINE_SSH_KEY:-}"
|
|
45
45
|
|
|
46
46
|
say() { printf '\033[1;32m[vm-prepare]\033[0m %s\n' "$*"; }
|
|
47
47
|
die() { printf '\033[1;31m[vm-prepare] FAIL:\033[0m %s\n' "$*" >&2; exit 1; }
|
|
@@ -56,7 +56,7 @@ EOF
|
|
|
56
56
|
cat <<'EOF'
|
|
57
57
|
|
|
58
58
|
Host/key resolution: --host | (no env default — explicit flag);
|
|
59
|
-
--ssh-key |
|
|
59
|
+
--ssh-key | KAMPODINE_SSH_KEY | ssh-agent / ~/.ssh/config.
|
|
60
60
|
EOF
|
|
61
61
|
exit 0
|
|
62
62
|
}
|
|
@@ -87,7 +87,7 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
|
87
87
|
fi
|
|
88
88
|
|
|
89
89
|
# --- managed file payloads (built Mac-side, scp'd) ----------------------------
|
|
90
|
-
TMPDIR_LOCAL="$(mktemp -d /tmp/
|
|
90
|
+
TMPDIR_LOCAL="$(mktemp -d /tmp/kampodine-vm-prepare.XXXXXX)"
|
|
91
91
|
trap 'rm -rf "$TMPDIR_LOCAL"' EXIT
|
|
92
92
|
|
|
93
93
|
cat > "$TMPDIR_LOCAL/registries.conf" <<'REGISTRIES'
|
|
@@ -102,17 +102,17 @@ location = "127.0.0.1:5000"
|
|
|
102
102
|
insecure = true
|
|
103
103
|
REGISTRIES
|
|
104
104
|
|
|
105
|
-
cat > "$TMPDIR_LOCAL/60-
|
|
105
|
+
cat > "$TMPDIR_LOCAL/60-kampodine.conf" <<'SYSCTL'
|
|
106
106
|
# Managed by packages/kampodine/scripts/vm-prepare.sh — do not hand-edit.
|
|
107
107
|
# OpenRC contract: let unprivileged/container paths bind port 80
|
|
108
108
|
# (kamal-proxy publishes 80+443; belt-and-braces for a future rootless move).
|
|
109
109
|
net.ipv4.ip_unprivileged_port_start=80
|
|
110
110
|
SYSCTL
|
|
111
111
|
|
|
112
|
-
cat > "$TMPDIR_LOCAL/
|
|
112
|
+
cat > "$TMPDIR_LOCAL/kampodine-api" <<'INITD_API'
|
|
113
113
|
#!/sbin/openrc-run
|
|
114
114
|
# Managed by packages/kampodine/scripts/vm-prepare.sh — do not hand-edit.
|
|
115
|
-
# KEEP IN SYNC with
|
|
115
|
+
# KEEP IN SYNC with the golden image's container-service init template
|
|
116
116
|
# (rendered with role defaults): vm-prepare bootstraps a fresh VM BEFORE the
|
|
117
117
|
# first ansible converge; ansible owns the file afterwards (drift repair).
|
|
118
118
|
#
|
|
@@ -122,7 +122,7 @@ cat > "$TMPDIR_LOCAL/esellar-api" <<'INITD_API'
|
|
|
122
122
|
# dies, the WHOLE run restarts against the local :latest (no pull inside the
|
|
123
123
|
# run — restarts are offline-safe; the registry tunnel is down between deploys).
|
|
124
124
|
#
|
|
125
|
-
# Env: /etc/
|
|
125
|
+
# Env: /etc/kampodine/env (0600 root, written by kampodine deploy per deploy) is
|
|
126
126
|
# the single SECRETS source (podman --env-file reads KEY=VALUE lines directly).
|
|
127
127
|
# The five CLEAR vars (NODE_ENV, PORT, LIBSQL_TENANT_DIR, LIBSQL_API_MOUNT,
|
|
128
128
|
# STATIC_SPA_MOUNT) are owned HERE via -e — deploy filters them out of the env
|
|
@@ -130,16 +130,16 @@ cat > "$TMPDIR_LOCAL/esellar-api" <<'INITD_API'
|
|
|
130
130
|
# (tenant sqlite files) — without the bind mount, tenant data would be
|
|
131
131
|
# container-ephemeral and lost on every restart.
|
|
132
132
|
|
|
133
|
-
name="
|
|
134
|
-
description="
|
|
133
|
+
name="kampodine-api"
|
|
134
|
+
description="kampodine API + SPA container (127.0.0.1:5000/kampodine-api:latest on 8080)"
|
|
135
135
|
|
|
136
136
|
supervisor=supervise-daemon
|
|
137
137
|
command="/usr/bin/podman"
|
|
138
|
-
command_args="run --rm --name
|
|
138
|
+
command_args="run --rm --name kampodine-api --network kamal -p 8080:8080"
|
|
139
139
|
command_args="$command_args -v /data/tenants:/data/tenants"
|
|
140
140
|
command_args="$command_args -e NODE_ENV=production -e PORT=8080"
|
|
141
141
|
command_args="$command_args -e LIBSQL_TENANT_DIR=/data/tenants -e LIBSQL_API_MOUNT=1 -e STATIC_SPA_MOUNT=1"
|
|
142
|
-
command_args="$command_args --env-file /etc/
|
|
142
|
+
command_args="$command_args --env-file /etc/kampodine/env 127.0.0.1:5000/kampodine-api:latest"
|
|
143
143
|
|
|
144
144
|
# Respawn forever (blue's restart=always precedent): a crash loop self-heals at
|
|
145
145
|
# the next deploy's restart; the 10s delay bounds log noise.
|
|
@@ -147,26 +147,26 @@ respawn_delay=10
|
|
|
147
147
|
respawn_max=0
|
|
148
148
|
|
|
149
149
|
# podman run's OWN stderr (missing image, name conflicts, netavark failures);
|
|
150
|
-
# container stdout/stderr go to `podman logs
|
|
151
|
-
supervise_daemon_args="--stderr /var/log/
|
|
150
|
+
# container stdout/stderr go to `podman logs kampodine-api`.
|
|
151
|
+
supervise_daemon_args="--stderr /var/log/kampodine-service.log"
|
|
152
152
|
|
|
153
153
|
depend() {
|
|
154
154
|
need net
|
|
155
|
-
after
|
|
155
|
+
after kampodine-datamount
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
start_pre() {
|
|
159
159
|
# SIGKILLed runs leave the container name holding port 8080 (--rm only
|
|
160
160
|
# cleans CLEAN exits) — sweep any leftover before the supervised run.
|
|
161
|
-
podman container exists
|
|
161
|
+
podman container exists kampodine-api 2>/dev/null && podman rm -f kampodine-api >/dev/null 2>&1
|
|
162
162
|
return 0
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
stop_post() {
|
|
166
166
|
# supervise-daemon killed the podman client; make sure the container goes
|
|
167
167
|
# down too (TERM -> graceful shutdown, then a tolerant sweep).
|
|
168
|
-
podman stop --time 10
|
|
169
|
-
podman rm -f --time 0
|
|
168
|
+
podman stop --time 10 kampodine-api >/dev/null 2>&1 || true
|
|
169
|
+
podman rm -f --time 0 kampodine-api >/dev/null 2>&1 || true
|
|
170
170
|
}
|
|
171
171
|
INITD_API
|
|
172
172
|
|
|
@@ -174,7 +174,7 @@ cat > "$TMPDIR_LOCAL/kamal-proxy" <<'INITD_PROXY'
|
|
|
174
174
|
#!/sbin/openrc-run
|
|
175
175
|
# Managed by packages/kampodine/scripts/vm-prepare.sh — do not hand-edit.
|
|
176
176
|
# KEEP IN SYNC with ansible/roles/container-service/templates/kamal-proxy.initd.j2
|
|
177
|
-
# (see the
|
|
177
|
+
# (see the kampodine-api header for the vm-prepare/ansible split).
|
|
178
178
|
#
|
|
179
179
|
# TLS edge (kamal-proxy, Let's Encrypt HTTP-01 on :80). Publishes 80+443; the
|
|
180
180
|
# LE certs + host->target registrations persist in the kamal-proxy-config named
|
|
@@ -182,7 +182,7 @@ cat > "$TMPDIR_LOCAL/kamal-proxy" <<'INITD_PROXY'
|
|
|
182
182
|
# the first deploy survives container recreation AND reboots. Start-fresh:
|
|
183
183
|
# nothing is carried from the retired blue VM. Target registration happens at
|
|
184
184
|
# deploy time (kampodine deploy: podman exec kamal-proxy kamal-proxy deploy
|
|
185
|
-
#
|
|
185
|
+
# kampodine-api --host=$PROXY_HOST --target=kampodine-api:8080 --tls
|
|
186
186
|
# --health-check-path=/api/auth/ok).
|
|
187
187
|
|
|
188
188
|
name="kamal-proxy"
|
|
@@ -212,19 +212,19 @@ stop_post() {
|
|
|
212
212
|
}
|
|
213
213
|
INITD_PROXY
|
|
214
214
|
|
|
215
|
-
cat > "$TMPDIR_LOCAL/
|
|
215
|
+
cat > "$TMPDIR_LOCAL/kampodine-anchor.sh" <<'ANCHOR_WATCHER'
|
|
216
216
|
#!/bin/sh
|
|
217
|
-
# KEEP IN SYNC with ansible/roles/container-service/files/
|
|
217
|
+
# KEEP IN SYNC with ansible/roles/container-service/files/kampodine-anchor.sh
|
|
218
218
|
# (vm-prepare rendered-content convention: vm-prepare bootstraps a fresh VM
|
|
219
219
|
# BEFORE the first ansible converge; ansible owns the file afterwards).
|
|
220
220
|
#
|
|
221
|
-
#
|
|
221
|
+
# kampodine-anchor.sh — guest half of the blue-green reserved-ip flip.
|
|
222
222
|
#
|
|
223
223
|
# OCI assigns the reserved PUBLIC ip to a SECONDARY private ip ("the anchor")
|
|
224
224
|
# on the instance VNIC, but this image has NO oracle-cloud-agent: nothing
|
|
225
225
|
# configures that private ip inside the guest, so packets to the reserved ip
|
|
226
226
|
# die until the address exists on the interface. `kampodine bluegreen flip` writes
|
|
227
|
-
# /etc/
|
|
227
|
+
# /etc/kampodine/anchor.conf over ssh at flip time; this watcher polls it and
|
|
228
228
|
# runs `ip addr add` within one interval. The unit is enabled+started on every
|
|
229
229
|
# VM by default and is INERT without the conf — a VM that never flips never
|
|
230
230
|
# touches its addresses.
|
|
@@ -236,13 +236,13 @@ cat > "$TMPDIR_LOCAL/esellar-anchor.sh" <<'ANCHOR_WATCHER'
|
|
|
236
236
|
# ADD-ONLY BY DESIGN: this script NEVER removes or flushes addresses (rollback
|
|
237
237
|
# cleanup is the flip tool's explicit ssh job). Idempotent: a present address
|
|
238
238
|
# is a no-op. Only state TRANSITIONS are logged (bounded log noise), to
|
|
239
|
-
# stdout — supervise-daemon tees it to /var/log/
|
|
239
|
+
# stdout — supervise-daemon tees it to /var/log/kampodine-anchor.log.
|
|
240
240
|
|
|
241
|
-
CONF="/etc/
|
|
242
|
-
INTERVAL="${
|
|
241
|
+
CONF="/etc/kampodine/anchor.conf"
|
|
242
|
+
INTERVAL="${KAMPODINE_ANCHOR_INTERVAL:-5}"
|
|
243
243
|
STATE="boot" # last logged transition (boot | idle | added | error)
|
|
244
244
|
|
|
245
|
-
log() { printf '%s
|
|
245
|
+
log() { printf '%s kampodine-anchor: %s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z')" "$*"; }
|
|
246
246
|
|
|
247
247
|
# supervise-daemon SIGTERMs on stop — die cleanly with the current tick.
|
|
248
248
|
trap 'exit 0' TERM INT
|
|
@@ -283,33 +283,33 @@ while :; do
|
|
|
283
283
|
done
|
|
284
284
|
ANCHOR_WATCHER
|
|
285
285
|
|
|
286
|
-
cat > "$TMPDIR_LOCAL/
|
|
286
|
+
cat > "$TMPDIR_LOCAL/kampodine-anchor" <<'INITD_ANCHOR'
|
|
287
287
|
#!/sbin/openrc-run
|
|
288
288
|
# Managed by packages/kampodine/scripts/vm-prepare.sh — do not hand-edit.
|
|
289
|
-
# KEEP IN SYNC with ansible/roles/container-service/templates/
|
|
290
|
-
# (rendered with role defaults — see the
|
|
289
|
+
# KEEP IN SYNC with ansible/roles/container-service/templates/kampodine-anchor.initd.j2
|
|
290
|
+
# (rendered with role defaults — see the kampodine-api header for the
|
|
291
291
|
# vm-prepare/ansible split).
|
|
292
292
|
#
|
|
293
293
|
# Guest half of the blue-green reserved-ip flip: supervise-daemon runs the
|
|
294
|
-
#
|
|
294
|
+
# kampodine-anchor.sh watcher, which polls /etc/kampodine/anchor.conf (written by
|
|
295
295
|
# `kampodine bluegreen flip` over ssh at flip time) and `ip addr add`s the
|
|
296
296
|
# anchor address when it appears. Started+enabled on EVERY vm by default and
|
|
297
297
|
# INERT without the conf: a VM that never flips never touches its addresses —
|
|
298
|
-
# unlike the container units, there is no
|
|
299
|
-
# Transitions land in /var/log/
|
|
298
|
+
# unlike the container units, there is no kampodine_start_containers gate.
|
|
299
|
+
# Transitions land in /var/log/kampodine-anchor.log.
|
|
300
300
|
|
|
301
|
-
name="
|
|
301
|
+
name="kampodine-anchor"
|
|
302
302
|
description="Blue-green reserved-ip anchor address watcher (guest half of the flip)"
|
|
303
303
|
|
|
304
304
|
supervisor=supervise-daemon
|
|
305
|
-
command="/usr/local/sbin/
|
|
305
|
+
command="/usr/local/sbin/kampodine-anchor.sh"
|
|
306
306
|
|
|
307
307
|
# Respawn forever: the watcher itself never exits (trap TERM/INT -> exit 0 on
|
|
308
308
|
# stop), so a respawn means the script died abnormally — retry gently.
|
|
309
309
|
respawn_delay=5
|
|
310
310
|
respawn_max=0
|
|
311
311
|
|
|
312
|
-
supervise_daemon_args="--stdout /var/log/
|
|
312
|
+
supervise_daemon_args="--stdout /var/log/kampodine-anchor.log --stderr /var/log/kampodine-anchor.log"
|
|
313
313
|
|
|
314
314
|
depend() {
|
|
315
315
|
need net
|
|
@@ -366,7 +366,7 @@ say "sshd hardening: ensure drop-in + Include + restart (golden image predates t
|
|
|
366
366
|
vm 'grep -q "^Include /etc/ssh/sshd_config.d/\*.conf" /etc/ssh/sshd_config || sed -i "1i Include /etc/ssh/sshd_config.d/*.conf" /etc/ssh/sshd_config' \
|
|
367
367
|
|| die "could not ensure the sshd_config Include line"
|
|
368
368
|
vm 'mkdir -p /etc/ssh/sshd_config.d && chmod 700 /etc/ssh/sshd_config.d'
|
|
369
|
-
scp -q "${SSH_ARGS[@]}" "$TMPDIR_LOCAL/sshd-hardening.conf" "$HOST:/etc/ssh/sshd_config.d/99-
|
|
369
|
+
scp -q "${SSH_ARGS[@]}" "$TMPDIR_LOCAL/sshd-hardening.conf" "$HOST:/etc/ssh/sshd_config.d/99-kampodine-hardening.conf"
|
|
370
370
|
vm 'rc-service sshd restart' || die "sshd restart failed after hardening ensure"
|
|
371
371
|
|
|
372
372
|
say "gate: sshd hardening effective"
|
|
@@ -404,50 +404,50 @@ PODSTACK_GATE
|
|
|
404
404
|
vm 'rc-update show boot | grep -q cgroups || rc-update add cgroups boot' || die "rc-update cgroups failed"
|
|
405
405
|
vm 'rc-service cgroups status >/dev/null 2>&1 || rc-service cgroups start' || die "cgroups start failed"
|
|
406
406
|
|
|
407
|
-
# --- 3. /etc/
|
|
408
|
-
say "creating /etc/
|
|
409
|
-
vm 'mkdir -p /etc/
|
|
407
|
+
# --- 3. /etc/kampodine + managed config files ------------------------------------
|
|
408
|
+
say "creating /etc/kampodine (env file lands here via kampodine deploy, 0600 root)…"
|
|
409
|
+
vm 'mkdir -p /etc/kampodine && chmod 700 /etc/kampodine'
|
|
410
410
|
|
|
411
411
|
say "writing /etc/containers/registries.conf (insecure 127.0.0.1:5000; search docker.io)…"
|
|
412
412
|
vm 'mkdir -p /etc/containers'
|
|
413
413
|
scp -q "${SSH_ARGS[@]}" "$TMPDIR_LOCAL/registries.conf" "$HOST:/etc/containers/registries.conf"
|
|
414
414
|
|
|
415
|
-
say "writing /etc/sysctl.d/60-
|
|
416
|
-
scp -q "${SSH_ARGS[@]}" "$TMPDIR_LOCAL/60-
|
|
415
|
+
say "writing /etc/sysctl.d/60-kampodine.conf + applying live…"
|
|
416
|
+
scp -q "${SSH_ARGS[@]}" "$TMPDIR_LOCAL/60-kampodine.conf" "$HOST:/etc/sysctl.d/60-kampodine.conf"
|
|
417
417
|
vm_sh <<'SYSCTL_APPLY' || die "sysctl ensure/apply failed"
|
|
418
418
|
rc-update show boot | grep -Eq '^[[:space:]]*sysctl[[:space:]]*\|' || rc-update add sysctl boot
|
|
419
419
|
sysctl -w net.ipv4.ip_unprivileged_port_start=80 > /dev/null
|
|
420
420
|
SYSCTL_APPLY
|
|
421
421
|
|
|
422
422
|
# --- 4. OpenRC services (canonical: ansible container-service templates) --------
|
|
423
|
-
say "ensuring the 'kamal' podman network (deploy's proxy re-point resolves
|
|
423
|
+
say "ensuring the 'kamal' podman network (deploy's proxy re-point resolves kampodine-api:8080 by network DNS)…"
|
|
424
424
|
# shellcheck disable=SC2016
|
|
425
425
|
vm 'podman network exists kamal 2>/dev/null || podman network create kamal' \
|
|
426
426
|
|| die "podman network create kamal failed"
|
|
427
|
-
say "installing OpenRC services
|
|
428
|
-
scp -q "${SSH_ARGS[@]}" "$TMPDIR_LOCAL/
|
|
427
|
+
say "installing OpenRC services kampodine-api + kamal-proxy (supervise-daemon around podman run)…"
|
|
428
|
+
scp -q "${SSH_ARGS[@]}" "$TMPDIR_LOCAL/kampodine-api" "$HOST:/etc/init.d/kampodine-api"
|
|
429
429
|
scp -q "${SSH_ARGS[@]}" "$TMPDIR_LOCAL/kamal-proxy" "$HOST:/etc/init.d/kamal-proxy"
|
|
430
|
-
vm 'chmod 755 /etc/init.d/
|
|
430
|
+
vm 'chmod 755 /etc/init.d/kampodine-api /etc/init.d/kamal-proxy'
|
|
431
431
|
|
|
432
|
-
# --- 4b.
|
|
432
|
+
# --- 4b. kampodine-anchor (blue-green flip guest half — watcher, no container) ----
|
|
433
433
|
# Ships on EVERY vm: started + enabled now, INERT until a flip writes
|
|
434
|
-
# /etc/
|
|
434
|
+
# /etc/kampodine/anchor.conf over ssh (ansible container-service owns both files
|
|
435
435
|
# afterwards — drift repair keeps them in sync).
|
|
436
|
-
say "installing the
|
|
436
|
+
say "installing the kampodine-anchor watcher service (blue-green flip guest half)…"
|
|
437
437
|
# /usr/local/sbin does NOT exist on the golden image (fresh Alpine ships no
|
|
438
438
|
# /usr/local hierarchy; the alpine-base role creates it — vm-prepare runs
|
|
439
439
|
# BEFORE any ansible)
|
|
440
440
|
vm 'mkdir -p /usr/local/sbin' || die "mkdir /usr/local/sbin failed"
|
|
441
|
-
scp -q "${SSH_ARGS[@]}" "$TMPDIR_LOCAL/
|
|
442
|
-
scp -q "${SSH_ARGS[@]}" "$TMPDIR_LOCAL/
|
|
443
|
-
vm 'chmod 755 /usr/local/sbin/
|
|
444
|
-
vm 'sh -n /usr/local/sbin/
|
|
445
|
-
vm 'sh -n /etc/init.d/
|
|
446
|
-
vm 'rc-update show default | grep -qE "^[[:space:]]*
|
|
447
|
-
|| die "rc-update add
|
|
448
|
-
vm 'rc-service
|
|
449
|
-
|
|
450
|
-
for SVC in
|
|
441
|
+
scp -q "${SSH_ARGS[@]}" "$TMPDIR_LOCAL/kampodine-anchor.sh" "$HOST:/usr/local/sbin/kampodine-anchor.sh"
|
|
442
|
+
scp -q "${SSH_ARGS[@]}" "$TMPDIR_LOCAL/kampodine-anchor" "$HOST:/etc/init.d/kampodine-anchor"
|
|
443
|
+
vm 'chmod 755 /usr/local/sbin/kampodine-anchor.sh /etc/init.d/kampodine-anchor' || die "chmod kampodine-anchor files failed"
|
|
444
|
+
vm 'sh -n /usr/local/sbin/kampodine-anchor.sh' || die "kampodine-anchor.sh does not parse (busybox sh)"
|
|
445
|
+
vm 'sh -n /etc/init.d/kampodine-anchor' || die "/etc/init.d/kampodine-anchor does not parse"
|
|
446
|
+
vm 'rc-update show default | grep -qE "^[[:space:]]*kampodine-anchor[[:space:]]*\\|" || rc-update add kampodine-anchor default' \
|
|
447
|
+
|| die "rc-update add kampodine-anchor default failed"
|
|
448
|
+
vm 'rc-service kampodine-anchor start' || die "rc-service kampodine-anchor start failed"
|
|
449
|
+
|
|
450
|
+
for SVC in kampodine-api kamal-proxy; do
|
|
451
451
|
vm "rc-update show default | grep -qE '^[[:space:]]*${SVC}[[:space:]]*\\|' || rc-update add ${SVC} default" \
|
|
452
452
|
|| die "rc-update add $SVC default failed"
|
|
453
453
|
done
|
|
@@ -456,7 +456,7 @@ done
|
|
|
456
456
|
# re-runs after a deploy must not bounce production.
|
|
457
457
|
# shellcheck disable=SC2016
|
|
458
458
|
vm_sh <<'UNIT_DRIFT' || true
|
|
459
|
-
for SVC in
|
|
459
|
+
for SVC in kampodine-api kamal-proxy kampodine-anchor; do
|
|
460
460
|
if rc-service "$SVC" status > /dev/null 2>&1; then
|
|
461
461
|
echo "RUNNING: $SVC (unit file was overwritten — rc-service $SVC restart to apply, on your call)"
|
|
462
462
|
fi
|
|
@@ -483,26 +483,26 @@ if [[ $DO_PULL -eq 1 ]]; then
|
|
|
483
483
|
# installs it later; vm-prepare runs BEFORE any ansible)
|
|
484
484
|
vm 'busybox wget -q -O /dev/null http://127.0.0.1:5000/v2/' || {
|
|
485
485
|
pkill -f "ssh.*-R 5000" 2>/dev/null || true; sleep 1
|
|
486
|
-
nohup ssh -R 5000:127.0.0.1:5000 -N -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes "$HOST" >/tmp/
|
|
486
|
+
nohup ssh -R 5000:127.0.0.1:5000 -N -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes "$HOST" >/tmp/kampodine-tunnel.log 2>&1 &
|
|
487
487
|
sleep 3
|
|
488
488
|
}
|
|
489
|
-
vm 'busybox wget -q -O /dev/null http://127.0.0.1:5000/v2/' || die "registry tunnel did not come up (/tmp/
|
|
490
|
-
vm 'podman pull --tls-verify=false 127.0.0.1:5000/
|
|
489
|
+
vm 'busybox wget -q -O /dev/null http://127.0.0.1:5000/v2/' || die "registry tunnel did not come up (/tmp/kampodine-tunnel.log)"
|
|
490
|
+
vm 'podman pull --tls-verify=false 127.0.0.1:5000/kampodine-api:latest' || die "app image pull failed"
|
|
491
491
|
else
|
|
492
492
|
say "skipping app-image pre-pull (pass --pull-images, or let the first deploy pull)"
|
|
493
493
|
fi
|
|
494
494
|
|
|
495
|
-
# --- 7. converge
|
|
495
|
+
# --- 7. converge kampodine-api ONLY if a previous deploy left image + env ---------
|
|
496
496
|
# On a truly fresh VM neither exists — the FIRST DEPLOY provides both and
|
|
497
497
|
# starts the service. Re-runs of this script after a deploy heal drift.
|
|
498
|
-
say "
|
|
498
|
+
say "kampodine-api start check (needs image + /etc/kampodine/env — first deploy provides both)…"
|
|
499
499
|
# shellcheck disable=SC2016
|
|
500
|
-
vm_sh <<'API_CONVERGE' || die "
|
|
501
|
-
if [ -f /etc/
|
|
502
|
-
rc-service
|
|
503
|
-
echo "
|
|
500
|
+
vm_sh <<'API_CONVERGE' || die "kampodine-api start failed (image + env present — investigate: podman logs kampodine-api)"
|
|
501
|
+
if [ -f /etc/kampodine/env ] && podman image exists 127.0.0.1:5000/kampodine-api:latest; then
|
|
502
|
+
rc-service kampodine-api start
|
|
503
|
+
echo "kampodine-api started (image + env present)"
|
|
504
504
|
else
|
|
505
|
-
echo "
|
|
505
|
+
echo "kampodine-api deferred: no image and/or /etc/kampodine/env yet (normal on a fresh VM — first deploy handles it)"
|
|
506
506
|
fi
|
|
507
507
|
API_CONVERGE
|
|
508
508
|
|
|
@@ -522,16 +522,16 @@ vm 'grep -q "127.0.0.1:5000" /etc/containers/registries.conf && grep -q "insecur
|
|
|
522
522
|
|| die "registries.conf incomplete (127.0.0.1:5000 insecure + docker.io search)"
|
|
523
523
|
|
|
524
524
|
say "gate: services enabled in the default runlevel"
|
|
525
|
-
vm 'rc-update show default | grep -qE "^[[:space:]]*
|
|
525
|
+
vm 'rc-update show default | grep -qE "^[[:space:]]*kampodine-api[[:space:]]*\\|"' || die "kampodine-api not in the default runlevel"
|
|
526
526
|
vm 'rc-update show default | grep -qE "^[[:space:]]*kamal-proxy[[:space:]]*\\|"' || die "kamal-proxy not in the default runlevel"
|
|
527
527
|
vm 'rc-service kamal-proxy status >/dev/null' || die "kamal-proxy service not started"
|
|
528
528
|
|
|
529
|
-
say "gate:
|
|
530
|
-
vm 'rc-update show default | grep -qE "^[[:space:]]*
|
|
531
|
-
vm 'rc-service
|
|
532
|
-
vm '! test -e /etc/
|
|
533
|
-
vm 'grep -q "no anchor conf" /var/log/
|
|
534
|
-
|| die "
|
|
529
|
+
say "gate: kampodine-anchor watcher running + INERT (no anchor conf on a fresh VM)"
|
|
530
|
+
vm 'rc-update show default | grep -qE "^[[:space:]]*kampodine-anchor[[:space:]]*\\|"' || die "kampodine-anchor not in the default runlevel"
|
|
531
|
+
vm 'rc-service kampodine-anchor status >/dev/null' || die "kampodine-anchor service not started"
|
|
532
|
+
vm '! test -e /etc/kampodine/anchor.conf' || die "/etc/kampodine/anchor.conf already exists on a fresh VM (wrong machine?)"
|
|
533
|
+
vm 'grep -q "no anchor conf" /var/log/kampodine-anchor.log' \
|
|
534
|
+
|| die "kampodine-anchor is started but never logged its idle transition (watcher loop not running?)"
|
|
535
535
|
|
|
536
536
|
# --- summary ---------------------------------------------------------------------
|
|
537
537
|
IP="${HOST#*@}"
|