vibed-infra 0.7.0 → 0.8.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/README.md CHANGED
@@ -41,10 +41,17 @@ Multi-app: further products’ `install-gateway.sh` only add `apps/{other}/sites
41
41
  | Path | Purpose |
42
42
  |------|---------|
43
43
  | `~/services/gateway` | Host nginx (`GATEWAY_HOME`) |
44
- | `~/services/vibed-infra/update-agent` | Serial pull queue + optional GHCR webhook |
44
+ | `~/services/vibed-infra/update-agent` | Serial pull queue; GHCR notify via GitHub Actions OIDC |
45
45
  | `~/services/vibed-infra/persist-logs` | Per-app WALs + optional R2/S3 ship |
46
46
 
47
- Auto-update cron **enqueues** work; the agent processes one job at a time. After a GHCR push, the reusable image workflow mints a GitHub Actions OIDC JWT and notifies `https://{domain}/_vibed/hooks/ghcr` (fallback `http://{publicIp}/…`) so the pull does not wait for cron. Updates prune dangling images (`DOCKER_AUTO_PRUNE=1`).
47
+ ## Image updates (docker pull)
48
+
49
+ Install registers each role in the machine **update-agent**. Two paths enqueue work; the agent runs **one pull at a time** (`update-*.sh` is digest-gated — no restart if the image digest is unchanged):
50
+
51
+ 1. **Immediate** — push `:main` with the reusable GHCR workflow (`id-token: write`). CI mints an OIDC JWT and POSTs `https://{site host}/_vibed/hooks/ghcr` (fallback `http://{publicIp}/…`). No GitHub webhook UI or `VIBED_WEBHOOK_SECRET`.
52
+ 2. **Cron** — if `*_AUTO_UPDATE=1`, periodic enqueue (backup if notify is missed).
53
+
54
+ See [`skills/infra-update-agent/SKILL.md`](skills/infra-update-agent/SKILL.md). After a successful update, dangling images are pruned (`DOCKER_AUTO_PRUNE=1`).
48
55
 
49
56
  ## Layout
50
57
 
@@ -55,7 +62,7 @@ Auto-update cron **enqueues** work; the agent processes one job at a time. After
55
62
  | `templates/update-agent/` | Queue agent + webhook |
56
63
  | `templates/persist-logs/` | Shipper install |
57
64
  | `lib/persistlog/` | Python append / seal / replay |
58
- | `skills/` | system-gateway, infra-update-agent, persist-logs, dns-configure |
65
+ | `skills/` | system-gateway, infra-update-agent, infra-cicd, infra-packager, persist-logs, dns-configure |
59
66
 
60
67
  ## Environment
61
68
 
@@ -63,6 +70,7 @@ Auto-update cron **enqueues** work; the agent processes one job at a time. After
63
70
  |----------|---------|
64
71
  | `GATEWAY_HOME` | Host gateway dir (default `~/services/gateway`) |
65
72
  | `VIBED_HOME` | Machine vibed root (default `~/services/vibed-infra`) |
73
+ | `VIBED_UPDATE_AGENT` | Override update-agent dir (default `$VIBED_HOME/update-agent`) |
66
74
  | `GATEWAY_PUBLIC_IP` | VPS IPv4 (from `gateway.publicIp`) |
67
75
  | `TLS_EMAIL` / `TLS_MODE` | Let’s Encrypt email; `lab` or `letsencrypt` |
68
76
  | `PERSIST_LOG_DIR` | Per-service event log dir |
@@ -72,6 +80,7 @@ Auto-update cron **enqueues** work; the agent processes one job at a time. After
72
80
 
73
81
  ```bash
74
82
  npm test
83
+ npm run test:oidc # real GitHub OIDC mint; skips locally unless ACTIONS_ID_TOKEN_* set
75
84
  npm run test:dist
76
85
  npm run test:e2e-multi # two apps, localhost wget|bash, shared host gateway
77
86
  ```
package/lib/package.py CHANGED
@@ -351,6 +351,10 @@ wget -qO- https://raw.githubusercontent.com/ORG/REPO/main/dist/install-gateway.s
351
351
  Network: `{meta['network']}` (shared host edge). API container: `{meta['apiContainer']}`.
352
352
  Gateway install bootstraps `~/services/gateway` once, then drops `apps/{{product}}/sites.conf`.
353
353
 
354
+ ## Image updates
355
+
356
+ wget install registers this product with the machine update-agent. A GHCR `:main` push (reusable workflow + `id-token: write`) POSTs `/_vibed/hooks/ghcr` so the VPS pulls immediately. Cron `*_AUTO_UPDATE` is a backup. See the packager’s infra-update-agent skill.
357
+
354
358
  ## DNS (AU agent)
355
359
 
356
360
  Copy [`DNS-SKILL.md`](DNS-SKILL.md) into an AU agent browser extension. Domains and `gateway.publicIp` are baked in when configured; confirm the IP with the operator.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibed-infra",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Product-agnostic VPS packager: wget install, Docker Compose, TLS/nginx, auto-update",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -39,7 +39,7 @@ Compiled `packageconfig.yaml` also includes `webhook.url` / `fallbackUrl` (from
39
39
  | Path | Purpose |
40
40
  |------|---------|
41
41
  | `install-*.sh` | wget entrypoints |
42
- | `notify-vps-pull.py` | Called by GHCR workflow after push; uses compiled `webhook` |
42
+ | `notify-vps-pull.py` | GHCR workflow: mint OIDC JWT and POST webhook URLs from `webhook:` |
43
43
  | `DNS-SKILL.md` | Paste into AU browser agent; includes domains + `publicIp` when set |
44
44
  | `packageconfig.yaml` | compiled config |
45
45
  | `start-*.sh` / `update-*.sh` | lifecycle |
@@ -47,11 +47,15 @@ Compiled `packageconfig.yaml` also includes `webhook.url` / `fallbackUrl` (from
47
47
 
48
48
  Gateway install writes `$GATEWAY_HOME/apps/{name}/sites.conf` (host-extension mode).
49
49
 
50
- ## Auto-update
50
+ ## Auto-update and docker pull
51
51
 
52
- When `*_AUTO_UPDATE=1`, cron **enqueues** into the machine update-agent (serial pulls). Fallback: direct `update-*.sh` if agent missing. After update: dangling image prune (`DOCKER_AUTO_PRUNE`).
52
+ Install registers each role with the machine **update-agent**. Pulls are serial and digest-gated (`update-*.sh`).
53
53
 
54
- Immediate pulls: install registers the app. The reusable docker-build workflow mints a GitHub Actions OIDC JWT and POSTs to `https://{host}/_vibed/hooks/ghcr` (fallback `http://{publicIp}/_vibed/hooks/ghcr`) after pushing `:main`. The VPS verifies GitHub’s signature (`id-token: write` on the caller job). Local curl may use the compiled `webhook.token`.
54
+ - **Immediate:** reusable GHCR workflow on the default branch mints a GitHub Actions OIDC JWT and POSTs `https://{host}/_vibed/hooks/ghcr` (fallback `http://{publicIp}/_vibed/hooks/ghcr`). Caller needs `id-token: write`. See [`skills/infra-update-agent/SKILL.md`](../skills/infra-update-agent/SKILL.md).
55
+ - **Cron:** when `*_AUTO_UPDATE=1`, cron enqueues on an interval (backup if notify is missed). Fallback: direct `update-*.sh` if the agent is missing.
56
+ - After update: dangling image prune (`DOCKER_AUTO_PRUNE`).
57
+
58
+ `webhook.token` in compiled packageconfig is only for local curl; CI does not need `VIBED_WEBHOOK_SECRET`.
55
59
 
56
60
  ## Persist logs
57
61
 
@@ -63,6 +67,7 @@ Immediate pulls: install registers the app. The reusable docker-build workflow m
63
67
  |----------|---------|
64
68
  | `GATEWAY_HOME` | Host gateway (default `~/services/gateway`) |
65
69
  | `VIBED_HOME` | `~/services/vibed-infra` |
70
+ | `VIBED_UPDATE_AGENT` | Override update-agent dir (default `$VIBED_HOME/update-agent`) |
66
71
  | `GATEWAY_PUBLIC_IP` / `TLS_EMAIL` / `TLS_MODE` | Host TLS (`setup-tls.sh`) |
67
72
  | `PACKAGER_RAW` / `PRODUCT_RAW` / `PACKAGECONFIG_URL` | as before |
68
73
  | `INFRA_PROFILE` | `api`, `ui`, `nodes`, `gateway` |
@@ -2,8 +2,7 @@
2
2
  name: infra-cicd
3
3
  description: >-
4
4
  Set up GitHub Actions CI/CD for infra-packaged products: GHCR image build,
5
- package validation, and dist e2e (api/ui/nodes). Use when adding deploy
6
- pipelines for Backend/UI/worker images.
5
+ OIDC notify for immediate VPS pulls, package validation, and dist e2e.
7
6
  ---
8
7
 
9
8
  # Infra packager CI/CD
@@ -39,13 +38,14 @@ On the default branch, the reusable workflow mints a GitHub Actions OIDC JWT (au
39
38
  ## Product repo CI
40
39
 
41
40
  1. Run `./package.sh` and fail if `dist/` drifted from templates (optional `git diff --exit-code dist`).
42
- 2. Build/push images to GHCR (reusable workflow notifies the VPS automatically).
41
+ 2. Build/push images to GHCR (reusable workflow notifies the VPS; requires `id-token: write`).
43
42
  3. Optional job: `test-dist.sh --profile api` on a Docker-enabled runner.
44
43
 
45
44
  ## Checklist
46
45
 
47
46
  - [ ] Four YAML templates under `templates/` (`gateway.publicIp` / `tlsEmail` / `sites[]` set for production)
48
47
  - [ ] Committed `dist/` matches `./package.sh` output (including `DNS-SKILL.md`)
49
- - [ ] Image names in configs match GHCR
48
+ - [ ] Image names in configs match GHCR (`ghcr.io/{owner}/…` so OIDC `repository_owner` can match)
49
+ - [ ] GHCR reusable job has `permissions.id-token: write`
50
50
  - [ ] `npm test` / install dry-run passes
51
51
  - [ ] Secrets not in templates — only `.env.*.example` placeholders (publicIp/domain are OK to commit)
@@ -2,8 +2,8 @@
2
2
  name: infra-packager
3
3
  description: >-
4
4
  Wire a new product to the infra VPS packager: four YAML configs, package.sh →
5
- committed dist/, Docker images, and CI. Use when adding Backend/UI/worker
6
- deploy to a repo or migrating from ad-hoc deploy/install scripts.
5
+ committed dist/, Docker images, GHCR OIDC pulls, and CI. Use when adding
6
+ Backend/UI/worker deploy to a repo or migrating from ad-hoc install scripts.
7
7
  ---
8
8
 
9
9
  # Infra packager — onboard a product
@@ -17,7 +17,7 @@ description: >-
17
17
 
18
18
  1. **Depend on** [`vibed-infra`](https://www.npmjs.com/package/vibed-infra) or clone this repo for `package.sh`.
19
19
 
20
- 2. **Create** `templates/` with four files — see [`schema/packageconfig.md`](../../schema/packageconfig.md) and [`examples/vps-hello/templates/`](../../examples/vps-hello/templates/). Set `gateway.publicIp`, `gateway.tlsEmail`, and `gateway.sites[].host` for DNS-SKILL + HTTPS.
20
+ 2. **Create** `templates/` with four files — see [`schema/packageconfig.md`](../../schema/packageconfig.md) and [`examples/vps-hello/templates/`](../../examples/vps-hello/templates/). Set `gateway.publicIp`, `gateway.tlsEmail`, and `gateway.sites[].host` for DNS-SKILL + HTTPS (those values are also the GHCR notify URLs).
21
21
 
22
22
  3. **Add** a tiny product `package.sh`:
23
23
 
@@ -27,7 +27,7 @@ PACKAGER="$(node -e "console.log(require('path').dirname(require.resolve('vibed-
27
27
  exec bash "$PACKAGER/package.sh" --product "$ROOT" --out "$ROOT/dist"
28
28
  ```
29
29
 
30
- 4. **Build images** — Dockerfiles in `app/`; tag `:local` for dev or push to GHCR for prod; reference image names in `*-config.yaml`. The reusable GHCR workflow notifies the VPS after push via GitHub Actions OIDC (`id-token: write`).
30
+ 4. **Images** — Dockerfiles in `app/`; tag `:local` for dev; GHCR `:main` for prod (names in `*-config.yaml`). Immediate VPS pull uses the reusable GHCR workflow with `id-token: write` — see infra-cicd and infra-update-agent. No extra GitHub secret.
31
31
 
32
32
  5. **Package and commit** `dist/` (includes `DNS-SKILL.md` with domains + public IP):
33
33
 
@@ -36,9 +36,9 @@ exec bash "$PACKAGER/package.sh" --product "$ROOT" --out "$ROOT/dist"
36
36
  git add dist && git commit && git push
37
37
  ```
38
38
 
39
- 6. **CI** — `npm test` (packager validate + install dry-run); optional `test-dist.sh --profile api|ui|nodes` and `npm run test:e2e-multi`. See [`examples/vps-hello/test-dist.sh`](../../examples/vps-hello/test-dist.sh).
39
+ 6. **CI** — product repo: package drift check + GHCR build (notifies VPS). Packager repo: `npm test`, `oidc-webhook-e2e`, `test-dist.sh`, `npm run test:e2e-multi`.
40
40
 
41
- 7. **VPS** — DNS first (paste `dist/DNS-SKILL.md` into AU agent), then wget:
41
+ 7. **VPS** — DNS first (paste `dist/DNS-SKILL.md` into AU agent), then wget. Install registers the app with the update-agent so the next GHCR push can pull immediately:
42
42
 
43
43
  ```bash
44
44
  wget -qO- .../dist/install-api.sh | bash
@@ -56,3 +56,4 @@ wget -qO- .../dist/install-gateway.sh | bash # bootstraps host + setup-tls.sh
56
56
  - Gateway container names in `gateway.sites[]` must match running API/UI container names on `network.edge`.
57
57
  - UI is a separate profile; gateway is nginx-only and does not start the UI.
58
58
  - Prefer Let's Encrypt via `gateway.tlsEmail`; use `TLS_MODE=lab` only for CI/lab.
59
+ - Product GHCR workflow must set `id-token: write` or the VPS will not get an immediate pull.
@@ -3,44 +3,50 @@ name: infra-update-agent
3
3
  description: >-
4
4
  Machine-level serial Docker update queue for vibed-infra apps. Use when
5
5
  multiple products share one VPS and must not pull images in parallel; includes
6
- GHCR webhook enqueue via GitHub Actions OIDC (no extra GitHub secrets).
6
+ immediate GHCR pulls via GitHub Actions OIDC (no extra GitHub secrets).
7
7
  ---
8
8
 
9
9
  # Serial update agent
10
10
 
11
11
  ## Why
12
12
 
13
- Five apps with auto-update cron would each `docker pull` at once and overload the box. The **update-agent** is installed once; apps only **enqueue** jobs.
13
+ Five apps with auto-update cron would each `docker pull` at once and overload the box. The **update-agent** is installed once; apps only **enqueue** jobs. The agent runs `update-*.sh`, which is **digest-gated**: pull if the registry digest changed, restart only if the running container is stale.
14
14
 
15
15
  ## Layout
16
16
 
17
17
  ```
18
18
  ~/services/vibed-infra/update-agent/
19
19
  queue/ processing/ done/ failed/
20
- registry/{app}/{role}.json
21
- tokens/{app} # optional local curl; CI uses OIDC instead
20
+ registry/{app}/{role}.json # image + installDir (written at wget install)
21
+ tokens/{app} # optional local curl; CI uses OIDC
22
22
  agent.sh
23
23
  enqueue.sh
24
- webhook_server.py # 0.0.0.0:19200 (firewall: do not expose this port)
24
+ webhook_server.py # 0.0.0.0:19200 do not open this port publicly
25
25
  lib/github_oidc.py
26
- .env # WEBHOOK_PORT; optional WEBHOOK_SECRET
26
+ .env # WEBHOOK_PORT=19200
27
27
  ```
28
28
 
29
- ## Flow
29
+ Override the directory with `VIBED_UPDATE_AGENT` (wins over `VIBED_HOME`).
30
30
 
31
- 1. Install registers the app (image + install dir).
32
- 2. Cron enqueues; agent cron (`*/5`) processes **serially**.
33
- 3. Image push: reusable GHCR workflow mints a GitHub Actions OIDC JWT (audience = webhook URL) and POSTs `{package,tag}` to `/_vibed/hooks/ghcr`.
34
- 4. Webhook verifies the JWT against GitHub’s JWKS (`iss`, `aud`, `exp`, RS256), then enqueues only registry images whose name contains `repository_owner`.
35
- 5. Agent runs `update-*.sh` immediately.
31
+ ## Pull paths
36
32
 
37
- Gateway exposes `/_vibed/hooks/` on HTTP (port 80) and on each HTTPS site host.
33
+ ```
34
+ git push main
35
+ → reusable GHCR workflow builds/pushes :main
36
+ → notify-vps-pull.py mints OIDC JWT (aud = webhook URL)
37
+ → POST https://{host}/_vibed/hooks/ghcr (fallback http://{publicIp}/…)
38
+ → nginx → webhook_server (verify GitHub JWKS)
39
+ → enqueue matching registry entries (owner must appear in image name)
40
+ → agent.sh → update-{role}.sh → docker pull + restart if digest changed
41
+ ```
38
42
 
39
- ## Zero extra GitHub setup
43
+ - **Immediate:** default-branch image push. Caller job needs `id-token: write`. No GitHub Packages webhook and no `VIBED_WEBHOOK_SECRET`.
44
+ - **Cron backup:** `*_AUTO_UPDATE=1` enqueues on an interval; agent cron `*/5` drains the queue if notify was missed.
45
+ - **Manual:** `bash ~/services/vibed-infra/update-agent/enqueue.sh /path/to/api-install`
40
46
 
41
- `gateway.publicIp` + `gateway.sites[].host` already in product config are the URLs. Auth is GitHub’s signature no `VIBED_WEBHOOK_SECRET`.
47
+ Gateway HTTP (`00-default.conf`) and each HTTPS site proxy `/_vibed/hooks/` to `host.docker.internal:19200`. Keep **19200** off the public firewall; only 80/443.
42
48
 
43
- Product workflow must grant OIDC to the reusable job:
49
+ ## Product CI
44
50
 
45
51
  ```yaml
46
52
  jobs:
@@ -55,10 +61,11 @@ jobs:
55
61
  image: ghcr.io/${{ github.repository_owner }}/my-api
56
62
  ```
57
63
 
58
- Local curl can still use `X-Vibed-Secret` from `tokens/{app}` (compiled hash). Optional `WEBHOOK_SECRET` override remains.
64
+ URLs come from `gateway.publicIp` + `gateway.sites[].host` already in config (`dist/packageconfig.yaml` `webhook.url` / `fallbackUrl`). Disable notify with `notify: false`.
59
65
 
60
- ## Register / enqueue manually
66
+ Local curl can send `X-Vibed-Secret` from `tokens/{app}` (compiled hash). Optional `WEBHOOK_SECRET` still works as an override.
61
67
 
62
- ```bash
63
- bash ~/services/vibed-infra/update-agent/enqueue.sh /path/to/api-install
64
- ```
68
+ ## Tests
69
+
70
+ - `npm test` — openssl-signed JWT (same verifier) + enqueue under `VIBED_UPDATE_AGENT`
71
+ - `npm run test:oidc` / CI job `oidc-webhook-e2e` — real GitHub OIDC mint against a local webhook
@@ -58,9 +58,9 @@ cd ~/services/gateway
58
58
  - **Production:** certbot (webroot if gateway is up, else standalone). On failure, fix DNS then re-run `setup-tls.sh`.
59
59
  - **Lab/CI:** `TLS_MODE=lab` → multi-SAN self-signed under `./certs/`.
60
60
 
61
- ## Webhook path
61
+ ## Webhook path (immediate docker pull)
62
62
 
63
- Host `00-default.conf` (HTTP) and each app `sites.conf` (HTTPS) proxy `/_vibed/hooks/` to the update-agent on `host.docker.internal:19200`. Image builds notify this path automatically — see infra-update-agent skill.
63
+ Host `00-default.conf` (HTTP) and each app `sites.conf` (HTTPS) proxy `/_vibed/hooks/` to the update-agent on `host.docker.internal:19200`. After a GHCR `:main` push, CI POSTs a GitHub Actions OIDC JWT to `/_vibed/hooks/ghcr` — see infra-update-agent. Do not expose port 19200 on the public firewall.
64
64
 
65
65
  ## Pitfalls
66
66