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/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 ->
|
|
@@ -20,27 +20,47 @@
|
|
|
20
20
|
# kampodine deploy --version <sha7> # stream an existing local build
|
|
21
21
|
# kampodine deploy --rollback [<sha7>] # default: previous
|
|
22
22
|
# kampodine deploy --host root@<ip> ... # target VM override
|
|
23
|
+
# kampodine deploy --profile <name> ... # per-instance profile (config.json)
|
|
23
24
|
# kampodine deploy --dockerfile apps/api-go/Dockerfile.cutover # Go api image
|
|
25
|
+
# kampodine deploy list|prune|logs|restart|shell # lifecycle ops (each has --help)
|
|
24
26
|
# --skip-smoke # skip the public smoke (behind a not-yet-switched proxy)
|
|
25
27
|
# --refresh-config # ansible container-service refresh BEFORE restart
|
|
28
|
+
# --require-disk <pct> # fail closed when VM disk usage >= pct (default: warn above 90)
|
|
26
29
|
set -euo pipefail
|
|
27
30
|
|
|
31
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
32
|
+
# Lifecycle subcommands (list/prune/logs/restart/shell) are read-mostly
|
|
33
|
+
# VM/ledger ops — they never build or stream, must work OUTSIDE the app repo,
|
|
34
|
+
# and must not pay the deploy preflight (git/podman). Delegate first.
|
|
35
|
+
case "${1:-}" in
|
|
36
|
+
list|prune|logs|restart|shell)
|
|
37
|
+
exec bash "$HERE/deploy-lifecycle.sh" "$@"
|
|
38
|
+
;;
|
|
39
|
+
esac
|
|
40
|
+
# shellcheck source=common.sh
|
|
41
|
+
source "$HERE/common.sh"
|
|
42
|
+
|
|
28
43
|
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
|
29
|
-
|
|
44
|
+
KAMPODINE_HOST="${KAMPODINE_HOST:-}"
|
|
30
45
|
PROXY_HOST="${PROXY_HOST:-app.example.com}"
|
|
31
|
-
ENV_FILE_REMOTE="/etc/
|
|
32
|
-
DEPLOYED_SHA_FILE="/etc/
|
|
46
|
+
ENV_FILE_REMOTE="/etc/kampodine/env"
|
|
47
|
+
DEPLOYED_SHA_FILE="/etc/kampodine/deployed-sha"
|
|
33
48
|
ENV_CLEAR_KEYS='^(NODE_ENV|PORT|LIBSQL_TENANT_DIR|LIBSQL_API_MOUNT|STATIC_SPA_MOUNT)='
|
|
34
|
-
IMAGE="127.0.0.1:5000/
|
|
49
|
+
IMAGE="127.0.0.1:5000/kampodine-api"
|
|
50
|
+
DISK_PATH="/var/lib/containers"
|
|
35
51
|
|
|
36
52
|
MODE="deploy"
|
|
37
53
|
VERSION=""
|
|
38
54
|
SKIP_SMOKE=0
|
|
39
55
|
REFRESH_CONFIG=0
|
|
56
|
+
REQUIRE_DISK=""
|
|
57
|
+
PROFILE_FLAG=""
|
|
58
|
+
HOST_FLAG=""
|
|
59
|
+
KEY_FLAG=""
|
|
40
60
|
# SSH key resolution — GENERIC, no hardcoded personal paths:
|
|
41
61
|
# 1. --ssh-key flag (explicit, per-invocation)
|
|
42
|
-
# 2.
|
|
43
|
-
# 3.
|
|
62
|
+
# 2. profile sshKey (--profile | KAMPODINE_PROFILE | defaultProfile)
|
|
63
|
+
# 3. KAMPODINE_SSH_KEY env (legacy: direnv / .envrc / export)
|
|
44
64
|
# 4. empty → ssh-agent and/or the operator's ~/.ssh/config Host block
|
|
45
65
|
# (the POSIX way: per-host IdentityFile belongs in ssh config, not here)
|
|
46
66
|
if [ -n "${KAMPODINE_SSH_KEY:-}" ]; then
|
|
@@ -58,7 +78,7 @@ die() { printf '\033[1;31m[deploy] FAIL:\033[0m %s\n' "$*" >&2; exit 1; }
|
|
|
58
78
|
usage() {
|
|
59
79
|
cat <<'EOF'
|
|
60
80
|
Usage:
|
|
61
|
-
kampodine deploy [--host root@<ip>] [--version <sha7>] [--rollback [<sha7>]]
|
|
81
|
+
kampodine deploy [--host root@<ip>] [--profile <name>] [--version <sha7>] [--rollback [<sha7>]]
|
|
62
82
|
[--dockerfile <path>] [--ssh-key <path>] [--skip-smoke] [--refresh-config]
|
|
63
83
|
|
|
64
84
|
Examples:
|
|
@@ -66,16 +86,19 @@ EOF
|
|
|
66
86
|
grep '^# kampodine deploy' "$0" | sed 's/^# //'
|
|
67
87
|
cat <<'EOF'
|
|
68
88
|
|
|
69
|
-
Host/key resolution: --host |
|
|
70
|
-
|
|
89
|
+
Host/key resolution: --host | --profile <name> | KAMPODINE_PROFILE |
|
|
90
|
+
config defaultProfile | KAMPODINE_HOST; --ssh-key | profile sshKey |
|
|
91
|
+
KAMPODINE_SSH_KEY | ssh-agent / ~/.ssh/config. Profiles:
|
|
92
|
+
kampodine config init --name prod --host root@<ip> --ssh-key <path>.
|
|
71
93
|
EOF
|
|
72
94
|
exit 0
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
while [[ $# -gt 0 ]]; do
|
|
76
98
|
case "$1" in
|
|
77
|
-
--host)
|
|
78
|
-
--ssh-key) SSH_KEY="$2"; shift 2 ;;
|
|
99
|
+
--host) HOST_FLAG="$2"; KAMPODINE_HOST="$2"; shift 2 ;;
|
|
100
|
+
--ssh-key) KEY_FLAG="$2"; SSH_KEY="$2"; shift 2 ;;
|
|
101
|
+
--profile) PROFILE_FLAG="$2"; shift 2 ;;
|
|
79
102
|
--version) VERSION="$2"; MODE="version"; shift 2 ;;
|
|
80
103
|
--rollback)
|
|
81
104
|
MODE="rollback"
|
|
@@ -83,19 +106,27 @@ while [[ $# -gt 0 ]]; do
|
|
|
83
106
|
;;
|
|
84
107
|
--skip-smoke) SKIP_SMOKE=1; shift ;;
|
|
85
108
|
--refresh-config) REFRESH_CONFIG=1; shift ;;
|
|
109
|
+
--require-disk) REQUIRE_DISK="$2"; shift 2 ;;
|
|
86
110
|
--dockerfile) DOCKERFILE="$2"; shift 2 ;;
|
|
87
111
|
-h|--help) usage ;;
|
|
88
112
|
*) die "unknown argument: $1 (--help)" ;;
|
|
89
113
|
esac
|
|
90
114
|
done
|
|
91
115
|
[[ "$VERSION" != *..* && "$VERSION" =~ ^[0-9a-f]{4,40}$|^$ ]] || die "--version must be a git sha fragment"
|
|
92
|
-
[ -
|
|
116
|
+
[[ -z "$REQUIRE_DISK" || "$REQUIRE_DISK" =~ ^[0-9]+$ && "$REQUIRE_DISK" -le 100 ]] || die "--require-disk must be a percentage 0-100 (got: $REQUIRE_DISK)"
|
|
117
|
+
# profile resolution (flag > KAMPODINE_PROFILE > defaultProfile); an explicit
|
|
118
|
+
# --host/--ssh-key flag always beats the profile, the profile beats the
|
|
119
|
+
# legacy env; proxyHost from the profile REPLACES the PROXY_HOST env
|
|
120
|
+
profile_resolve "$PROFILE_FLAG" "$HOST_FLAG" "$KEY_FLAG"
|
|
121
|
+
SSH_KEY="${KEY_FLAG:-${KAMPODINE_SSH_KEY:-}}"
|
|
122
|
+
PROXY_HOST="${PROFILE_PROXY_HOST:-${PROXY_HOST:-app.example.com}}"
|
|
123
|
+
[ -n "$KAMPODINE_HOST" ] || die "target required: --host root@<vm-ip>, --profile <name>, KAMPODINE_PROFILE, config defaultProfile, or KAMPODINE_HOST=root@<vm-ip>"
|
|
93
124
|
|
|
94
125
|
SSH_ARGS=(-o ConnectTimeout=10 -o BatchMode=yes)
|
|
95
126
|
[[ -n "$SSH_KEY" ]] && SSH_ARGS+=(-i "$SSH_KEY")
|
|
96
127
|
# $1 is a composed remote command — client-side expansion is the design.
|
|
97
128
|
# shellcheck disable=SC2029
|
|
98
|
-
vm() { ssh "${SSH_ARGS[@]}" "$
|
|
129
|
+
vm() { ssh "${SSH_ARGS[@]}" "$KAMPODINE_HOST" "$1"; }
|
|
99
130
|
|
|
100
131
|
# --- macOS ssh-agent quirk (first deploy from a fresh machine) ---------------
|
|
101
132
|
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
@@ -103,6 +134,67 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
|
103
134
|
export SSH_AUTH_SOCK
|
|
104
135
|
fi
|
|
105
136
|
|
|
137
|
+
# --- host init detection (first connect): openrc vs systemd + http client ----
|
|
138
|
+
# Groundwork for non-Alpine targets; on Alpine the verdict is openrc and every
|
|
139
|
+
# svc_action output is byte-identical to the historical strings. The verdict
|
|
140
|
+
# is cached in the active profile entry ("init" field) when one resolves.
|
|
141
|
+
init_detect vm >/dev/null 2>&1 || true
|
|
142
|
+
|
|
143
|
+
# --- deployment ledger: every run appends to ~/.kampodine/deployments.jsonl ---
|
|
144
|
+
# --- {ts, host, sha, tag, result, duration_ms, subject} — shas/tags/hosts ----
|
|
145
|
+
# --- only, NEVER secrets. The EXIT trap records failures + prints the footer --
|
|
146
|
+
# --- so EVERY deploy run ends with the total deployments count. --
|
|
147
|
+
DEPLOY_START_SECONDS="$SECONDS"
|
|
148
|
+
DEPLOY_RECORDING=0
|
|
149
|
+
DEPLOY_APPENDED=0
|
|
150
|
+
DEPLOY_RESULT="success"
|
|
151
|
+
ENV_TMP=""
|
|
152
|
+
deploy_on_exit() {
|
|
153
|
+
local rc=$?
|
|
154
|
+
[[ -n "$ENV_TMP" ]] && rm -f "$ENV_TMP" 2>/dev/null
|
|
155
|
+
if [[ "$DEPLOY_RECORDING" -eq 1 && "$DEPLOY_APPENDED" -ne 1 ]]; then
|
|
156
|
+
# never made it to the append: this run FAILED
|
|
157
|
+
ledger_append "$KAMPODINE_HOST" "$VER" "$VER" "failed" \
|
|
158
|
+
$(( (SECONDS - DEPLOY_START_SECONDS) * 1000 )) "" || true
|
|
159
|
+
DEPLOY_RESULT="failed"
|
|
160
|
+
fi
|
|
161
|
+
if [[ "$DEPLOY_RECORDING" -eq 1 ]]; then
|
|
162
|
+
if [[ "$DEPLOY_RESULT" == "failed" ]]; then
|
|
163
|
+
say "Total deployments: $(ledger_count "$KAMPODINE_HOST") · FAILED — attempted tag: $VER (not activated)"
|
|
164
|
+
else
|
|
165
|
+
say "Total deployments: $(ledger_count "$KAMPODINE_HOST") · current tag: $VER"
|
|
166
|
+
fi
|
|
167
|
+
fi
|
|
168
|
+
exit "$rc"
|
|
169
|
+
}
|
|
170
|
+
trap deploy_on_exit EXIT
|
|
171
|
+
|
|
172
|
+
# --- disk guard (2026-10-08 incident class: old sha-tagged deploy images ------
|
|
173
|
+
# --- silently filled the VM disk until sqlite writes failed with I/O errors). -
|
|
174
|
+
# --- Read-only VM df; LOUD warning above 90%; --require-disk <pct> fails closed
|
|
175
|
+
# --- BEFORE the build. phase = gate (pre-deploy, may fail) | report (warn-only)
|
|
176
|
+
vm_disk_check() {
|
|
177
|
+
local phase="${1:-report}" df_out pct verdict
|
|
178
|
+
df_out="$(vm "df -P $DISK_PATH 2>/dev/null" || true)"
|
|
179
|
+
pct="$(disk_pct_from_df "$df_out")"
|
|
180
|
+
verdict="$(disk_verdict "$pct" "$REQUIRE_DISK")"
|
|
181
|
+
case "$verdict" in
|
|
182
|
+
fail)
|
|
183
|
+
die "VM disk at ${pct}% (>= --require-disk ${REQUIRE_DISK}%) — free space first: kampodine deploy prune --dry-run"
|
|
184
|
+
;;
|
|
185
|
+
warn)
|
|
186
|
+
say "WARNING: VM disk $DISK_PATH at ${pct}% — old sha-tagged deploy images pile up (~1GB each)"
|
|
187
|
+
say "WARNING: reclaim space: kampodine deploy prune --dry-run (this deploy continues)"
|
|
188
|
+
;;
|
|
189
|
+
unknown)
|
|
190
|
+
if [[ "$phase" == "gate" && -n "$REQUIRE_DISK" ]]; then
|
|
191
|
+
die "cannot read VM disk usage (df $DISK_PATH) — --require-disk is fail-closed"
|
|
192
|
+
fi
|
|
193
|
+
[[ "$phase" == "gate" ]] && say "disk usage unknown (df unreadable) — continuing"
|
|
194
|
+
;;
|
|
195
|
+
esac
|
|
196
|
+
}
|
|
197
|
+
|
|
106
198
|
# --- preflight -----------------------------------------------------------------
|
|
107
199
|
cd "$REPO_ROOT"
|
|
108
200
|
if [[ "$MODE" == "deploy" ]]; then
|
|
@@ -110,10 +202,15 @@ if [[ "$MODE" == "deploy" ]]; then
|
|
|
110
202
|
fi
|
|
111
203
|
command -v podman >/dev/null 2>&1 || die "podman not found on the Mac (podman machine must run: podman machine start)"
|
|
112
204
|
podman info >/dev/null 2>&1 || die "podman machine not reachable (podman machine start)"
|
|
205
|
+
# disk gate: BEFORE the build — failing fast beats streaming 1GB into a full disk
|
|
206
|
+
vm_disk_check gate
|
|
113
207
|
# version mode: stream an EXISTING local build (no rebuild, no clean-tree gate —
|
|
114
208
|
# the build already happened when that sha was HEAD)
|
|
115
209
|
[[ "$MODE" == "version" ]] && VER="${VERSION:-$(git rev-parse --short HEAD)}"
|
|
116
210
|
[[ "$MODE" != "version" ]] && VER="$(git rev-parse --short HEAD)"
|
|
211
|
+
# from here on, every outcome is recorded in the ledger (success via the
|
|
212
|
+
# append at the end; anything else via the EXIT trap)
|
|
213
|
+
DEPLOY_RECORDING=1
|
|
117
214
|
|
|
118
215
|
# --- build (deploy mode always builds HEAD — cached layers keep it minutes) ----
|
|
119
216
|
if [[ "$MODE" == "deploy" ]]; then
|
|
@@ -141,8 +238,8 @@ vm "podman tag $IMAGE:$VER $IMAGE:latest" || die "stream retag failed"
|
|
|
141
238
|
|
|
142
239
|
# --- env file from varlock (schema-only from HEAD; secrets from pass) ----------
|
|
143
240
|
say "generating $ENV_FILE_REMOTE from varlock (schema-only from HEAD; secrets resolve from pass — no plaintext secret files)…"
|
|
144
|
-
ENV_TMP="$(mktemp /tmp/
|
|
145
|
-
trap
|
|
241
|
+
ENV_TMP="$(mktemp /tmp/kampodine-env.XXXXXX)"
|
|
242
|
+
# (the EXIT trap — deploy_on_exit — removes $ENV_TMP on every exit path)
|
|
146
243
|
# Schema-only resolution: extract the COMMITTED schema into a scratch dir
|
|
147
244
|
# inside the repo (plugin resolution needs node_modules; local
|
|
148
245
|
# .env/.env.local never enter the pipeline — varlock sees only the extracted
|
|
@@ -158,29 +255,32 @@ git -C "$REPO_ROOT" archive HEAD apps/api/.env.schema | tar -x -C "$ENV_SCHEMA_D
|
|
|
158
255
|
| sed -E 's/^([A-Za-z_][A-Za-z0-9_]*)="(.*)"$/\1=\2/' > "$ENV_TMP" \
|
|
159
256
|
|| die "varlock env generation failed (pass store unlocked? schema committed?)"
|
|
160
257
|
printf 'API_GIT_SHA=%s\n' "$VER" >> "$ENV_TMP"
|
|
161
|
-
scp -q "${SSH_ARGS[@]}" "$ENV_TMP" "$
|
|
162
|
-
vm "mv /etc/
|
|
258
|
+
scp -q "${SSH_ARGS[@]}" "$ENV_TMP" "$KAMPODINE_HOST:/etc/kampodine/env.tmp"
|
|
259
|
+
vm "mv /etc/kampodine/env.tmp $ENV_FILE_REMOTE && chmod 600 $ENV_FILE_REMOTE"
|
|
163
260
|
rm -f "$ENV_TMP"
|
|
164
261
|
rm -rf "$ENV_SCHEMA_DIR"
|
|
165
262
|
|
|
166
263
|
if [[ $REFRESH_CONFIG -eq 1 ]]; then
|
|
167
264
|
say "ansible container-service refresh (--tags container-service — rc script/config drift repair)…"
|
|
168
265
|
(cd "$REPO_ROOT/infra/alpine-host/ansible" \
|
|
169
|
-
&& ansible-playbook -i "
|
|
266
|
+
&& 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
267
|
|| die "ansible playbook failed"
|
|
171
268
|
fi
|
|
172
269
|
|
|
173
|
-
|
|
174
|
-
|
|
270
|
+
RESTART_CMD="$(svc_action kampodine-api restart)" \
|
|
271
|
+
|| die "neither rc-service nor systemctl found on $KAMPODINE_HOST — cannot restart the service (host init auto-detection failed)"
|
|
272
|
+
STATUS_CMD="$(svc_action kampodine-api status 2>/dev/null || true)"
|
|
273
|
+
say "$RESTART_CMD (supervise-daemon on Alpine: stop-old/start-new podman run on the retagged :latest)…"
|
|
274
|
+
vm "$RESTART_CMD" || die "kampodine-api restart failed (${STATUS_CMD:-check the service})"
|
|
175
275
|
|
|
176
|
-
say "health probe (host-side
|
|
276
|
+
say "health probe (host-side fetch of /api/auth/ok + served-sha — busybox wget default, curl fallback on GNU-only hosts)…"
|
|
177
277
|
HEALTH_OK=0
|
|
178
278
|
for _ in $(seq 1 30); do
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
# (busybox BRE alternation + nested quotes →
|
|
182
|
-
# grep -E keeps the alternation portable
|
|
183
|
-
if vm "
|
|
279
|
+
# the fetch runs on the VM (busybox wget by default; curl when the host has
|
|
280
|
+
# none — remote_http_cmd in common.sh); the served-sha grep runs LOCALLY —
|
|
281
|
+
# remote grep quoting is fragile (busybox BRE alternation + nested quotes →
|
|
282
|
+
# permanent false-negative). grep -E keeps the alternation portable.
|
|
283
|
+
if vm "$(remote_http_cmd "http://127.0.0.1:8080/api/auth/ok")" \
|
|
184
284
|
| grep -qE "\"(git|build)\":\"$VER"; then
|
|
185
285
|
HEALTH_OK=1
|
|
186
286
|
break
|
|
@@ -188,17 +288,17 @@ for _ in $(seq 1 30); do
|
|
|
188
288
|
sleep 3
|
|
189
289
|
done
|
|
190
290
|
if [[ $HEALTH_OK -ne 1 ]]; then
|
|
191
|
-
vm "podman logs --tail 30
|
|
192
|
-
vm "
|
|
291
|
+
vm "podman logs --tail 30 kampodine-api 2>&1" || true
|
|
292
|
+
vm "$(svc_action kampodine-api status 2>/dev/null || echo 'true') 2>&1" || true
|
|
193
293
|
PREV_HINT="$(vm "test -f $DEPLOYED_SHA_FILE && cat $DEPLOYED_SHA_FILE" 2>/dev/null || true)"
|
|
194
294
|
die "container never became healthy — rollback: kampodine deploy --rollback${PREV_HINT:+ $PREV_HINT}"
|
|
195
295
|
fi
|
|
196
|
-
CANARY="$(vm "podman logs
|
|
296
|
+
CANARY="$(vm "podman logs kampodine-api 2>&1 | grep -c ENV_IMPORT_SUSPECT || true")"
|
|
197
297
|
say "container healthy, env-canary hits: $CANARY"
|
|
198
298
|
|
|
199
299
|
say "kamal-proxy re-point (podman exec — same invocation kamal used)…"
|
|
200
300
|
if vm "podman ps --format '{{.Names}}' | grep -qx kamal-proxy"; then
|
|
201
|
-
vm "podman exec kamal-proxy kamal-proxy deploy
|
|
301
|
+
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
302
|
|| die "proxy re-point failed (podman logs kamal-proxy)"
|
|
203
303
|
else
|
|
204
304
|
say " kamal-proxy not running — skipping re-point"
|
|
@@ -220,4 +320,21 @@ vm "podman image prune -f >/dev/null 2>&1 || true"
|
|
|
220
320
|
# busybox head lacks -n -N — tail -n +4 after a newest-first sort keeps 3 tags.
|
|
221
321
|
vm "podman images --format '{{.Tag}} {{.CreatedAt}}' $IMAGE 2>/dev/null | grep -E '^[0-9a-f]{7,40} ' | sort -k2,2r | tail -n +4 | awk '{print \$1}' | while read -r old; do podman rmi \"$IMAGE:\$old\" >/dev/null 2>&1 || true; done" || true
|
|
222
322
|
[[ "$MODE" == "rollback" ]] || vm "echo $VER > $DEPLOYED_SHA_FILE" || true
|
|
323
|
+
|
|
324
|
+
# --- post-deploy disk report (warn-only — the deploy already succeeded) --------
|
|
325
|
+
vm_disk_check report
|
|
326
|
+
|
|
327
|
+
# --- deployment ledger append: this run made it all the way through ------------
|
|
328
|
+
DEPLOY_RESULT="success"
|
|
329
|
+
[[ "$MODE" == "rollback" ]] && DEPLOY_RESULT="rollback"
|
|
330
|
+
if [[ "$MODE" == "rollback" ]]; then
|
|
331
|
+
# the subject of a rollback is the ORIGINAL deploy's subject — the ledger
|
|
332
|
+
# knows it; git HEAD would be the wrong commit's message
|
|
333
|
+
SUBJECT="$(ledger_subject_for_tag "$KAMPODINE_HOST" "$VER")"
|
|
334
|
+
else
|
|
335
|
+
SUBJECT="$(git -C "$REPO_ROOT" log -1 --pretty=%s 2>/dev/null || true)"
|
|
336
|
+
fi
|
|
337
|
+
ledger_append "$KAMPODINE_HOST" "$VER" "$VER" "$DEPLOY_RESULT" \
|
|
338
|
+
$(( (SECONDS - DEPLOY_START_SECONDS) * 1000 )) "$SUBJECT" || true
|
|
339
|
+
DEPLOY_APPENDED=1
|
|
223
340
|
say "done (version: $VER). instant rollback: kampodine deploy --rollback"
|
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,9 +23,21 @@
|
|
|
23
23
|
# (all: optional --zone <id-or-name>; --instance-principal for instance auth)
|
|
24
24
|
set -euo pipefail
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
# NOTE: no $(dirname) here — some hermetic test PATHs strip coreutils;
|
|
27
|
+
# parameter expansion keeps HERE resolution dependency-free.
|
|
28
|
+
HERE="${BASH_SOURCE[0]%/*}"
|
|
29
|
+
# shellcheck source=common.sh
|
|
30
|
+
source "$HERE/common.sh"
|
|
31
|
+
|
|
32
|
+
# NOTE on --profile: dns.sh follows the OCI auth convention — PROFILE below is
|
|
33
|
+
# the OCI CONFIG profile (OCI_PROFILE). The kampodine INSTANCE profile flag
|
|
34
|
+
# (--profile <name>) is parsed into PROFILE_FLAG and validated via
|
|
35
|
+
# profile_resolve (uniform resolution across every subcommand); dns consumes
|
|
36
|
+
# no instance host/sshKey fields.
|
|
37
|
+
PROFILE="${OCI_PROFILE:-default}"
|
|
38
|
+
COMPARTMENT_NAME="${OCI_COMPARTMENT:-kampodine}"
|
|
28
39
|
AUTH_ARGS=()
|
|
40
|
+
PROFILE_FLAG=""
|
|
29
41
|
|
|
30
42
|
say() { printf '\033[1;34m[dns]\033[0m %s\n' "$*"; }
|
|
31
43
|
die() { printf '\033[1;31m[dns] FAIL:\033[0m %s\n' "$*" >&2; exit 1; }
|
|
@@ -37,13 +49,13 @@ usage() {
|
|
|
37
49
|
|
|
38
50
|
Auth is the oci CLI's own — its config file (--profile) or instance
|
|
39
51
|
principal. kampodine never accepts, stores, or logs credential material.
|
|
40
|
-
Zone/compartment: OCI_PROFILE (default
|
|
41
|
-
|
|
52
|
+
Zone/compartment: OCI_PROFILE (default: "default"), OCI_COMPARTMENT (default
|
|
53
|
+
kampodine). Types are pinned to A | AAAA | CNAME; ttl range 60..172800.
|
|
42
54
|
|
|
43
55
|
Examples:
|
|
44
56
|
kampodine dns records
|
|
45
|
-
kampodine dns records --zone
|
|
46
|
-
kampodine dns add --name app --type A --value 203.0.113.10 --zone
|
|
57
|
+
kampodine dns records --zone kampodine.example.com
|
|
58
|
+
kampodine dns add --name app --type A --value 203.0.113.10 --zone kampodine.example.com
|
|
47
59
|
kampodine dns add --name www --type CNAME --value app.example.com --ttl 3600
|
|
48
60
|
kampodine dns rm --name app --type A --value 203.0.113.10
|
|
49
61
|
EOF
|
|
@@ -100,17 +112,24 @@ while [[ $# -gt 0 ]]; do
|
|
|
100
112
|
--type) RTYPE="$2"; shift 2 ;;
|
|
101
113
|
--value) VALUE="$2"; shift 2 ;;
|
|
102
114
|
--ttl) TTL="$2"; shift 2 ;;
|
|
115
|
+
--profile) PROFILE_FLAG="$2"; shift 2 ;;
|
|
103
116
|
--instance-principal) AUTH_ARGS=(--auth instance_principal); shift ;;
|
|
104
117
|
-h|--help) usage 0 ;;
|
|
105
118
|
*) die "unknown argument: $1 (--help)" ;;
|
|
106
119
|
esac
|
|
107
120
|
done
|
|
108
121
|
|
|
122
|
+
# validate the kampodine instance profile when selected (uniform resolution);
|
|
123
|
+
# dns consumes none of its host/sshKey fields
|
|
124
|
+
profile_resolve "$PROFILE_FLAG" "" ""
|
|
125
|
+
|
|
109
126
|
need_provider() {
|
|
110
127
|
command -v oci >/dev/null 2>&1 \
|
|
111
128
|
|| 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
129
|
command -v jq >/dev/null 2>&1 \
|
|
113
130
|
|| die "jq not found — brew install jq (dns.sh uses it for RRSet surgery)"
|
|
131
|
+
[[ -n "${OCI_COMPARTMENT:-}" ]] \
|
|
132
|
+
|| die "set OCI_COMPARTMENT=<compartment name or ocid> — no default: compartments are account-specific"
|
|
114
133
|
}
|
|
115
134
|
|
|
116
135
|
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,29 +8,33 @@
|
|
|
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:
|
|
16
|
-
# kampodine env list [--host <user@ip>]
|
|
17
|
-
# kampodine env push --file <local-env-file> [...]
|
|
18
|
-
# kampodine env pull [--out <file>] [...]
|
|
19
|
-
# kampodine env fingerprint [--file <f>]
|
|
16
|
+
# kampodine env list [--host <user@ip>] [--profile <name>] # KEY + fingerprint table (NEVER values)
|
|
17
|
+
# kampodine env push --file <local-env-file> [...] [--profile <name>] # upload: 0600 temp + atomic mv + restart hint
|
|
18
|
+
# kampodine env pull [--out <file>] [...] [--profile <name>] # raw payload to stdout/--out (0600); masked summary follows it
|
|
19
|
+
# kampodine env fingerprint [--file <f>] # preview the masking for a LOCAL file / stdin (never values)
|
|
20
20
|
#
|
|
21
21
|
# push sends the file VERBATIM (what you push is what lands). deploy's env
|
|
22
22
|
# step additionally drops rc-script-owned CLEAR keys — keep those out of the
|
|
23
23
|
# file you push unless you mean to own them here.
|
|
24
24
|
set -euo pipefail
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
HERE="${BASH_SOURCE[0]%/*}"
|
|
27
|
+
# shellcheck source=common.sh
|
|
28
|
+
source "$HERE/common.sh"
|
|
29
|
+
|
|
30
|
+
ENV_FILE_REMOTE="${KAMPODINE_ENV_REMOTE:-/etc/kampodine/env}"
|
|
27
31
|
ENV_TMP_BASE="$(dirname "$ENV_FILE_REMOTE")"
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
KAMPODINE_HOST="${KAMPODINE_HOST:-}"
|
|
30
34
|
# SSH key resolution — same ladder as deploy.sh:
|
|
31
35
|
# 1. --ssh-key flag (explicit, per-invocation)
|
|
32
36
|
# 2. KAMPODINE_SSH_KEY env (project-level: direnv / .envrc / export)
|
|
33
|
-
# 3.
|
|
37
|
+
# 3. KAMPODINE_SSH_KEY env (legacy alias, kept for existing setups)
|
|
34
38
|
# 4. empty → ssh-agent and/or the operator's ~/.ssh/config Host block
|
|
35
39
|
if [ -n "${KAMPODINE_SSH_KEY:-}" ]; then
|
|
36
40
|
SSH_KEY="$KAMPODINE_SSH_KEY"
|
|
@@ -47,8 +51,9 @@ usage() {
|
|
|
47
51
|
|
|
48
52
|
Fingerprints NEVER leak values: every line is KEY + value length + first 2 chars.
|
|
49
53
|
Raw values move only in push's upload stream and pull's stdout/--out payload.
|
|
50
|
-
Host/key resolution matches deploy.sh: --host |
|
|
51
|
-
|
|
54
|
+
Host/key resolution matches deploy.sh: --host | --profile <name> |
|
|
55
|
+
KAMPODINE_PROFILE | config defaultProfile | KAMPODINE_HOST; --ssh-key |
|
|
56
|
+
profile sshKey | KAMPODINE_SSH_KEY | ssh-agent / ~/.ssh/config.
|
|
52
57
|
|
|
53
58
|
Examples:
|
|
54
59
|
kampodine env list --host root@203.0.113.10
|
|
@@ -70,10 +75,14 @@ esac
|
|
|
70
75
|
|
|
71
76
|
FILE=""
|
|
72
77
|
OUT=""
|
|
78
|
+
PROFILE_FLAG=""
|
|
79
|
+
HOST_FLAG=""
|
|
80
|
+
KEY_FLAG=""
|
|
73
81
|
while [[ $# -gt 0 ]]; do
|
|
74
82
|
case "$1" in
|
|
75
|
-
--host)
|
|
76
|
-
--ssh-key) SSH_KEY="$2"; shift 2 ;;
|
|
83
|
+
--host) HOST_FLAG="$2"; KAMPODINE_HOST="$2"; shift 2 ;;
|
|
84
|
+
--ssh-key) KEY_FLAG="$2"; SSH_KEY="$2"; shift 2 ;;
|
|
85
|
+
--profile) PROFILE_FLAG="$2"; shift 2 ;;
|
|
77
86
|
--file) FILE="$2"; shift 2 ;;
|
|
78
87
|
--out) OUT="$2"; shift 2 ;;
|
|
79
88
|
-h|--help) usage ;;
|
|
@@ -81,11 +90,16 @@ while [[ $# -gt 0 ]]; do
|
|
|
81
90
|
esac
|
|
82
91
|
done
|
|
83
92
|
|
|
93
|
+
# profile resolution (flag > KAMPODINE_PROFILE > defaultProfile); an explicit
|
|
94
|
+
# --host/--ssh-key flag always beats the profile
|
|
95
|
+
profile_resolve "$PROFILE_FLAG" "$HOST_FLAG" "$KEY_FLAG"
|
|
96
|
+
SSH_KEY="${KEY_FLAG:-${KAMPODINE_SSH_KEY:-}}"
|
|
97
|
+
|
|
84
98
|
SSH_ARGS=(-o ConnectTimeout=10 -o BatchMode=yes)
|
|
85
99
|
[[ -n "$SSH_KEY" ]] && SSH_ARGS+=(-i "$SSH_KEY")
|
|
86
100
|
# $1 is a composed remote command — client-side expansion is the design.
|
|
87
101
|
# shellcheck disable=SC2029
|
|
88
|
-
vm() { ssh "${SSH_ARGS[@]}" "$
|
|
102
|
+
vm() { ssh "${SSH_ARGS[@]}" "$KAMPODINE_HOST" "$1"; }
|
|
89
103
|
|
|
90
104
|
# --- macOS ssh-agent quirk (first run from a fresh machine) -------------------
|
|
91
105
|
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
@@ -94,7 +108,7 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
|
94
108
|
fi
|
|
95
109
|
|
|
96
110
|
require_host() {
|
|
97
|
-
[[ -n "$
|
|
111
|
+
[[ -n "$KAMPODINE_HOST" ]] || die "target required: --host root@<ip>, --profile <name>, KAMPODINE_PROFILE, config defaultProfile, or KAMPODINE_HOST=root@<ip> (resolution matches deploy.sh)"
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
# env_fp_lines — env content on stdin -> fingerprint table on stdout.
|
|
@@ -126,7 +140,7 @@ env_fp_lines() {
|
|
|
126
140
|
}
|
|
127
141
|
|
|
128
142
|
fetch_remote_env() {
|
|
129
|
-
vm "cat $ENV_FILE_REMOTE" || die "cannot read $ENV_FILE_REMOTE on $
|
|
143
|
+
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
144
|
}
|
|
131
145
|
|
|
132
146
|
case "$SUB" in
|
|
@@ -142,7 +156,7 @@ case "$SUB" in
|
|
|
142
156
|
require_host
|
|
143
157
|
count="$(grep -cE '^[A-Za-z_][A-Za-z0-9_]*=' "$FILE" || true)"
|
|
144
158
|
[[ "${count:-0}" -gt 0 ]] || die "no KEY=VALUE lines in $FILE — nothing to push"
|
|
145
|
-
say "pushing $FILE -> $
|
|
159
|
+
say "pushing $FILE -> $KAMPODINE_HOST:$ENV_FILE_REMOTE (fingerprint summary below; values are NEVER printed)"
|
|
146
160
|
env_fp_lines < "$FILE"
|
|
147
161
|
tmp_remote="$ENV_TMP_BASE/env.tmp.$$"
|
|
148
162
|
# 0600 FROM CREATION: umask 077 + ssh stdin pipe — the temp is never
|
|
@@ -151,8 +165,8 @@ case "$SUB" in
|
|
|
151
165
|
vm "umask 077; cat > $tmp_remote" < "$FILE" || die "upload failed"
|
|
152
166
|
vm "chmod 600 $tmp_remote && mv -f $tmp_remote $ENV_FILE_REMOTE" \
|
|
153
167
|
|| die "atomic install failed (remote temp left at: $tmp_remote)"
|
|
154
|
-
say "installed $ENV_FILE_REMOTE (0600) on $
|
|
155
|
-
say "restart to apply: ssh $
|
|
168
|
+
say "installed $ENV_FILE_REMOTE (0600) on $KAMPODINE_HOST"
|
|
169
|
+
say "restart to apply: ssh $KAMPODINE_HOST 'rc-service kampodine-api restart' # or: kampodine deploy"
|
|
156
170
|
;;
|
|
157
171
|
|
|
158
172
|
pull)
|
|
@@ -167,7 +181,7 @@ case "$SUB" in
|
|
|
167
181
|
# stdout IS the payload (pipe into whatever needs the values);
|
|
168
182
|
# the human-readable summary goes to stderr, masked.
|
|
169
183
|
printf '%s\n' "$raw"
|
|
170
|
-
say "fingerprint summary for $ENV_FILE_REMOTE on $
|
|
184
|
+
say "fingerprint summary for $ENV_FILE_REMOTE on $KAMPODINE_HOST (payload above on stdout):" >&2
|
|
171
185
|
env_fp_lines <<<"$raw" >&2
|
|
172
186
|
fi
|
|
173
187
|
;;
|
package/scripts/image-import.sh
CHANGED
|
@@ -14,28 +14,29 @@
|
|
|
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
|
+
PROFILE_FLAG=""
|
|
39
40
|
|
|
40
41
|
say() { printf '[import] %s\n' "$*"; }
|
|
41
42
|
die() { printf '[import][FAIL] %s\n' "$*" >&2; exit 1; }
|
|
@@ -57,6 +58,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
57
58
|
--bucket) BUCKET="$2"; shift 2 ;;
|
|
58
59
|
--name-prefix) NAME_PREFIX="$2"; shift 2 ;;
|
|
59
60
|
--compartment) COMPARTMENT_NAME="$2"; shift 2 ;;
|
|
61
|
+
--profile) PROFILE_FLAG="$2"; shift 2 ;;
|
|
60
62
|
--from-pass) FROM_PASS=1; shift ;;
|
|
61
63
|
--keep-object) KEEP_OBJECT=1; shift ;;
|
|
62
64
|
-h|--help) usage ;;
|
|
@@ -64,19 +66,27 @@ while [[ $# -gt 0 ]]; do
|
|
|
64
66
|
esac
|
|
65
67
|
done
|
|
66
68
|
|
|
69
|
+
# validate the kampodine instance profile when selected (uniform resolution);
|
|
70
|
+
# image-import consumes none of its host/sshKey fields (OCI-only surface)
|
|
71
|
+
HERE="${BASH_SOURCE[0]%/*}"
|
|
72
|
+
# shellcheck source=common.sh
|
|
73
|
+
source "$HERE/common.sh"
|
|
74
|
+
profile_resolve "$PROFILE_FLAG" "" ""
|
|
75
|
+
|
|
67
76
|
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)"
|
|
77
|
+
[[ -n "${OCI_COMPARTMENT:-}" ]] || die "set OCI_COMPARTMENT=<compartment name or ocid> — no default: compartments are account-specific"
|
|
68
78
|
command -v pass >/dev/null 2>&1 || true
|
|
69
79
|
|
|
70
80
|
if [[ $FROM_PASS -eq 1 ]]; then
|
|
71
81
|
command -v pass >/dev/null 2>&1 || die "pass not installed (brew install pass)"
|
|
72
82
|
say "sourcing OCI API credentials from pass (values never printed/logged)…"
|
|
73
83
|
for entry in user tenancy fingerprint api-key; do
|
|
74
|
-
pass show "
|
|
84
|
+
pass show "kampodine/oci/$entry" >/dev/null 2>&1 || die "missing pass entry kampodine/oci/$entry"
|
|
75
85
|
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
|
|
86
|
+
OCI_CLI_USER="$(pass show kampodine/oci/user)"
|
|
87
|
+
OCI_CLI_TENANCY="$(pass show kampodine/oci/tenancy)"
|
|
88
|
+
OCI_CLI_FINGERPRINT="$(pass show kampodine/oci/fingerprint)"
|
|
89
|
+
OCI_CLI_KEY_CONTENT="$(pass show kampodine/oci/api-key)"
|
|
80
90
|
export OCI_CLI_USER OCI_CLI_TENANCY OCI_CLI_FINGERPRINT OCI_CLI_KEY_CONTENT
|
|
81
91
|
fi
|
|
82
92
|
|
|
@@ -84,7 +94,7 @@ fi
|
|
|
84
94
|
if [[ -z "$IMAGE" ]]; then
|
|
85
95
|
# Constrained glob (our own build output names) — ls is fine here.
|
|
86
96
|
# shellcheck disable=SC2012
|
|
87
|
-
IMAGE="$(ls -t "$REPO_ROOT/infra/alpine-host/build"/
|
|
97
|
+
IMAGE="$(ls -t "$REPO_ROOT/infra/alpine-host/build"/kampodine-alpine-*.qcow2 2>/dev/null | head -1 || true)"
|
|
88
98
|
[[ -n "$IMAGE" ]] || die "no qcow2 under infra/alpine-host/build/ — run: (cd infra/alpine-host && packer build .)"
|
|
89
99
|
fi
|
|
90
100
|
[[ -f "$IMAGE" ]] || die "image not found: $IMAGE"
|
|
@@ -96,7 +106,7 @@ NAMESPACE="$(oci os ns get --profile "$PROFILE" --query data --raw-output)"
|
|
|
96
106
|
|
|
97
107
|
STAMP="$(date +%Y%m%d-%H%M%S)"
|
|
98
108
|
OBJECT_NAME="${NAME_PREFIX}-${STAMP}.qcow2"
|
|
99
|
-
IMAGE_NAME="${NAME_PREFIX}-$(basename "$IMAGE" .qcow2 | sed 's/^
|
|
109
|
+
IMAGE_NAME="${NAME_PREFIX}-$(basename "$IMAGE" .qcow2 | sed 's/^kampodine-alpine-//')-${STAMP}"
|
|
100
110
|
|
|
101
111
|
say "uploading $(basename "$IMAGE") -> os://$BUCKET/$OBJECT_NAME"
|
|
102
112
|
oci os object put -bn "$BUCKET" --profile "$PROFILE" --file "$IMAGE" --name "$OBJECT_NAME" --force \
|
|
@@ -162,7 +172,7 @@ say " display : $IMAGE_NAME"
|
|
|
162
172
|
say " firmware : $IMPORT_FIRMWARE"
|
|
163
173
|
say "next:"
|
|
164
174
|
if [[ "$IMPORT_FIRMWARE" == "UEFI_64" ]]; then
|
|
165
|
-
say " A1-ready. kampodine bluegreen provision <blue|green> picks this image (newest
|
|
175
|
+
say " A1-ready. kampodine bluegreen provision <blue|green> picks this image (newest kampodine-alpine*)."
|
|
166
176
|
else
|
|
167
177
|
say " A1 launch will REJECT this image (firmware $IMPORT_FIRMWARE) — see the WARN above."
|
|
168
178
|
fi
|