kampodra 0.8.1 → 0.8.3
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kampodra",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "Registry-free deployment CLI for single-VM container stacks over SSH (Podman + kamal-proxy)",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"type": "module",
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
"kampodra": "bin/kampodra.js"
|
|
9
9
|
},
|
|
10
10
|
"optionalDependencies": {
|
|
11
|
-
"@talha7k/darwin-arm64": "0.8.
|
|
12
|
-
"@talha7k/darwin-x64": "0.8.
|
|
13
|
-
"@talha7k/linux-arm64": "0.8.
|
|
14
|
-
"@talha7k/linux-x64": "0.8.
|
|
11
|
+
"@talha7k/darwin-arm64": "0.8.3",
|
|
12
|
+
"@talha7k/darwin-x64": "0.8.3",
|
|
13
|
+
"@talha7k/linux-arm64": "0.8.3",
|
|
14
|
+
"@talha7k/linux-x64": "0.8.3"
|
|
15
15
|
},
|
|
16
16
|
"engines": {
|
|
17
17
|
"node": ">=20"
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kampodra
|
|
3
|
+
description: "Operates the kampodra CLI, a kamal-alternative that deploys containers or binaries to Linux VMs over SSH with podman and kamal-proxy — no registry in the image path, zero-downtime by default. Use when deploying or rolling back a release, running the standby-update bluegreen flow (deploy to the idle color then flip), recovering an interrupted rolling deploy, reading deploy logs/status, pushing or diffing remote env files, generating env from a schema, managing instance profiles, listing or verifying backups, planning a restore, managing DNS records on OCI or GCP, staging golden images, running tenant database migrations, sampling VM metrics, or preparing and wiping VMs. Triggers: deploy, rollback, podman, kamal-proxy, SSH, env, metrics, backup, DNS, gcp, gcloud, oci, bluegreen, flip, migrate, VM, Alpine."
|
|
4
|
+
license: AGPL-3.0
|
|
5
|
+
compatibility: Requires podman, ssh, and git locally; the target VM needs a Linux userland (Alpine or Ubuntu 24.04) with podman and kamal-proxy (vm-prepare installs them). Cloud families authenticate through the provider CLI itself (oci or gcloud).
|
|
6
|
+
metadata:
|
|
7
|
+
author: kampodra maintainers
|
|
8
|
+
version: 0.8.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# kampodra
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
kampodra ships a git repository's app container straight to a Linux VM over SSH: `podman build` locally, `podman save | ssh podman load` to stream the image (no registry in the path), env push, init-aware restart, a served-sha health gate, a kamal-proxy re-point, then a public smoke check. It also owns the full instance lifecycle: rollback, deploy history and pruning, logs and shell, env management, metrics, OCI Object Storage backups, OCI DNS records, tenant database migrations, and VM prepare/wipe.
|
|
16
|
+
|
|
17
|
+
Every host-aware command resolves its target the same way (first match wins):
|
|
18
|
+
|
|
19
|
+
- profile selection: `--profile` flag > `KAMPODRA_PROFILE` env > the config's `defaultProfile`
|
|
20
|
+
- host: `--host` flag > profile `host` > `KAMPODRA_HOST`
|
|
21
|
+
- ssh key: `--ssh-key` flag > profile `sshKey` > `KAMPODRA_SSH_KEY` (empty = ssh agent / `~/.ssh/config`)
|
|
22
|
+
|
|
23
|
+
The project's shape (container name, env-file path, ports, bucket, ...) resolves through the ladder:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
flags > KAMPODRA_* env > profile "project" block > kampodra.json > built-in defaults
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`kampodra config print` renders the fully resolved effective project config with per-field provenance — always the first debugging stop when a value is not what you expect. Full flag-level detail for every command lives in [references/commands.md](references/commands.md).
|
|
30
|
+
|
|
31
|
+
## Prerequisites
|
|
32
|
+
|
|
33
|
+
- Local: Go 1.27+ to build; `git`, `podman`, and `ssh` on `PATH`.
|
|
34
|
+
- Remote: a Linux VM reachable over key-based SSH (kampodra targets Alpine + OpenRC; systemd hosts are detected for restart/stop paths). `vm-prepare` bootstraps a bare Alpine host with the podman stack, OpenRC services, and kamal-proxy.
|
|
35
|
+
- Build once: `go build -o kampodra ./cmd/kampodra` (source build is the supported install path). Verify with `kampodra --version`.
|
|
36
|
+
|
|
37
|
+
## Configure once, deploy forever
|
|
38
|
+
|
|
39
|
+
1. Create a profile (per-instance, stored in `~/.kampodra/config.json`, dir 0700 / file 0600; ssh keys are stored as paths only — never secrets):
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
kampodra config init --name prod --host root@203.0.113.10 --ssh-key ~/.ssh/id_ed25519 --proxy-host app.example.com --set-default
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The first profile auto-becomes the default. Hosts/keys live only in profiles; the committed repo file `kampodra.json` carries project naming only.
|
|
46
|
+
|
|
47
|
+
2. (Recommended) Commit a `kampodra.json` at the repo root naming YOUR app. The built-in defaults are deliberately generic placeholders (`app`, `/etc/kampodra/env`, `app-backups`, …) — set your real naming in the manifest (19 optional camelCase fields: `container`, `shadowSuffix`, `envFile`, `dataDir`, `bucket`, `objectPrefix`, `healthPath`, `healthPaths`, `proxyHost`, `services`, `imagePrefix`, `port`, `network`, `shadowProbePort`, `deployedShaFile`, `envClearKeys`, `dockerfile`, `migrateScript`, plus a `unit.env` map rendering repo-owned -e flags into the app unit; an optional `"$schema"` key points at docs/kampodra.schema.json for editor autocomplete and is ignored). The parse is strict: malformed JSON or an unknown key FAILS CLOSED — a typo errors (naming the valid keys) instead of silently resolving to defaults. See the field table in [references/commands.md](references/commands.md).
|
|
48
|
+
|
|
49
|
+
3. Push env (values are never printed; only KEY + fingerprint):
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
kampodra env push --file ./ops/env.production # or generate locally: kampodra env from-schema
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
4. First bootstrap on a fresh VM: `kampodra vm-prepare --host root@203.0.113.10` (idempotent; the app container is intentionally NOT started — the first deploy provides image and env).
|
|
56
|
+
|
|
57
|
+
## First deploy and verification
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
kampodra deploy # build HEAD (clean-tree gate) and run the full pipeline
|
|
61
|
+
kampodra status # live health through the proxy + deployment count + VM state
|
|
62
|
+
kampodra deploy logs --lines 100 # tail the app container (--since 2h catches boot-first lines)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Deploy stamps the git sha. In deploy mode the clean tree is gated, so what HEAD names is what ships. `kampodra deploy list` shows history (VM sha-tagged images merged with the local ledger `~/.kampodra/deployments.jsonl`); `kampodra metrics` snapshots load/memory/disk/containers; `deploy prune` reclaims disk from old sha-tagged images (always preview with `--dry-run`).
|
|
66
|
+
|
|
67
|
+
## Rollback: a flag, not a subcommand
|
|
68
|
+
|
|
69
|
+
There is NO `rollback` subcommand. Rollback is the flag `deploy --rollback [<sha7>]`:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
kampodra deploy --rollback ccc3333 # explicit target
|
|
73
|
+
kampodra deploy --rollback # resolves from the VM's deployed-sha stamp
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Target resolution: the explicit sha, else the VM's deployed-sha stamp file, else it DIES — it never falls back to git HEAD (rolling back must not redeploy the very build you are rolling back from). Rollback skips the build/stream when the tag still exists on the VM and never rewrites the stamp.
|
|
77
|
+
|
|
78
|
+
## Zero-downtime deploys and recovery
|
|
79
|
+
|
|
80
|
+
`kampodra deploy --rolling` boots the new version as a shadow container, health-gates it BEFORE any traffic can reach it, then double re-points kamal-proxy (to the shadow, then back to the new main). Failure paths keep the safer state — but if an interrupted run exits loudly mid-switch, the shadow is left SERVING on purpose. Finish it with:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
kampodra deploy converge [<sha7>] # retag → restart → health gate → re-point to main → remove shadow
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
A leftover shadow must be converged before the next `--rolling` deploy. `--drain-timeout` (default 10s, rolling only) bounds the proxy-switch confirmation.
|
|
87
|
+
|
|
88
|
+
## Safety: destructive and guarded operations
|
|
89
|
+
|
|
90
|
+
| Operation | Behavior | Guard |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `vm-wipe` | Stops/disables services, removes project containers, prunes images, deletes env/stamp/state | `--yes` REQUIRED; `--keep-data` spares the data dir; `--force` overrides a services-mismatch refusal (wrong host?) |
|
|
93
|
+
| `backup restore-plan <object>` | PRINTS the documented recovery sequence | Never executes anything |
|
|
94
|
+
| `backup verify <file>` | Read-only: uploads to VM scratch files and runs the VM's `restore-verify` | Never writes into the data dir |
|
|
95
|
+
| `deploy prune` | Removes old sha-tagged images (keeps running + ts-rollback + newest N) | `--dry-run` prints exact commands first |
|
|
96
|
+
| `migrate` | Writes db schema | Stop-first guard: refuses while the api service runs unless `--allow-running` |
|
|
97
|
+
| `env push` | Overwrites the remote env file atomically | Preview first with `env diff <local-file>` (exit 1 = differs) |
|
|
98
|
+
| `vm-prepare` | Mutates a bare host (packages, sshd, services) | Idempotent; safe to re-run |
|
|
99
|
+
|
|
100
|
+
## Gotchas
|
|
101
|
+
|
|
102
|
+
- Defaults are GENERIC placeholders (`app`, `/etc/kampodra/env`, `app-backups`, `/up`), not your app's names — commit a `kampodra.json` before the first deploy against a new tree, or check `config print`.
|
|
103
|
+
- `migrate` runs the app's own script on the VM: the path comes from the `migrateScript` config (default `scripts/migrate-db.ts`), executed with `pnpm exec tsx` inside `--repo-root` (or `KAMPODRA_REPO_ROOT`). Point `migrateScript` at your repo's real layout.
|
|
104
|
+
- `dns`/`backup`/`bluegreen` are multi-provider (oci | gcp behind the neutral dispatch; one `RunCloud` seam): they authenticate through the provider CLI's own auth, and kampodra passes no auth flags unless the instance profile's `cloud` block overrides it (oci: `profile`/`compartment`/`instancePrincipal`; gcp: `provider: gcp` + `project`/`account`); `OCI_PROFILE`/`OCI_COMPARTMENT` env work as the oci escape hatch, else the provider CLI resolves natively; gcp scopes natively via the block's `project` (dns also takes `dnsCompartment` + `--dns-compartment` for split-compartment zones). `image-import` is OCI-only by design (a `gcp` profile fails closed — Cloud Build trust-shape). `bluegreen provision` pins `VM.Standard.A1.Flex` on oci, `e2-standard-2` on gcp. `--profile` always means the kampodra INSTANCE profile.
|
|
105
|
+
- Fleet profiles: `--group web` filters `config list` / `status` / `deploy list`; `config clone prod prod-2 --host root@<ip>` stamps out a sibling with explicit copies (no live link).
|
|
106
|
+
- `bluegreen` manages the reserved-IP pair (`status | init | provision <color> | deploy --to <color> | flip --to <color> [--force] | rollback`) — pairs + reserved IP derive from LIVE provider state, no manual-IP fallback. `deploy --to <color>` is the standby-update composite: build once → push to the idle color → health-gate it there → flip (pipeline failure = no flip). A flip warns when the CURRENT holder is unhealthy (no healthy fallback after cutover). `image-import --image <qcow2>` stages+imports golden images on oci (warns on non-UEFI firmware). See [references/commands.md](references/commands.md).
|
|
107
|
+
- `deploy` health-gates and smokes EVERY path in the manifest's `healthPaths[]` (default: exactly [`healthPath`]) — pin the routes that must answer, not just the shallow one. Binary fast-pushes are sha256-fingerprinted and byte-verified on the VM before the swap; a stale rebuild (bytes identical to the previous push under a new sha) warns loudly.
|
|
108
|
+
- No command ever prints env values — only KEY + fingerprint tables (value length + first 2 chars). Raw values move only through `env push`'s upload and `env pull`'s stdout/`--out` payload.
|
|
109
|
+
|
|
110
|
+
## Common flows
|
|
111
|
+
|
|
112
|
+
- Bad release, revert now: `kampodra deploy --rollback` (stamp-resolved), confirm with `kampodra status`.
|
|
113
|
+
- Disk pressure: `kampodra metrics --disk-threshold 90` → `kampodra deploy prune --dry-run` → apply.
|
|
114
|
+
- Env drift suspected: `kampodra env diff ./ops/env.production` → `kampodra env push --file ./ops/env.production` → `kampodra deploy restart`.
|
|
115
|
+
- One-off inspection on the VM: `kampodra ssh df -h` (argv passes through verbatim), or inside the container `kampodra deploy shell exec -- df -h`.
|
|
116
|
+
- Schema change: stop-first `kampodra migrate --repo-root /srv/app` (failures are collected per tenant db; exit 1 means do NOT restart the API on a half-migrated estate).
|
|
117
|
+
|
|
118
|
+
For every command's full flags, defaults, and exit codes, read [references/commands.md](references/commands.md).
|
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
# kampodra command reference
|
|
2
|
+
|
|
3
|
+
Per-command deep reference for AI agents operating the `kampodra` CLI. Every
|
|
4
|
+
flag name, type, and default below is taken from the Cobra definitions in the
|
|
5
|
+
CLI source (`internal/command/*.go`). When this file and `kampodra <command>
|
|
6
|
+
--help` disagree, re-run `--help` and trust the binary's live output.
|
|
7
|
+
|
|
8
|
+
**Contents**
|
|
9
|
+
|
|
10
|
+
- [Global resolution rules](#global-resolution-rules)
|
|
11
|
+
- [Environment variables](#environment-variables)
|
|
12
|
+
- [kampodra.json (project manifest)](#kampodrajson-project-manifest)
|
|
13
|
+
- [deploy](#deploy) (+ [converge](#deploy-converge-sha7) / [list](#deploy-list) / [prune](#deploy-prune) / [logs](#deploy-logs) / [restart](#deploy-restart) / [shell](#deploy-shell))
|
|
14
|
+
- [env](#env) (list / push / pull / fingerprint / diff / from-schema)
|
|
15
|
+
- [ssh](#ssh)
|
|
16
|
+
- [config](#config) (init / list / show / print / set-default / remove)
|
|
17
|
+
- [backup](#backup) (list / download / verify / restore-plan)
|
|
18
|
+
- [vm-prepare](#vm-prepare)
|
|
19
|
+
- [vm-wipe](#vm-wipe)
|
|
20
|
+
- [metrics](#metrics)
|
|
21
|
+
- [dns](#dns) (records / add / rm)
|
|
22
|
+
- [migrate](#migrate)
|
|
23
|
+
- [status](#status)
|
|
24
|
+
- [Exit codes](#exit-codes)
|
|
25
|
+
- [Agent gotchas](#agent-gotchas)
|
|
26
|
+
|
|
27
|
+
## Global resolution rules
|
|
28
|
+
|
|
29
|
+
Every host-aware command resolves its target the same way (first match wins):
|
|
30
|
+
|
|
31
|
+
- profile: `--profile` flag > `KAMPODRA_PROFILE` env > the config's `defaultProfile`
|
|
32
|
+
- host: `--host` flag > profile `host` > `KAMPODRA_HOST`
|
|
33
|
+
- ssh key: `--ssh-key` flag > profile `sshKey` > `KAMPODRA_SSH_KEY` (empty = ssh agent / `~/.ssh/config`)
|
|
34
|
+
- proxy host: profile `project.proxyHost` block > `KAMPODRA_PROXY_HOST` > project default; the profile's legacy flat `proxyHost` field still wins when set
|
|
35
|
+
|
|
36
|
+
The project's shape (container name, env-file path, ports, bucket, ...) resolves
|
|
37
|
+
through the ladder:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
flags > KAMPODRA_* env > profile "project" block > kampodra.json > built-in defaults
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
An unknown profile name fails closed naming the known set. Profiles live in
|
|
44
|
+
`~/.kampodra/config.json` (dir 0700 / file 0600); ssh keys are stored as paths
|
|
45
|
+
only — the config never contains secret material. `kampodra config print`
|
|
46
|
+
renders the fully resolved effective config with per-field provenance; make it
|
|
47
|
+
the first debugging stop. Every command supports `--help` (usage + examples).
|
|
48
|
+
## Environment variables
|
|
49
|
+
|
|
50
|
+
| Variable | Effect |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `KAMPODRA_HOST` | Fallback host (`user@ip` or ssh-config alias) when no flag/profile provides one |
|
|
53
|
+
| `KAMPODRA_SSH_KEY` | Fallback identity file path |
|
|
54
|
+
| `KAMPODRA_PROFILE` | Fallback profile name (beaten by `--profile`, beats `defaultProfile`) |
|
|
55
|
+
| `KAMPODRA_BUCKET` | The project ladder's canonical bucket override — beats the project config's `bucket` (only `--bucket` beats it) |
|
|
56
|
+
| `KAMPODRA_MIGRATE_SCRIPT` | Repo-relative migrate script path override for `migrate` |
|
|
57
|
+
| `KAMPODRA_REPO_ROOT` | Fallback VM repo checkout for `migrate` (`--repo-root` beats it) |
|
|
58
|
+
| `KAMPODRA_CLOUD_PROFILE` | OCI config profile override when the profile's `cloud` block sets none (else native resolution) |
|
|
59
|
+
| `KAMPODRA_CLOUD_COMPARTMENT` | REQUIRED by the oci legs of `dns`/`bluegreen`/`image-import` — no default; name or `ocid1.` ocid |
|
|
60
|
+
| `KAMPODRA_CLOUD_NAMESPACE` | OCI tenancy object-storage namespace override (`--namespace` flag > cloud block / this env > `oci os ns get`; ignored for gcp) |
|
|
61
|
+
| `OCI_PROFILE` **(deprecated)** | legacy alias of `KAMPODRA_CLOUD_PROFILE` — still read this release (one stderr notice per process); removed in a future release |
|
|
62
|
+
| `OCI_COMPARTMENT` **(deprecated)** | legacy alias of `KAMPODRA_CLOUD_COMPARTMENT` — same ladder, same notice; the new name wins when both are set |
|
|
63
|
+
| `OCI_NAMESPACE` **(deprecated)** | legacy alias of `KAMPODRA_CLOUD_NAMESPACE` — same ladder, same notice; the new name wins when both are set |
|
|
64
|
+
| `MIGRATE_JOBS` | Fallback parallelism for `migrate` (else 4; accepted range 1–64) |
|
|
65
|
+
|
|
66
|
+
Each `kampodra.json` field also has a `KAMPODRA_*` env override in the ladder:
|
|
67
|
+
`KAMPODRA_CONTAINER`, `KAMPODRA_SHADOW_SUFFIX`, `KAMPODRA_ENV_FILE`,
|
|
68
|
+
`KAMPODRA_DATA_DIR`, `KAMPODRA_BUCKET`, `KAMPODRA_OBJECT_PREFIX`,
|
|
69
|
+
`KAMPODRA_HEALTH_PATH`, `KAMPODRA_PROXY_HOST`, `KAMPODRA_SERVICES`,
|
|
70
|
+
`KAMPODRA_IMAGE_PREFIX`, `KAMPODRA_PORT`, `KAMPODRA_NETWORK`,
|
|
71
|
+
`KAMPODRA_SHADOW_PROBE_PORT`, `KAMPODRA_DEPLOYED_SHA_FILE`,
|
|
72
|
+
`KAMPODRA_ENV_CLEAR_KEYS`, `KAMPODRA_DOCKERFILE`.
|
|
73
|
+
|
|
74
|
+
## kampodra.json (project manifest)
|
|
75
|
+
|
|
76
|
+
Repo-level manifest, committed per-project; discovered upward from the working
|
|
77
|
+
directory (nearest file wins). Carries project NAMING only — never hosts, ssh
|
|
78
|
+
keys, or secrets. All 19 fields optional, camelCase (plus the `unit` block), an optional `"$schema"`
|
|
79
|
+
key pointing at docs/kampodra.schema.json is accepted (ignored) for editor
|
|
80
|
+
autocomplete:
|
|
81
|
+
|
|
82
|
+
| Field | Type | Purpose |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `container` | string | api container / service name |
|
|
85
|
+
| `shadowSuffix` | string | rolling-deploy shadow container suffix |
|
|
86
|
+
| `envFile` | string | remote env file path (profile blocks also accept the legacy `envFilePath` spelling; the manifest takes `envFile` only) |
|
|
87
|
+
| `dataDir` | string | tenant/app data dir on the VM |
|
|
88
|
+
| `bucket` | string | object-storage backup bucket |
|
|
89
|
+
| `objectPrefix` | string | backup object prefix |
|
|
90
|
+
| `healthPath` | string | app health endpoint behind the proxy |
|
|
91
|
+
| `proxyHost` | string | public TLS edge hostname |
|
|
92
|
+
| `services` | string[] | service roll call |
|
|
93
|
+
| `imagePrefix` | string | VM-local image reference prefix for deploy images |
|
|
94
|
+
| `port` | string | container's published/listening port |
|
|
95
|
+
| `network` | string | podman network shared with kamal-proxy |
|
|
96
|
+
| `shadowProbePort` | string | shadow container's loopback-only probe port |
|
|
97
|
+
| `deployedShaFile` | string | VM deployed-sha stamp (rollback's fallback) |
|
|
98
|
+
| `envClearKeys` | string[] | env keys owned by init — `env from-schema` drops them from varlock output |
|
|
99
|
+
| `dockerfile` | string | Containerfile/Dockerfile deploy builds |
|
|
100
|
+
| `migrateScript` | string | repo-relative db migrate script (`migrate` runs it on the VM) |
|
|
101
|
+
| `healthPaths` | string[] | EXTRA health-gate/smoke routes — the deploy probes EVERY path (default: exactly [`healthPath`]) |
|
|
102
|
+
| `unit` | object | repo-owned app-unit surface: `unit.env` renders each key as `-e K=V` on the app container's `podman run` (later-wins over renderer clear-env defaults; secrets stay in the env file) |
|
|
103
|
+
|
|
104
|
+
The parse is **strict fail-closed**: malformed JSON or an unknown key errors
|
|
105
|
+
instead of silently resolving to defaults. An empty object is valid. Built-in
|
|
106
|
+
defaults describe one sample app's shape, not neutral conventions — check
|
|
107
|
+
`config print` before the first run against a new tree.
|
|
108
|
+
|
|
109
|
+
## deploy
|
|
110
|
+
|
|
111
|
+
Full pipeline: clean-tree gate → `podman build --build-arg GIT_SHA=<sha>` (sha
|
|
112
|
+
tag from `git rev-parse --short HEAD`) → `podman save | ssh podman load`
|
|
113
|
+
(registry-free stream) → env push (`--env-file`; 0600 + atomic mv, `API_GIT_SHA`
|
|
114
|
+
stamped) → init restart → VM-side served-sha health gate → kamal-proxy re-point
|
|
115
|
+
→ public smoke → ledger append → disk report → keep-set image cleanup (running
|
|
116
|
+
+ `ts-rollback` + newest 3 kept).
|
|
117
|
+
|
|
118
|
+
| Flag | Type | Default |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `--host` | string | `""` (resolution ladder) |
|
|
121
|
+
| `--profile` | string | `""` (resolution ladder) |
|
|
122
|
+
| `--ssh-key` | string | `""` (agent / ssh config) |
|
|
123
|
+
| `--dockerfile` | string | `""` → project config's `dockerfile` (else `Dockerfile`) |
|
|
124
|
+
| `--sha <sha7>` | string | `""`; sha fragment required, never inferred |
|
|
125
|
+
| `--rollback [<sha7>]` | string | bare `--rollback` = stamp-resolved |
|
|
126
|
+
| `--rolling` | bool | `false` |
|
|
127
|
+
| `--drain-timeout` | int | `10` (seconds; requires `--rolling`) |
|
|
128
|
+
| `--disk-threshold` | string | `""` (percentage 0–100) |
|
|
129
|
+
| `--env-file` | string | `""` |
|
|
130
|
+
| `--skip-smoke` | bool | `false` |
|
|
131
|
+
| `--refresh-config` | bool | `false` — compatibility no-op (a warning prints) |
|
|
132
|
+
|
|
133
|
+
- `--sha` streams an EXISTING local build: no rebuild, clean-tree gate
|
|
134
|
+
skipped. `--rollback` and `--sha` are exclusive; `--rolling` and
|
|
135
|
+
`--rollback` are exclusive.
|
|
136
|
+
- `--disk-threshold` fails closed BEFORE the build when VM disk usage >= pct; an
|
|
137
|
+
unreadable df with the flag set is also an error. Disk > 90% always warns
|
|
138
|
+
loudly, flag or not.
|
|
139
|
+
- Deploy mode requires a git repo (the sha is stamped). Rollback target
|
|
140
|
+
resolution: explicit sha > the VM's deployed-sha stamp file > DIE — never git
|
|
141
|
+
HEAD. Rollback skips build/stream when the tag still exists on the VM, streams
|
|
142
|
+
from this machine when it does not, and never rewrites the stamp. A positional
|
|
143
|
+
in rollback mode is the sha (given twice = error); any other positional is
|
|
144
|
+
rejected.
|
|
145
|
+
|
|
146
|
+
Example: `kampodra deploy --host root@203.0.113.10 --rolling --env-file ./ops/env.production`
|
|
147
|
+
|
|
148
|
+
### deploy converge [<sha7>]
|
|
149
|
+
|
|
150
|
+
Finishes an interrupted `--rolling` deploy: retag → restart → health gate →
|
|
151
|
+
re-point to main → remove the shadow. Flags: `--host`, `--profile`,
|
|
152
|
+
`--ssh-key`, `--drain-timeout` (int, default 10). The sha comes from the
|
|
153
|
+
argument (must be a sha fragment) or the shadow's own image — never HEAD. A
|
|
154
|
+
leftover shadow must be converged before the next `--rolling`.
|
|
155
|
+
Example: `kampodra deploy converge --host root@203.0.113.10`
|
|
156
|
+
|
|
157
|
+
### deploy list
|
|
158
|
+
|
|
159
|
+
Deployment history: the VM's sha-tagged images (running one marked) merged with
|
|
160
|
+
the local ledger `~/.kampodra/deployments.jsonl`, plus a `Total deployments`
|
|
161
|
+
footer. Flags: `--host`, `--profile`, `--ssh-key`, `--all-profiles` (bool,
|
|
162
|
+
default false — one section per configured profile, sorted; errors when no
|
|
163
|
+
profiles exist), `--group <g>` (one section per profile in that group).
|
|
164
|
+
Same-tag rows merge to ONE row showing the NEWEST deploy timestamp. An
|
|
165
|
+
unreachable VM degrades to a ledger-only view with a warning, not an error.
|
|
166
|
+
Example: `kampodra deploy list --profile prod --all-profiles`
|
|
167
|
+
|
|
168
|
+
### deploy prune
|
|
169
|
+
|
|
170
|
+
Reclaims VM disk by removing OLD sha-tagged deploy images. ALWAYS kept: the
|
|
171
|
+
currently-running image, `ts-rollback`, and the newest `--keep` N.
|
|
172
|
+
Flags: `--host`, `--profile`, `--ssh-key`, `--keep` (string, default `"2"`,
|
|
173
|
+
non-negative integer), `--dry-run` (bool, default false — prints the exact
|
|
174
|
+
`podman rmi` commands and removes nothing). Refuses to prune if the image list
|
|
175
|
+
fails over ssh. After applying, reports disk on `/var/lib/containers` and warns
|
|
176
|
+
above 90%.
|
|
177
|
+
Example: `kampodra deploy prune --host root@203.0.113.10 --dry-run`
|
|
178
|
+
|
|
179
|
+
### deploy logs
|
|
180
|
+
|
|
181
|
+
Tails the running api container's logs. Flags: `--host`, `--profile`,
|
|
182
|
+
`--ssh-key`, `--lines` (string, default `"100"`, positive integer),
|
|
183
|
+
`--follow` (bool, default false — streams until ctrl-c with a clean exit; a
|
|
184
|
+
non-zero remote exit code propagates), `--since` (Go duration like `2h` or
|
|
185
|
+
RFC3339 — passed verbatim to `podman logs`; catches boot-first lines a bare
|
|
186
|
+
`--tail` cuts off).
|
|
187
|
+
Example: `kampodra deploy logs --host root@203.0.113.10 --lines 200 --since 30m --follow`
|
|
188
|
+
|
|
189
|
+
### deploy restart
|
|
190
|
+
|
|
191
|
+
Restarts the api service, init-aware: `rc-service` on Alpine (supervise-daemon
|
|
192
|
+
re-runs `podman run` on `:latest`), `systemctl` on systemd hosts, then a
|
|
193
|
+
best-effort status check. Flags: `--host`, `--profile`, `--ssh-key`.
|
|
194
|
+
Example: `kampodra deploy restart --profile prod`
|
|
195
|
+
|
|
196
|
+
### deploy shell
|
|
197
|
+
|
|
198
|
+
Interactive `sh` inside the running api container (forced tty), or a one-shot
|
|
199
|
+
via `exec -- <cmd>...`. The `--` separator is REQUIRED for one-shot mode (pflag
|
|
200
|
+
consumes it; the command vector is located via ArgsLenAtDash). Arguments are
|
|
201
|
+
POSIX sh-quoted before composition. Flags: `--host`, `--profile`, `--ssh-key`.
|
|
202
|
+
The container's exit code propagates.
|
|
203
|
+
Example: `kampodra deploy shell --host root@203.0.113.10 exec -- df -h`
|
|
204
|
+
|
|
205
|
+
## env
|
|
206
|
+
|
|
207
|
+
The remote app env file (project config's `envFile` path, 0600). Values are
|
|
208
|
+
NEVER printed — every display is a KEY + fingerprint table (value length +
|
|
209
|
+
first 2 chars). Raw values move only through `push`'s upload and `pull`'s
|
|
210
|
+
stdout/`--out` payload. Subcommand flags: `--host`, `--profile`, `--ssh-key`
|
|
211
|
+
(standard ladder) except where noted.
|
|
212
|
+
|
|
213
|
+
- `env list` — KEY + fingerprint table of the remote file.
|
|
214
|
+
- `env push --file <f>` — `--file` string, required; refuses files with no
|
|
215
|
+
`KEY=VALUE` lines; uploads verbatim: remote 0600 from creation (umask 077) +
|
|
216
|
+
atomic `mv`; prints the fingerprint table and a restart hint (does NOT
|
|
217
|
+
restart itself).
|
|
218
|
+
- `env pull [--out <file>]` — `--out` string, default `""`. Without it the raw
|
|
219
|
+
payload goes to stdout and the masked summary to stderr; with it the file is
|
|
220
|
+
written 0600 and the summary goes to stdout.
|
|
221
|
+
- `env fingerprint [--file <f>]` — `--file` string, default stdin. Previews the
|
|
222
|
+
masking for a LOCAL file; no host needed.
|
|
223
|
+
- `env diff <local-file>` — exactly one positional arg. Fingerprint-level diff
|
|
224
|
+
of local vs remote; **exit 1 = differs** (GNU diff convention).
|
|
225
|
+
- `env from-schema [--repo <path>] [--schema <path>] [--out <file>]` —
|
|
226
|
+
kampodra-native. Generates the env file from the repo's committed
|
|
227
|
+
`.env.schema` via varlock (`node_modules/.bin/varlock` must exist in the
|
|
228
|
+
repo; `pass()` refs resolve through varlock + the pass store, values never
|
|
229
|
+
echo); `envClearKeys`-owned keys are dropped. Defaults: `--repo` `"."`
|
|
230
|
+
(resolved via `git rev-parse --show-toplevel`; no git repo = error),
|
|
231
|
+
`--schema` `"apps/api/.env.schema"`, `--out` `""` (= push through the push
|
|
232
|
+
flow instead of writing locally, 0600).
|
|
233
|
+
|
|
234
|
+
Example: `kampodra env diff ./ops/env.production --host root@203.0.113.10`
|
|
235
|
+
|
|
236
|
+
## ssh
|
|
237
|
+
|
|
238
|
+
Host-level ssh passthrough (kampodra-native). Flags: `--host`, `--profile`,
|
|
239
|
+
`--ssh-key`. Flag parsing is NOT interspersed: everything after the first
|
|
240
|
+
non-flag argument belongs to the remote command verbatim. No command =
|
|
241
|
+
interactive login (forced tty). ssh's exit code propagates.
|
|
242
|
+
Example: `kampodra ssh --host root@203.0.113.10 df -h`
|
|
243
|
+
|
|
244
|
+
## config
|
|
245
|
+
|
|
246
|
+
Per-instance profiles in `~/.kampodra/config.json` (dir 0700 / file 0600).
|
|
247
|
+
FLAT — no inheritance; `group` is a cosmetic list filter. Persistent flags:
|
|
248
|
+
`--name` (string), `--host` (string), `--ssh-key` (string, path only, tilde
|
|
249
|
+
expanded), `--proxy-host` (string), `--group` (string), `--profile` (string),
|
|
250
|
+
`--set-default` (bool, false), `--force` (bool, false).
|
|
251
|
+
|
|
252
|
+
- `config init` — create or `--force`-refresh a profile. `--name` required:
|
|
253
|
+
`[A-Za-z0-9][A-Za-z0-9_-]*`, max 64. The first profile auto-becomes the
|
|
254
|
+
default. Re-init refreshes flat fields, keeps the tooling-owned cached init
|
|
255
|
+
verdict unless host/ssh-key changed, and always preserves the profile's
|
|
256
|
+
`project` block (config init never manages it).
|
|
257
|
+
- `config list [--group <g>]` — all profiles, default marked `*`.
|
|
258
|
+
- `config show [--profile <name>]` — one profile's fields (else the default).
|
|
259
|
+
- `config print [--profile <name>]` — the fully resolved effective project
|
|
260
|
+
config with per-field provenance (flag/env/profile/repo-file/default). The
|
|
261
|
+
debugging tool for the resolution ladder.
|
|
262
|
+
- `config set-default <name>` — repoint `defaultProfile` (max 1 positional).
|
|
263
|
+
- `config remove <name> [--force]` — removing the LAST profile requires
|
|
264
|
+
`--force`; if the default was removed it reassigns sorted-first.
|
|
265
|
+
|
|
266
|
+
Example: `kampodra config init --name prod --host root@203.0.113.10 --ssh-key ~/.ssh/id_ed25519 --proxy-host app.example.com --set-default`
|
|
267
|
+
|
|
268
|
+
## backup
|
|
269
|
+
|
|
270
|
+
OCI Object Storage backups via the `oci` CLI's own auth — kampodra passes
|
|
271
|
+
no auth flags unless the profile's `cloud` block overrides it, else the
|
|
272
|
+
oci CLI resolves natively; kampodra never accepts, stores, or logs
|
|
273
|
+
credential material. Persistent flags:
|
|
274
|
+
|
|
275
|
+
| Flag | Type | Default | Meaning |
|
|
276
|
+
|---|---|---|---|
|
|
277
|
+
| `--bucket` | string | `""` | bucket; ladder: `--bucket` > `KAMPODRA_BUCKET` > project config `bucket` |
|
|
278
|
+
| `--prefix` | string | `""` | object name prefix for `list` |
|
|
279
|
+
| `--out` | string | `""` | `download` destination (else the object's basename) |
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
| `--host` | string | `""` | VM target for `verify` |
|
|
283
|
+
| `--ssh-key` | string | `""` | identity file for `verify` |
|
|
284
|
+
| `--migrated-topology` | bool | `false` | `verify`: pass restore-verify's drill flag through |
|
|
285
|
+
|
|
286
|
+
- `backup list [--prefix <p>]` — objects in the bucket. LIST-denied by policy:
|
|
287
|
+
prints the exact policy shape needed on stderr, **exit 1** (GET works without
|
|
288
|
+
LIST once the object name is known).
|
|
289
|
+
- `backup download <object> [--out <file>]` — pre-creates the file 0600, fetches
|
|
290
|
+
via `oci get`, then verifies sha256/md5 against the object's digest metadata;
|
|
291
|
+
mismatch prints FAIL and **exits 1**; no metadata = integrity noted as skipped.
|
|
292
|
+
- `backup verify <local.db|.tgz>` — read-only drill. Accepts `.db`/`.sqlite`/`.sqlite3`
|
|
293
|
+
or `.tgz`/`.tar.gz`. Needs a VM target (`--host` / the legacy instance-profile
|
|
294
|
+
flag above / ladder). Each db is uploaded to a VM scratch file
|
|
295
|
+
`/tmp/kampodra-verify-<n>-<crypto-random-hex>.db` (created under umask 077,
|
|
296
|
+
ALWAYS removed — cleanup never masks the verdict), then the VM's
|
|
297
|
+
`/usr/local/bin/restore-verify` runs as `restore-verify -db <path>`.
|
|
298
|
+
`--migrated-topology` appends restore-verify's `--migrated-topology` drill
|
|
299
|
+
flag (bulk-migration chain shape downgraded to warnings). For a `.tgz`, the
|
|
300
|
+
archive is extracted to a local temp dir (path-traversal-refusing) and every
|
|
301
|
+
`.db`/`.sqlite` member is verified sorted; `root/root.db` members are
|
|
302
|
+
SKIPPED — the root db is schema-only, not in restore-verify scope. Never
|
|
303
|
+
writes into the data dir. Any member failure fails the command.
|
|
304
|
+
- `backup restore-plan <object> [--bucket <name>]` — PRINTS the documented
|
|
305
|
+
recovery sequence (stage → scp to scratch → verify → stop api → snapshot →
|
|
306
|
+
swap in → start + smoke). Never executes anything: no oci call, no ssh, no
|
|
307
|
+
mutation.
|
|
308
|
+
|
|
309
|
+
Example: `kampodra backup verify /tmp/restore.db --host root@203.0.113.10`
|
|
310
|
+
|
|
311
|
+
## vm-prepare
|
|
312
|
+
|
|
313
|
+
First-run bootstrap of a bare Alpine VM; idempotent, safe to re-run. Sequence:
|
|
314
|
+
sanity gates (UEFI boot, no systemd, OpenRC tooling) → sshd hardening (drop-in +
|
|
315
|
+
Include + restart, then gated) → apk community repo + podman stack (netavark) →
|
|
316
|
+
project state dir + `/etc/containers/registries.conf` → sysctl
|
|
317
|
+
`net.ipv4.ip_unprivileged_port_start=80` → OpenRC services (api container +
|
|
318
|
+
kamal-proxy via supervise-daemon, rc-update'd into the default runlevel) →
|
|
319
|
+
`kampodra-anchor` watcher (inert without its conf) → kamal-proxy image pulled +
|
|
320
|
+
service UP. The app container is intentionally NOT started on a fresh VM — the
|
|
321
|
+
first deploy provides image and env (a converge runs only when a previous
|
|
322
|
+
deploy left both). The first TLS certificate issues during the first deploy.
|
|
323
|
+
Readiness loops: ssh 60 × 5s, proxy container 20 × 3s. Accepts unknown host
|
|
324
|
+
keys (first contact).
|
|
325
|
+
|
|
326
|
+
| Flag | Type | Default |
|
|
327
|
+
|---|---|---|
|
|
328
|
+
| `--host` | string | `""` (required in practice for a new VM) |
|
|
329
|
+
| `--profile` | string | `""` |
|
|
330
|
+
| `--ssh-key` | string | `""` |
|
|
331
|
+
|
|
332
|
+
| `--ansible <playbook>` | string | `""` — run this ansible-playbook against the host AFTER bootstrap (temp inventory file, `--private-key` from the resolved key; failure is fatal) |
|
|
333
|
+
|
|
334
|
+
Example: `kampodra vm-prepare --host root@203.0.113.10`
|
|
335
|
+
|
|
336
|
+
## vm-wipe
|
|
337
|
+
|
|
338
|
+
kampodra-native teardown of the project stack on the target VM: stop + disable
|
|
339
|
+
every project service, remove the project containers (app, its rolling-deploy
|
|
340
|
+
shadow, kamal-proxy), `podman image prune -a -f`, delete the env file,
|
|
341
|
+
deployed-sha stamp, anchor conf, and state dir; the tenant data dir goes LAST.
|
|
342
|
+
DESTRUCTIVE — **`--yes` is REQUIRED**; without it nothing runs.
|
|
343
|
+
|
|
344
|
+
| Flag | Type | Default |
|
|
345
|
+
|---|---|---|
|
|
346
|
+
| `--yes` | bool | `false` — confirm the teardown (required) |
|
|
347
|
+
| `--keep-data` | bool | `false` — preserve the tenant data dir |
|
|
348
|
+
| `--force` | bool | `false` — override the services-mismatch refusal |
|
|
349
|
+
| `--host` | string | `""` |
|
|
350
|
+
| `--profile` | string | `""` |
|
|
351
|
+
| `--ssh-key` | string | `""` |
|
|
352
|
+
|
|
353
|
+
Match check: if NO project container is present AND no project service is
|
|
354
|
+
running, it refuses (`--force` to mean it — wrong host?). Individual step
|
|
355
|
+
failures are tolerated and reported, not fatal.
|
|
356
|
+
Example: `kampodra vm-wipe --profile prod --yes --keep-data`
|
|
357
|
+
|
|
358
|
+
## metrics
|
|
359
|
+
|
|
360
|
+
One-shot VM snapshot over SSH (load, memory, disk breakdown, containers, top
|
|
361
|
+
procs) — one round-trip per snapshot, busybox-safe remote commands. NOT
|
|
362
|
+
continuous monitoring.
|
|
363
|
+
|
|
364
|
+
| Flag | Type | Default |
|
|
365
|
+
|---|---|---|
|
|
366
|
+
| `--host` | string | `""` |
|
|
367
|
+
| `--profile` | string | `""` |
|
|
368
|
+
| `--ssh-key` | string | `""` |
|
|
369
|
+
| `--disk-threshold` | string | `"90"` (1–100) — **exit 1** when root disk usage >= this threshold |
|
|
370
|
+
| `--watch` | string | `""` — re-snapshot every N seconds (positive int; ctrl-c ends, exit 0) |
|
|
371
|
+
| `--count` | string | `""` — bound the snapshot count (positive int) |
|
|
372
|
+
|
|
373
|
+
Above 90% root disk always warns (exit 0); only the `--disk-threshold` threshold
|
|
374
|
+
fails. Requires a resolved target.
|
|
375
|
+
Example: `kampodra metrics --host root@203.0.113.10 --disk-threshold 85`
|
|
376
|
+
|
|
377
|
+
## dns
|
|
378
|
+
|
|
379
|
+
DNS records via the provider CLI's own auth ONLY (oci | gcp) — no auth
|
|
380
|
+
flags unless the profile's `cloud` block overrides (oci:
|
|
381
|
+
`profile`/`compartment`/`instancePrincipal`; gcp: `provider: "gcp"` +
|
|
382
|
+
`project`/`account`, else gcloud's active config); never credential
|
|
383
|
+
material. An OCI compartment is REQUIRED for oci only (block or
|
|
384
|
+
`KAMPODRA_CLOUD_COMPARTMENT` env — deprecated: `OCI_COMPARTMENT`), no default (a plain name resolves through
|
|
385
|
+
`iam compartment list`; an `ocid1.`
|
|
386
|
+
value is used as-is); gcp scopes via the block's `project`. Zone
|
|
387
|
+
resolution: `--zone` (ocid → name fetched; name →
|
|
388
|
+
as-is, trailing dot trimmed) else the compartment must hold EXACTLY one zone —
|
|
389
|
+
several → fail listing them (**exit 1**), zero → error. Record types are
|
|
390
|
+
pinned to `A | AAAA | CNAME`; all validation runs locally before any
|
|
391
|
+
provider call.
|
|
392
|
+
|
|
393
|
+
| Flag (persistent) | Type | Default |
|
|
394
|
+
|---|---|---|
|
|
395
|
+
| `--zone` | string | `""` |
|
|
396
|
+
| `--name` | string | `""` — DNS label or fqdn inside the zone |
|
|
397
|
+
| `--type` | string | `""` — A, AAAA, or CNAME |
|
|
398
|
+
| `--value` | string | `""` — type-shaped target |
|
|
399
|
+
| `--ttl` | string | `"300"` (60..172800) |
|
|
400
|
+
| `--profile` | string | `""` — instance profile (carries the `cloud` block) |
|
|
401
|
+
| `--dns-compartment` | string | `""` — dns-only scope for split-compartment tenancies: flag > cloud block `dnsCompartment` > `compartment`; the no-zones error names the compartment it searched |
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
- `dns records [--zone <id-or-name>]` — list records: domain / type / ttl / value.
|
|
405
|
+
- `dns add --name <label> --type <t> --value <v> [--ttl <s>]` — merges into the
|
|
406
|
+
existing RRSet (round-robin records survive); already present = no-op.
|
|
407
|
+
- `dns rm --name <label> --type <t> --value <v>` — filters the value out of the
|
|
408
|
+
RRSet (an emptied RRSet is removed entirely); no matching record = error.
|
|
409
|
+
|
|
410
|
+
Example: `kampodra dns add --name app --type A --value 203.0.113.10 --zone example.com`
|
|
411
|
+
|
|
412
|
+
## migrate
|
|
413
|
+
|
|
414
|
+
Tenant db migrations over SSH, applied THROUGH the repo checkout ON the VM
|
|
415
|
+
(the app's own script, path from the `migrateScript` config — default
|
|
416
|
+
`scripts/migrate-db.ts`; no parallel implementation; runs via `pnpm exec
|
|
417
|
+
tsx`). Order: root.db first
|
|
418
|
+
(`root/root.db` preferred, legacy flat `root.db` honored), then `tenant_*.db`
|
|
419
|
+
sorted, bounded-parallel. Preconditions are probed in one round-trip and fail
|
|
420
|
+
closed: repo root (must contain the migrate script), pnpm, tenant data dir.
|
|
421
|
+
|
|
422
|
+
| Flag | Type | Default |
|
|
423
|
+
|---|---|---|
|
|
424
|
+
| `--repo-root` | string | `""` — the repo checkout ON THE VM; required (beats `KAMPODRA_REPO_ROOT`) |
|
|
425
|
+
| `--jobs` | string | `""` → `MIGRATE_JOBS` env, else `4` (1–64) |
|
|
426
|
+
| `--allow-running` | bool | `false` — migrate while the api service runs (SQLITE_BUSY risk) |
|
|
427
|
+
| `--host` | string | `""` |
|
|
428
|
+
| `--profile` | string | `""` |
|
|
429
|
+
| `--ssh-key` | string | `""` |
|
|
430
|
+
|
|
431
|
+
Stop-first guard: refuses while the api service is running (init-aware:
|
|
432
|
+
`rc-service` on OpenRC, `systemctl` on systemd; neither found = loud warning and
|
|
433
|
+
proceed). `--allow-running` overrides with a warning. Per-file failures are
|
|
434
|
+
COLLECTED (one bad tenant must not block the others); **exit 1 if any failed —
|
|
435
|
+
do NOT start the API on a half-migrated estate**.
|
|
436
|
+
Example: `kampodra migrate --host root@203.0.113.10 --repo-root /srv/app --jobs 1`
|
|
437
|
+
|
|
438
|
+
## status
|
|
439
|
+
|
|
440
|
+
Live view: deployment count (ledger), health through the proxy
|
|
441
|
+
(`https://<proxyHost><healthPath>` + build-id), and — when a host resolves —
|
|
442
|
+
VM disk on `/var/lib/containers` (warn above 90%), sha-tagged image count +
|
|
443
|
+
prune estimate (keep 2), an init-aware service roll call, and with `--verbose`
|
|
444
|
+
the full metrics snapshot. Never fails on an unhealthy system — it renders
|
|
445
|
+
state.
|
|
446
|
+
|
|
447
|
+
| Flag | Type | Default |
|
|
448
|
+
|---|---|---|
|
|
449
|
+
| `--host` | string | `""` |
|
|
450
|
+
| `--profile` | string | `""` |
|
|
451
|
+
| `--ssh-key` | string | `""` |
|
|
452
|
+
| `--verbose` | bool | `false` (metrics snapshot; needs a target) |
|
|
453
|
+
| `--all-profiles` | bool | `false` — one full section per configured profile |
|
|
454
|
+
|
|
455
|
+
Example: `kampodra status --host root@203.0.113.10 --verbose`
|
|
456
|
+
|
|
457
|
+
## bluegreen
|
|
458
|
+
|
|
459
|
+
Reserved-IP pair for zero-downtime cutovers, multi-provider (oci | gcp
|
|
460
|
+
behind the neutral dispatch). One instance can start alone; the pair
|
|
461
|
+
shares one RESERVED public IP (derived from LIVE provider state — no
|
|
462
|
+
local state file). Instances are `<prefix>-blue/green`. Auth: profile
|
|
463
|
+
`cloud` block / env / native (no cloud flags). Provision pins
|
|
464
|
+
`VM.Standard.A1.Flex` 2/12 on oci, `e2-standard-2` on gcp; the gcp
|
|
465
|
+
anchor is the reserved external IP itself (the guest watcher's metadata
|
|
466
|
+
poll is the documented follow-up).
|
|
467
|
+
|
|
468
|
+
- `deploy --to <blue|green> [--force-flip]` — the standby-update
|
|
469
|
+
composite: resolve the RUNNING idle color → build once (clean-tree
|
|
470
|
+
gate) → push + health-gate it on ITS own IP → flip (health-gated).
|
|
471
|
+
Pipeline failure = no flip (the pair keeps serving the holder). The
|
|
472
|
+
PRE-flip public smoke is skipped by design (the edge is still the
|
|
473
|
+
holder's); the flip's own ACME + served-sha verify through the
|
|
474
|
+
reserved IP is the post-cutover smoke.
|
|
475
|
+
|
|
476
|
+
- `status` — pair view: reserved IP + holder, both instances (ocid, AD),
|
|
477
|
+
per-color app health. Read-only.
|
|
478
|
+
- `init` — create the DORMANT reserved IP (`kampodra-active`); idempotent.
|
|
479
|
+
- `provision <blue|green>` — launch the sibling from the other color's AD
|
|
480
|
+
+ subnet (`VM.Standard.A1.Flex` 2/12). `--os` (`alpine`\|`ubuntu`,
|
|
481
|
+
default `alpine`; ubuntu = 24.04 LTS) picks the golden-image prefix.
|
|
482
|
+
Native: newest `<container>-alpine*` (default) or
|
|
483
|
+
`<container>-ubuntu-24.04*` (`--os ubuntu`) custom image with UEFI_64
|
|
484
|
+
firmware. Else inject: platform-image launch
|
|
485
|
+
(`--platform-key` file / `OPS_SSH_PUBKEY`, `--platform-user` /
|
|
486
|
+
`PLATFORM_SSH_USER` default `ubuntu`) + golden qcow2 streamed onto the
|
|
487
|
+
boot disk (`--qcow2` / `ALPINE_QCOW2`; qemu-img required) + reboot +
|
|
488
|
+
per-OS boot verify (`/etc/alpine-release` 3.x for alpine, `ID=ubuntu`
|
|
489
|
+
in `/etc/os-release` for ubuntu). `--image-id` forces one image. Ends
|
|
490
|
+
with `vm-prepare` next steps (does not run it).
|
|
491
|
+
- `flip --to <color> [--force]` — ACME-first: health gate on the target's
|
|
492
|
+
own IP (unless `--force`) → anchor conf on the target guest (watcher
|
|
493
|
+
configures it) → reserved IP assigned (waits ASSIGNED) → ACME on the
|
|
494
|
+
target for the `<ip-dashes>.sslip.io` hostname → valid-cert HTTPS +
|
|
495
|
+
served-sha verify through the reserved IP → FLIPPED, then prints the
|
|
496
|
+
post-flip proxyHost guidance (the pre-flip host is DE-REGISTERED —
|
|
497
|
+
update the profile's `project.proxyHost` AND kampodra.json). Warns when
|
|
498
|
+
the CURRENT holder is unhealthy: after the cutover there is NO healthy
|
|
499
|
+
fallback (warn-only — rescuing a dead pair is legitimate). Post-assign
|
|
500
|
+
failure auto-rolls back to the other color's EXISTING anchor
|
|
501
|
+
(lookup-only) or to dormant, then cleans the failed target's guest.
|
|
502
|
+
Bad `--to` = **exit 2**.
|
|
503
|
+
- `rollback` — unassign to DORMANT: holder resolved by anchor match
|
|
504
|
+
(lookup-only), guest cleaned (conf + address), unassign waits AVAILABLE.
|
|
505
|
+
|
|
506
|
+
## image-import
|
|
507
|
+
|
|
508
|
+
Upload a packer-built qcow2 and import it as an OCI custom image (build
|
|
509
|
+
machine; PARAVIRTUALIZED — alpine imports self-supported, ubuntu as
|
|
510
|
+
`Canonical Ubuntu` / `Ubuntu 24.04` via `--os alpine|ubuntu`, default
|
|
511
|
+
`alpine`). `--image` required;
|
|
512
|
+
`--bucket` (`KAMPODRA_IMPORT_BUCKET`, else `<container>-image-import`);
|
|
513
|
+
`--name-prefix` (default `<container>-alpine`, or
|
|
514
|
+
`<container>-ubuntu-24.04` with `--os ubuntu` — provision looks this up);
|
|
515
|
+
`--compartment`; `--keep-object`. Polls AVAILABLE (30m cap, terminal
|
|
516
|
+
states fail); non-UEFI firmware warns loudly (A1 rejects, x86 launches);
|
|
517
|
+
staged object deleted unless kept; prints the image OCID.
|
|
518
|
+
|
|
519
|
+
## Exit codes
|
|
520
|
+
|
|
521
|
+
- `0` success (including degraded-but-loud views like `deploy list` without a VM).
|
|
522
|
+
- `2` unknown top-level command (the offender is named, the index reprinted).
|
|
523
|
+
- `1` everything else that errors, plus the defined semantic failures:
|
|
524
|
+
`env diff` differing (GNU diff convention), `metrics --disk-threshold` threshold
|
|
525
|
+
reached, `migrate` per-file failures (do-not-restart signal), `backup list`
|
|
526
|
+
LIST-denied, `backup download` digest mismatch, `dns` ambiguous multi-zone
|
|
527
|
+
compartment, `deploy logs --follow` / `ssh` / `deploy shell` propagating a
|
|
528
|
+
non-zero remote exit code.
|
|
529
|
+
|
|
530
|
+
## Agent gotchas
|
|
531
|
+
|
|
532
|
+
- **Rollback is a FLAG, not a subcommand**: `kampodra deploy --rollback [<sha7>]`.
|
|
533
|
+
Bare `--rollback` resolves from the VM's deployed-sha stamp and DIES without
|
|
534
|
+
one — it never falls back to git HEAD.
|
|
535
|
+
- **Cloud auth is provider-native**: `backup`/`dns` (multi-provider: oci |
|
|
536
|
+
gcp) take no auth flags. The instance profile's `cloud` block (oci:
|
|
537
|
+
`profile`/`compartment`/`instancePrincipal`; gcp: `provider: "gcp"` +
|
|
538
|
+
`project`/`account`) overrides; `KAMPODRA_CLOUD_PROFILE`/`KAMPODRA_CLOUD_COMPARTMENT` env are
|
|
539
|
+
the oci escape hatch (the `OCI_*` names are deprecated aliases — read
|
|
540
|
+
this release only, one stderr notice per process); with neither set the provider CLI resolves its
|
|
541
|
+
default itself. `bluegreen`/`image-import` are OCI-only today — a `gcp`
|
|
542
|
+
profile fails closed naming the surface.
|
|
543
|
+
`--profile` always means the kampodra INSTANCE profile.
|
|
544
|
+
- **`migrate` runs the app's own script**: path from the `migrateScript`
|
|
545
|
+
config (default `scripts/migrate-db.ts`) inside the VM repo checkout,
|
|
546
|
+
executed with `pnpm exec tsx`; `--repo-root`/`KAMPODRA_REPO_ROOT` is
|
|
547
|
+
mandatory.
|
|
548
|
+
- **`vm-wipe` is destructive**: `--yes` gates everything; the host-mismatch
|
|
549
|
+
refusal needs `--force`; `--keep-data` spares only the tenant data dir.
|
|
550
|
+
- `env push`/`deploy --env-file` overwrite the remote env atomically but do NOT
|
|
551
|
+
restart — follow with `deploy restart` or a deploy.
|
|
552
|
+
- `env pull` without `--out` puts RAW VALUES on stdout (summary on stderr);
|
|
553
|
+
never pipe it to a log.
|
|
554
|
+
- The manifest key is `envFile`; the profile `project` block's legacy key is
|
|
555
|
+
`envFilePath`. Only `envFile` is valid in `kampodra.json` (strict parse).
|
|
556
|
+
- The bucket ladder is `--bucket` > `KAMPODRA_BUCKET` (the project ladder's
|
|
557
|
+
own bucket layer) > project config `bucket`.
|
|
558
|
+
- `deploy prune` and `vm-wipe` both delete permanently — always `--dry-run` /
|
|
559
|
+
`--keep-data` first when unsure.
|
|
560
|
+
- `bluegreen` (reserved-IP pair: `status | init | provision | flip --to | rollback`) and `image-import --image` are live; flips are ACME-first with auto-rollback, imports warn on non-UEFI firmware.
|