kampodine 0.3.0 → 0.6.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 +139 -0
- package/README.md +74 -11
- package/cli.js +22 -2
- package/kampodine.md +13 -3
- package/package.json +1 -1
- package/scripts/backup.sh +290 -0
- package/scripts/bluegreen.sh +78 -57
- package/scripts/common.sh +644 -0
- package/scripts/config.sh +247 -0
- package/scripts/deploy-lifecycle.sh +266 -0
- package/scripts/deploy.sh +148 -31
- package/scripts/dns.sh +27 -8
- package/scripts/env.sh +35 -21
- package/scripts/image-import.sh +29 -19
- package/scripts/metrics.sh +127 -0
- package/scripts/migrate.sh +41 -7
- package/scripts/status.sh +132 -12
- package/scripts/vm-prepare.sh +103 -89
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,9 +71,29 @@
|
|
|
71
71
|
# loopback; the reserved IP is checked over :80 with the prod Host header).
|
|
72
72
|
set -euo pipefail
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
HERE="${BASH_SOURCE[0]%/*}"
|
|
75
|
+
# shellcheck source=common.sh
|
|
76
|
+
source "$HERE/common.sh"
|
|
77
|
+
|
|
78
|
+
# --profile pre-scan (kampodine INSTANCE profile): every sub-step accepts it
|
|
79
|
+
# before its own flags; flip/provision have their own flag loops, so the flag
|
|
80
|
+
# is lifted here once, validated, and its proxyHost feeds the status health
|
|
81
|
+
# header (APP_HOST_HEADER) when the env did not pin one. The flip's own
|
|
82
|
+
# ACME hostname stays derived from the reserved IP by design.
|
|
83
|
+
PROFILE_FLAG=""
|
|
84
|
+
args=()
|
|
85
|
+
while [[ $# -gt 0 ]]; do
|
|
86
|
+
case "$1" in
|
|
87
|
+
--profile) PROFILE_FLAG="$2"; shift 2 ;;
|
|
88
|
+
*) args+=("$1"); shift ;;
|
|
89
|
+
esac
|
|
90
|
+
done
|
|
91
|
+
set -- ${args[@]+"${args[@]}"}
|
|
92
|
+
profile_resolve "$PROFILE_FLAG" "" ""
|
|
93
|
+
|
|
94
|
+
PROFILE="${OCI_PROFILE:-default}"
|
|
95
|
+
COMPARTMENT_NAME="${OCI_COMPARTMENT:-kampodine}"
|
|
96
|
+
APP_HOST_HEADER="${PROFILE_PROXY_HOST:-${APP_HOST_HEADER:-app.example.com}}"
|
|
77
97
|
REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)}"
|
|
78
98
|
SSH_OPTS=(-o ConnectTimeout=6 -o BatchMode=yes -o StrictHostKeyChecking=accept-new)
|
|
79
99
|
|
|
@@ -86,7 +106,7 @@ usage() {
|
|
|
86
106
|
cat <<'EOF'
|
|
87
107
|
|
|
88
108
|
Each sub-step has its own --help: status | init | provision | flip | rollback.
|
|
89
|
-
Env: OCI_PROFILE (default
|
|
109
|
+
Env: OCI_PROFILE (default: "default"), OCI_COMPARTMENT (required).
|
|
90
110
|
|
|
91
111
|
Examples:
|
|
92
112
|
kampodine bluegreen status
|
|
@@ -181,7 +201,7 @@ compartment_ocid() {
|
|
|
181
201
|
instance_by_color() {
|
|
182
202
|
local comp="$1" color="$2" row
|
|
183
203
|
row="$(oci compute instance list -c "$comp" --profile "$PROFILE" \
|
|
184
|
-
--display-name "
|
|
204
|
+
--display-name "kampodine-$color" --lifecycle-state RUNNING \
|
|
185
205
|
--query 'sort_by(data, &"time-created")[-1] | ["id", "availability-domain"] | join('\'' '\'', @)' \
|
|
186
206
|
--raw-output 2>/dev/null || true)"
|
|
187
207
|
[[ -n "$row" ]] || return 0
|
|
@@ -240,7 +260,7 @@ reserved_anchor_ip() {
|
|
|
240
260
|
return 0
|
|
241
261
|
fi
|
|
242
262
|
oci network private-ip create --profile "$PROFILE" --vnic-id "$vnic" \
|
|
243
|
-
--display-name "
|
|
263
|
+
--display-name "kampodine-reserved-anchor" \
|
|
244
264
|
--query 'data.id' --raw-output 2>/dev/null
|
|
245
265
|
}
|
|
246
266
|
|
|
@@ -249,9 +269,9 @@ FLIP_POLL_SLEEP="${FLIP_POLL_SLEEP:-5}"
|
|
|
249
269
|
FLIP_ADDR_TRIES="${FLIP_ADDR_TRIES:-12}" # guest watcher pickup: 12 x 5s = 60s
|
|
250
270
|
FLIP_ACME_TRIES="${FLIP_ACME_TRIES:-24}" # LE HTTP-01: 24 x 5s = 120s
|
|
251
271
|
|
|
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/
|
|
272
|
+
# Guest anchor protocol (the kampodine-anchor watcher half lives in
|
|
273
|
+
# infra/alpine-host/ansible/roles/container-service/files/kampodine-anchor.sh —
|
|
274
|
+
# both halves of the conf path must stay in sync: /etc/kampodine/anchor.conf).
|
|
255
275
|
# Remote commands are composed client-side BY DESIGN (vm-prepare convention);
|
|
256
276
|
# the interpolated values are OCI-API derived and regex-gated at the call
|
|
257
277
|
# sites, never user input.
|
|
@@ -260,7 +280,7 @@ FLIP_ACME_TRIES="${FLIP_ACME_TRIES:-24}" # LE HTTP-01: 24 x 5s = 120s
|
|
|
260
280
|
write_anchor_conf() {
|
|
261
281
|
local ip="$1" addr="$2"
|
|
262
282
|
ssh "${SSH_OPTS[@]}" "root@$ip" \
|
|
263
|
-
"umask 077; mkdir -p /etc/
|
|
283
|
+
"umask 077; mkdir -p /etc/kampodine; printf 'ANCHOR_ADDR=%s\nANCHOR_IFACE=\n' '$addr' > /etc/kampodine/anchor.conf && echo ANCHOR_CONF_WRITTEN"
|
|
264
284
|
}
|
|
265
285
|
|
|
266
286
|
# shellcheck disable=SC2029
|
|
@@ -274,7 +294,7 @@ anchor_addr_ready() {
|
|
|
274
294
|
read_anchor_conf_addr() {
|
|
275
295
|
local ip="$1" line addr
|
|
276
296
|
line="$(ssh "${SSH_OPTS[@]}" "root@$ip" \
|
|
277
|
-
'grep -h "^ANCHOR_ADDR=" /etc/
|
|
297
|
+
'grep -h "^ANCHOR_ADDR=" /etc/kampodine/anchor.conf 2>/dev/null | head -n 1' 2>/dev/null || true)"
|
|
278
298
|
addr="${line#ANCHOR_ADDR=}"
|
|
279
299
|
addr="${addr//\"/}"
|
|
280
300
|
addr="${addr//\'/}"
|
|
@@ -297,7 +317,7 @@ delete_guest_addr() {
|
|
|
297
317
|
# re-added the address once; retry the delete across one watcher interval.
|
|
298
318
|
cleanup_target_anchor() {
|
|
299
319
|
local ip="$1" addr="$2" i
|
|
300
|
-
ssh "${SSH_OPTS[@]}" "root@$ip" "rm -f /etc/
|
|
320
|
+
ssh "${SSH_OPTS[@]}" "root@$ip" "rm -f /etc/kampodine/anchor.conf" >/dev/null 2>&1 || true
|
|
301
321
|
for ((i = 1; i <= 3; i++)); do
|
|
302
322
|
delete_guest_addr "$ip" "$addr"
|
|
303
323
|
anchor_addr_ready "$ip" "$addr" >/dev/null 2>&1 || return 0
|
|
@@ -323,7 +343,7 @@ flip_failure_rollback() {
|
|
|
323
343
|
if [[ "$bpip" == ocid1.privateip* ]]; then
|
|
324
344
|
if oci network public-ip update --public-ip-id "$rocid" --profile "$PROFILE" \
|
|
325
345
|
--private-ip-id "$bpip" --force >/dev/null 2>&1; then
|
|
326
|
-
say "rolled back to
|
|
346
|
+
say "rolled back to kampodine-$ob (existing anchor $bpip)" >&2
|
|
327
347
|
recovered=1
|
|
328
348
|
fi
|
|
329
349
|
fi
|
|
@@ -331,14 +351,14 @@ flip_failure_rollback() {
|
|
|
331
351
|
if (( ! recovered )); then
|
|
332
352
|
if oci network public-ip update --public-ip-id "$rocid" --profile "$PROFILE" \
|
|
333
353
|
--private-ip-id "" --force --wait-for-state AVAILABLE >/dev/null 2>&1; then
|
|
334
|
-
say "reserved ip UNASSIGNED (dormant) — no existing anchor on
|
|
354
|
+
say "reserved ip UNASSIGNED (dormant) — no existing anchor on kampodine-$ob" >&2
|
|
335
355
|
else
|
|
336
356
|
cleanup_target_anchor "$tpub" "$taddr"
|
|
337
357
|
die "AUTO-ROLLBACK FAILED — reserved ip state unknown; flip manually via console: $rocid"
|
|
338
358
|
fi
|
|
339
359
|
fi
|
|
340
360
|
cleanup_target_anchor "$tpub" "$taddr"
|
|
341
|
-
say "
|
|
361
|
+
say "kampodine-$to cleaned (anchor.conf removed, anchor address deleted) — investigate before retrying" >&2
|
|
342
362
|
}
|
|
343
363
|
|
|
344
364
|
# instance_healthy <ephemeral_ip> -> ssh + loopback app check.
|
|
@@ -349,7 +369,7 @@ instance_healthy() {
|
|
|
349
369
|
local ip="$1"
|
|
350
370
|
[[ -n "$ip" ]] || return 1
|
|
351
371
|
ssh "${SSH_OPTS[@]}" "root@$ip" \
|
|
352
|
-
"rc-service
|
|
372
|
+
"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
373
|
}
|
|
354
374
|
|
|
355
375
|
# instance_wait_running <iid> — poll lifecycle-state to RUNNING. The OCI CLI's
|
|
@@ -365,7 +385,7 @@ instance_wait_running() {
|
|
|
365
385
|
--query 'data."lifecycle-state"' --raw-output 2>/dev/null || true)"
|
|
366
386
|
case "$state" in
|
|
367
387
|
RUNNING) return 0 ;;
|
|
368
|
-
FAILED | TERMINATED | TERMINATING) die "
|
|
388
|
+
FAILED | TERMINATED | TERMINATING) die "kampodine instance reached $state — nothing to inject, check the console" ;;
|
|
369
389
|
esac
|
|
370
390
|
sleep "$sleep_s"
|
|
371
391
|
done
|
|
@@ -390,7 +410,7 @@ inject_alpine() {
|
|
|
390
410
|
# the Alpine verify forever. The scrub below
|
|
391
411
|
# clears the phase file between the two boots; the user's file is never
|
|
392
412
|
# touched.
|
|
393
|
-
kh="$(mktemp "${TMPDIR:-/tmp}/
|
|
413
|
+
kh="$(mktemp "${TMPDIR:-/tmp}/kampodine-inject-kh.XXXXXX")"
|
|
394
414
|
iss() { ssh -o UserKnownHostsFile="$kh" "${SSH_OPTS[@]}" "$@"; }
|
|
395
415
|
say "inject: waiting for ssh (${ruser}@${ip}, platform-image first boot)…"
|
|
396
416
|
ssh_wait_probe() { iss "${ruser}@${ip}" true; }
|
|
@@ -400,7 +420,7 @@ inject_alpine() {
|
|
|
400
420
|
[[ "$i" == "$probe_tries" ]] && die "ssh never came up on ${ip} (${ruser}) — check the instance console connection"
|
|
401
421
|
sleep "$probe_sleep"
|
|
402
422
|
done
|
|
403
|
-
raw="$(mktemp "${TMPDIR:-/tmp}/
|
|
423
|
+
raw="$(mktemp "${TMPDIR:-/tmp}/kampodine-inject-raw.XXXXXX")"
|
|
404
424
|
say "inject: converting ${qcow2} -> raw…"
|
|
405
425
|
qemu-img convert -O raw "$qcow2" "$raw"
|
|
406
426
|
say "inject: streaming golden disk -> ${ip} boot volume (gunzip | dd, conv=fsync)…"
|
|
@@ -440,7 +460,7 @@ inject_alpine() {
|
|
|
440
460
|
done
|
|
441
461
|
rm -f "$kh"
|
|
442
462
|
[[ -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
|
|
463
|
+
say "INJECTED kampodine-$color: Alpine ${rel} boots on ${ip} (instance image metadata stays the platform image — like green)"
|
|
444
464
|
say "next: kampodine vm-prepare --host root@${ip} -> kampodine deploy --host root@${ip}"
|
|
445
465
|
say "then 'bluegreen.sh flip --to ${color}' (health-gated) once its app checks green."
|
|
446
466
|
}
|
|
@@ -458,11 +478,12 @@ for help_arg in "$@"; do
|
|
|
458
478
|
;;
|
|
459
479
|
esac
|
|
460
480
|
done
|
|
481
|
+
[[ -n "${OCI_COMPARTMENT:-}" ]] || die "set OCI_COMPARTMENT=<compartment name or ocid> — no default: compartments are account-specific"
|
|
461
482
|
case "$cmd" in
|
|
462
483
|
status)
|
|
463
484
|
comp="$(compartment_ocid)"
|
|
464
485
|
rp="$(reserved_ip "$comp")"
|
|
465
|
-
say "==
|
|
486
|
+
say "== kampodine blue/green pair (compartment $COMPARTMENT_NAME) =="
|
|
466
487
|
if [[ -n "$rp" ]]; then
|
|
467
488
|
read -r rocid raddr rholder <<<"$rp"
|
|
468
489
|
say "reserved IP : $raddr ($rocid)"
|
|
@@ -475,9 +496,9 @@ case "$cmd" in
|
|
|
475
496
|
if [[ -n "$row" ]]; then
|
|
476
497
|
read -r iid pub ad <<<"$row"
|
|
477
498
|
if instance_healthy "$pub"; then verdict=HEALTHY; else verdict=UNHEALTHY/unreachable; fi
|
|
478
|
-
say "
|
|
499
|
+
say "kampodine-$color : $iid ip=${pub:-none} ad=${ad:-?} app=$verdict"
|
|
479
500
|
else
|
|
480
|
-
say "
|
|
501
|
+
say "kampodine-$color : not provisioned"
|
|
481
502
|
fi
|
|
482
503
|
done
|
|
483
504
|
;;
|
|
@@ -491,7 +512,7 @@ case "$cmd" in
|
|
|
491
512
|
exit 0
|
|
492
513
|
fi
|
|
493
514
|
addr="$(oci network public-ip create -c "$comp" --profile "$PROFILE" \
|
|
494
|
-
--lifetime RESERVED --display-name
|
|
515
|
+
--lifetime RESERVED --display-name kampodine-active \
|
|
495
516
|
--query 'data."ip-address"' --raw-output)"
|
|
496
517
|
say "created DORMANT reserved IP: $addr (unassigned — no instance attached)"
|
|
497
518
|
say "Point DNS/SSLIP at this address when the pair goes active."
|
|
@@ -502,25 +523,25 @@ case "$cmd" in
|
|
|
502
523
|
color="${1:-}"
|
|
503
524
|
[[ "$color" == blue || "$color" == green ]] || die "usage: kampodine bluegreen provision <blue|green>"
|
|
504
525
|
comp="$(compartment_ocid)"
|
|
505
|
-
instance_by_color "$comp" "$color" | grep -q . && die "
|
|
526
|
+
instance_by_color "$comp" "$color" | grep -q . && die "kampodine-$color already RUNNING"
|
|
506
527
|
# base: the OTHER color's AD + subnet (same fault domain layout), golden image
|
|
507
528
|
other=green; [[ "$color" == green ]] && other=blue
|
|
508
529
|
orow="$(instance_by_color "$comp" "$other")"
|
|
509
|
-
[[ -n "$orow" ]] || die "
|
|
530
|
+
[[ -n "$orow" ]] || die "kampodine-$other not RUNNING — need its AD/subnet as the pair template"
|
|
510
531
|
# instance_by_color rows are "<ocid> <ephemeral_public_ip> <ad>" — field 2
|
|
511
532
|
# is the IP, the AD is field 3 (launching with the IP as AD fails).
|
|
512
533
|
read -r oiid _ oad <<<"$orow"
|
|
513
534
|
subnet="$(oci compute vnic-attachment list -c "$comp" --profile "$PROFILE" \
|
|
514
535
|
--instance-id "$oiid" --query 'data[0]."subnet-id"' --raw-output)"
|
|
515
536
|
|
|
516
|
-
# Route selection. NATIVE only with a UEFI_64
|
|
537
|
+
# Route selection. NATIVE only with a UEFI_64 kampodine-alpine* custom image:
|
|
517
538
|
# OCI pins IMPORTED images to firmware=BIOS and A1 is UEFI-only, so a BIOS
|
|
518
539
|
# verdict means the import would die at launch (Shape ... is not valid for
|
|
519
540
|
# image) — skip it. Otherwise take the template's LIVE image-id from its
|
|
520
541
|
# instance record and inject the golden disk (provision-via-migrate).
|
|
521
542
|
mode="" image=""
|
|
522
543
|
custom="$(oci compute image list -c "$comp" --profile "$PROFILE" --all --sort-by TIMECREATED \
|
|
523
|
-
--query "data[?\"display-name\" != null && starts_with(\"display-name\", '
|
|
544
|
+
--query "data[?\"display-name\" != null && starts_with(\"display-name\", 'kampodine-alpine')] | [0].id" \
|
|
524
545
|
--raw-output 2>/dev/null || true)"
|
|
525
546
|
if [[ "$custom" == ocid1.image* ]]; then
|
|
526
547
|
fw="$(oci compute image get --image-id "$custom" --profile "$PROFILE" \
|
|
@@ -528,7 +549,7 @@ case "$cmd" in
|
|
|
528
549
|
if [[ "$fw" == "UEFI_64" ]]; then
|
|
529
550
|
image="$custom" mode="native"
|
|
530
551
|
else
|
|
531
|
-
say "note: newest
|
|
552
|
+
say "note: newest kampodine-alpine* custom image is firmware=${fw:-unknown} — A1 rejects BIOS-pinned imports, skipping to the platform-image + injection route"
|
|
532
553
|
fi
|
|
533
554
|
fi
|
|
534
555
|
if [[ -z "$image" ]]; then
|
|
@@ -536,7 +557,7 @@ case "$cmd" in
|
|
|
536
557
|
--query 'data."image-id"' --raw-output 2>/dev/null || true)"
|
|
537
558
|
[[ "$image" == ocid1.image* ]] || die "template instance has no resolvable image-id — cannot launch or inject"
|
|
538
559
|
mode="inject"
|
|
539
|
-
qcow2="${ALPINE_QCOW2:-${REPO_ROOT}/infra/alpine-host/build/
|
|
560
|
+
qcow2="${ALPINE_QCOW2:-${REPO_ROOT}/infra/alpine-host/build/kampodine-alpine-3.22.6-aarch64.qcow2}"
|
|
540
561
|
[[ -f "$qcow2" ]] || die "golden qcow2 not found: $qcow2 (build via packer, or set ALPINE_QCOW2)"
|
|
541
562
|
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
563
|
# ops ssh public key for the platform-image first boot. OPS_SSH_PUBKEY
|
|
@@ -549,22 +570,22 @@ case "$cmd" in
|
|
|
549
570
|
keyfile="$OPS_SSH_PUBKEY"
|
|
550
571
|
else
|
|
551
572
|
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}/
|
|
573
|
+
[[ -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)"
|
|
574
|
+
keyfile="$(mktemp "${TMPDIR:-/tmp}/kampodine-ops-pubkey.XXXXXX")"
|
|
554
575
|
printf '%s\n' "$agent_keys" > "$keyfile"
|
|
555
576
|
chmod 600 "$keyfile"
|
|
556
577
|
fi
|
|
557
578
|
fi
|
|
558
579
|
|
|
559
|
-
say "launching
|
|
580
|
+
say "launching kampodine-$color: image=${image:0:60}… ad=$oad subnet=${subnet:0:60}… mode=$mode"
|
|
560
581
|
# NOTE: --profile stays ON the launch line (script-gates static scan reads
|
|
561
582
|
# the invocation line, not array contents).
|
|
562
583
|
launch_args=(-c "$comp" --availability-domain "$oad" --subnet-id "$subnet"
|
|
563
584
|
--image-id "$image" --shape VM.Standard.A1.Flex --shape-config '{"ocpus":2,"memoryInGBs":12}'
|
|
564
|
-
--assign-public-ip true --display-name "
|
|
585
|
+
--assign-public-ip true --display-name "kampodine-$color")
|
|
565
586
|
[[ "$mode" == "inject" ]] && launch_args+=(--ssh-authorized-keys-file "$keyfile")
|
|
566
587
|
iid="$(oci compute instance launch "${launch_args[@]}" --profile "$PROFILE" --query 'data.id' --raw-output)"
|
|
567
|
-
say "LAUNCHED
|
|
588
|
+
say "LAUNCHED kampodine-$color: $iid"
|
|
568
589
|
if [[ "$mode" == "native" ]]; then
|
|
569
590
|
say "next: wait RUNNING -> ssh in -> kampodine vm-prepare -> kampodine deploy --host root@<ephemeral-ip>"
|
|
570
591
|
say "then 'bluegreen.sh flip --to $color' (health-gated) once its app checks green."
|
|
@@ -594,16 +615,16 @@ case "$cmd" in
|
|
|
594
615
|
read -r rocid raddr _ <<<"$rp"
|
|
595
616
|
rhost="${raddr//./-}.sslip.io"
|
|
596
617
|
trow="$(instance_by_color "$comp" "$to")"
|
|
597
|
-
[[ -n "$trow" ]] || die "
|
|
618
|
+
[[ -n "$trow" ]] || die "kampodine-$to is not RUNNING — nothing to flip to"
|
|
598
619
|
read -r tiid tpub _ <<<"$trow"
|
|
599
620
|
tvnic="$(primary_vnic_of "$tiid")"
|
|
600
|
-
[[ -n "$tvnic" ]] || die "no primary VNIC on
|
|
621
|
+
[[ -n "$tvnic" ]] || die "no primary VNIC on kampodine-$to"
|
|
601
622
|
tpip="$(reserved_anchor_ip "$tvnic")"
|
|
602
|
-
[[ "$tpip" == ocid1.privateip* ]] || die "could not resolve/create the reserved-anchor secondary private ip on
|
|
623
|
+
[[ "$tpip" == ocid1.privateip* ]] || die "could not resolve/create the reserved-anchor secondary private ip on kampodine-$to"
|
|
603
624
|
if instance_healthy "$tpub"; then
|
|
604
|
-
say "target health:
|
|
625
|
+
say "target health: kampodine-$to app HEALTHY on its own IP"
|
|
605
626
|
else
|
|
606
|
-
(( force )) || die "
|
|
627
|
+
(( force )) || die "kampodine-$to app UNHEALTHY — refusing flip (override: --force)"
|
|
607
628
|
say "target health: UNHEALTHY — flipping anyway (--force)"
|
|
608
629
|
fi
|
|
609
630
|
|
|
@@ -611,19 +632,19 @@ case "$cmd" in
|
|
|
611
632
|
# once the hostname resolves to the reserved ip AND routes to the target,
|
|
612
633
|
# so the guest anchor address goes FIRST, then the assign, then the cert.
|
|
613
634
|
ob=green; [[ "$to" == green ]] && ob=blue
|
|
614
|
-
say "flip[1/4]: anchor conf ->
|
|
635
|
+
say "flip[1/4]: anchor conf -> kampodine-$to guest ($tpub), watcher configures the address"
|
|
615
636
|
tsubnet="$(oci compute vnic-attachment list -c "$comp" --profile "$PROFILE" \
|
|
616
637
|
--instance-id "$tiid" --query 'data[0]."subnet-id"' --raw-output 2>/dev/null || true)"
|
|
617
|
-
[[ "$tsubnet" == ocid1.subnet* ]] || die "could not resolve
|
|
638
|
+
[[ "$tsubnet" == ocid1.subnet* ]] || die "could not resolve kampodine-$to's subnet id"
|
|
618
639
|
tcidr="$(oci network subnet get --subnet-id "$tsubnet" --profile "$PROFILE" \
|
|
619
640
|
--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
|
|
641
|
+
[[ "$tcidr" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$ ]] || die "could not resolve kampodine-$to's subnet cidr (got: ${tcidr:-none})"
|
|
621
642
|
tanchor_addr="$(oci network private-ip get --private-ip-id "$tpip" --profile "$PROFILE" \
|
|
622
643
|
--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
|
|
644
|
+
[[ "$tanchor_addr" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "could not resolve the anchor private address on kampodine-$to"
|
|
624
645
|
taddr_cidr="${tanchor_addr}/${tcidr##*/}"
|
|
625
646
|
if ! write_anchor_conf "$tpub" "$taddr_cidr" >/dev/null 2>&1; then
|
|
626
|
-
die "anchor.conf write failed on
|
|
647
|
+
die "anchor.conf write failed on kampodine-$to ($tpub) — nothing mutated, flip aborted"
|
|
627
648
|
fi
|
|
628
649
|
addr_ok=0
|
|
629
650
|
for ((i = 1; i <= FLIP_ADDR_TRIES; i++)); do
|
|
@@ -632,15 +653,15 @@ case "$cmd" in
|
|
|
632
653
|
done
|
|
633
654
|
if (( ! addr_ok )); then
|
|
634
655
|
cleanup_target_anchor "$tpub" "$taddr_cidr"
|
|
635
|
-
die "guest watcher never configured $taddr_cidr on
|
|
656
|
+
die "guest watcher never configured $taddr_cidr on kampodine-$to — is rc-service kampodine-anchor running? (conf removed, NOTHING mutated)"
|
|
636
657
|
fi
|
|
637
|
-
say "flip[2/4]: guest answers on $taddr_cidr — reserved $raddr ->
|
|
658
|
+
say "flip[2/4]: guest answers on $taddr_cidr — reserved $raddr -> kampodine-$to (anchor $tpip)"
|
|
638
659
|
oci network public-ip update --public-ip-id "$rocid" --profile "$PROFILE" \
|
|
639
660
|
--private-ip-id "$tpip" --force --wait-for-state ASSIGNED >/dev/null \
|
|
640
661
|
|| { 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
|
|
662
|
+
say "flip[3/4]: reserved IP ASSIGNED — registering $rhost on kampodine-$to's kamal-proxy (ACME HTTP-01 through the reserved ip)…"
|
|
642
663
|
if ! ssh "${SSH_OPTS[@]}" "root@$tpub" \
|
|
643
|
-
"podman exec kamal-proxy kamal-proxy deploy
|
|
664
|
+
"podman exec kamal-proxy kamal-proxy deploy kampodine-api --host=$rhost --target=kampodine-api:8080 --tls --health-check-path=/api/auth/ok"; then
|
|
644
665
|
flip_failure_rollback "$to" "$ob" "$comp" "$rocid" "$tpub" "$taddr_cidr"
|
|
645
666
|
exit 1
|
|
646
667
|
fi
|
|
@@ -656,14 +677,14 @@ case "$cmd" in
|
|
|
656
677
|
fi
|
|
657
678
|
served="$(curl -s -m 8 --resolve "$rhost:443:$raddr" "https://$rhost/api/auth/ok" || true)"
|
|
658
679
|
say "flip[4/4]: cert for $rhost VALID + serving through $raddr"
|
|
659
|
-
say "FLIPPED: https://$raddr/ (https://$rhost/) now serves from
|
|
680
|
+
say "FLIPPED: https://$raddr/ (https://$rhost/) now serves from kampodine-$to"
|
|
660
681
|
say "served /api/auth/ok: ${served:-<no body>}"
|
|
661
682
|
;;
|
|
662
683
|
|
|
663
684
|
rollback)
|
|
664
685
|
# DORMANT rollback: holder guest cleanup
|
|
665
686
|
# (anchor.conf removal + anchor address delete — the flip tool's explicit
|
|
666
|
-
# job; the
|
|
687
|
+
# job; the kampodine-anchor watcher is add-only) then the OCI unassign
|
|
667
688
|
# (documented CLI semantics: an empty --private-ip-id unassigns). For a
|
|
668
689
|
# real pair where traffic must land on the other color, use
|
|
669
690
|
# 'flip --to <other>' — it runs the full ACME-first sequence there.
|
|
@@ -692,10 +713,10 @@ case "$cmd" in
|
|
|
692
713
|
if [[ -z "$holder_color" ]]; then
|
|
693
714
|
say "warning: reserved $raddr held by an anchor of neither RUNNING color (terminated instance?) — unassigning without guest cleanup"
|
|
694
715
|
else
|
|
695
|
-
say "current holder:
|
|
716
|
+
say "current holder: kampodine-$holder_color ($holder_ip) — cleaning the guest anchor, then unassigning"
|
|
696
717
|
conf_addr="$(read_anchor_conf_addr "$holder_ip")"
|
|
697
718
|
cleanup_target_anchor "$holder_ip" "$conf_addr"
|
|
698
|
-
say "
|
|
719
|
+
say "kampodine-$holder_color guest cleaned (anchor.conf removed${conf_addr:+, address $conf_addr deleted})"
|
|
699
720
|
fi
|
|
700
721
|
say "unassigning reserved $raddr (-> dormant)…"
|
|
701
722
|
oci network public-ip update --public-ip-id "$rocid" --profile "$PROFILE" \
|