@celilo/cli 0.11.0-alpha.0 → 0.12.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/AGENTS.md +7 -4
- package/CELILO_CORE_MODULES.md +7 -6
- package/CELILO_SUBSYSTEMS.md +47 -5
- package/MODULE_PRIMITIVES.md +164 -0
- package/README.md +5 -5
- package/drizzle/0015_port_forwards.sql +12 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +8 -8
- package/schemas/system_config.json +1 -1
- package/src/ansible/inventory.test.ts +1 -1
- package/src/ansible/inventory.ts +3 -3
- package/src/capabilities/public-web-publish.test.ts +209 -0
- package/src/capabilities/validation.test.ts +1 -1
- package/src/capabilities/well-known.test.ts +1 -1
- package/src/capabilities/well-known.ts +2 -2
- package/src/cli/commands/api.ts +1 -1
- package/src/cli/commands/backup-pull.ts +1 -1
- package/src/cli/commands/events.ts +1 -1
- package/src/cli/commands/module-changeset.ts +1 -1
- package/src/cli/commands/module-import.ts +1 -1
- package/src/cli/commands/module-publish.ts +1 -1
- package/src/cli/commands/module-remove.ts +26 -1
- package/src/cli/commands/module-types.ts +1 -1
- package/src/cli/commands/module-upgrade.ts +1 -1
- package/src/cli/commands/module-version.ts +1 -1
- package/src/cli/commands/module-where.test.ts +26 -0
- package/src/cli/commands/module-where.ts +130 -0
- package/src/cli/commands/proxmox-node-list.ts +1 -1
- package/src/cli/commands/publish/alpha.test.ts +1 -1
- package/src/cli/commands/publish/alpha.ts +3 -3
- package/src/cli/commands/publish/changesets.ts +1 -1
- package/src/cli/commands/publish/global-install.ts +6 -2
- package/src/cli/commands/publish/helpers.ts +21 -14
- package/src/cli/commands/publish/index.ts +4 -4
- package/src/cli/commands/publish/plan.ts +3 -3
- package/src/cli/commands/publish/preflight.ts +2 -2
- package/src/cli/commands/publish/types.ts +2 -2
- package/src/cli/commands/publish/workspace.test.ts +1 -1
- package/src/cli/commands/publish/workspace.ts +2 -2
- package/src/cli/commands/registry-owner.test.ts +166 -0
- package/src/cli/commands/registry-owner.ts +124 -0
- package/src/cli/commands/registry-token.test.ts +109 -0
- package/src/cli/commands/registry-token.ts +194 -0
- package/src/cli/commands/restore.ts +1 -1
- package/src/cli/commands/service-add-proxmox.ts +1 -1
- package/src/cli/commands/subscribers-list.ts +1 -1
- package/src/cli/commands/system-apply-config-equivalence.test.ts +1 -1
- package/src/cli/commands/system-apply-config.ts +1 -1
- package/src/cli/commands/system-init-deprecation.test.ts +1 -1
- package/src/cli/commands/system-init.ts +2 -2
- package/src/cli/commands/token.test.ts +26 -0
- package/src/cli/commands/token.ts +160 -0
- package/src/cli/completion.ts +19 -0
- package/src/cli/index.ts +155 -4
- package/src/cli/restore-command.test.ts +1 -1
- package/src/db/client.ts +1 -1
- package/src/db/schema.ts +50 -7
- package/src/hooks/capability-loader-firewall.test.ts +4 -3
- package/src/hooks/capability-loader.ts +100 -10
- package/src/hooks/define-hook.test.ts +24 -0
- package/src/hooks/executor.test.ts +1 -1
- package/src/hooks/executor.ts +2 -2
- package/src/hooks/types.ts +1 -1
- package/src/manifest/contracts/v1.ts +2 -2
- package/src/manifest/schema.ts +13 -13
- package/src/manifest/template-validator.ts +1 -1
- package/src/module/packaging/build.ts +19 -0
- package/src/module/packaging/workspace-deps.test.ts +94 -0
- package/src/module/packaging/workspace-deps.ts +185 -0
- package/src/module/versioning/changeset-version.ts +1 -1
- package/src/policy/no-hand-built-ssh.test.ts +90 -0
- package/src/registry/client.test.ts +86 -0
- package/src/registry/client.ts +67 -1
- package/src/services/api-access.ts +1 -1
- package/src/services/aspect-approvals.ts +1 -1
- package/src/services/aspect-runner.ts +3 -3
- package/src/services/aspect-template-resolver.test.ts +1 -1
- package/src/services/aspect-template-resolver.ts +1 -1
- package/src/services/build-bus/delivery-events.ts +1 -1
- package/src/services/build-bus/fan-out.ts +1 -1
- package/src/services/build-bus/hook-dispatch.ts +1 -1
- package/src/services/build-bus/receiver-server.ts +1 -1
- package/src/services/build-bus/status.test.ts +1 -1
- package/src/services/build-bus/status.ts +1 -1
- package/src/services/build-bus/subscriber-store.ts +1 -1
- package/src/services/bus-interview.ts +1 -1
- package/src/services/celilo-events.ts +1 -1
- package/src/services/celilo-mgmt-hooks.test.ts +1 -1
- package/src/services/cross-module-read.ts +1 -1
- package/src/services/deploy-posture.ts +1 -1
- package/src/services/deployed-systems.test.ts +1 -1
- package/src/services/deployed-systems.ts +4 -4
- package/src/services/dns-provider-backfill.ts +2 -2
- package/src/services/events-daemon.ts +1 -1
- package/src/services/machine-pool.ts +3 -3
- package/src/services/module-deploy.ts +93 -8
- package/src/services/module-subscriptions.ts +1 -1
- package/src/services/module-types-generator.ts +1 -1
- package/src/services/module-validator/git-hygiene.ts +1 -1
- package/src/services/port-forwards.test.ts +92 -0
- package/src/services/port-forwards.ts +86 -0
- package/src/services/proxmox-reconcile.ts +2 -2
- package/src/services/public-web-republish.test.ts +189 -0
- package/src/services/public-web-republish.ts +84 -0
- package/src/services/restore-from-file.ts +1 -1
- package/src/services/restore-preflight.ts +1 -1
- package/src/services/system-identity.ts +1 -1
- package/src/services/system-init.test.ts +2 -2
- package/src/services/system-init.ts +2 -2
- package/src/services/web-route-cleanup.test.ts +250 -0
- package/src/services/web-route-cleanup.ts +144 -0
- package/src/templates/generator.ts +2 -2
- package/src/types/infrastructure.ts +1 -1
- package/src/variables/computed/computed-integration.test.ts +1 -1
- package/src/variables/computed/evaluate.ts +1 -1
- package/src/variables/computed/parse.ts +1 -1
- package/src/variables/context.test.ts +1 -1
- package/src/variables/context.ts +5 -5
- package/src/variables/lxc-nameserver.test.ts +1 -1
- package/src/variables/resolver.ts +2 -2
- package/src/variables/types.ts +1 -1
package/AGENTS.md
CHANGED
|
@@ -77,10 +77,13 @@ celilo module deploy <id> / update / health # deploy + verify on the fleet
|
|
|
77
77
|
implements (IPAM, capabilities, firewall/DNS, generator, …), with entry-point files.
|
|
78
78
|
- `./CELILO_CORE_MODULES.md` — the production modules celilo ships in `modules/`
|
|
79
79
|
(what each provides/requires, its role in the fleet).
|
|
80
|
-
- `../../
|
|
80
|
+
- `../../reference/MODULE_DEVELOPMENT_GUIDE.md` — author a module (start here).
|
|
81
|
+
- `./MODULE_PRIMITIVES.md` — the remote-ops primitives a hook uses to touch a box
|
|
82
|
+
(probe / serviceCtl / applyRenderedConfig / waitFor / …). **Modules never
|
|
83
|
+
hand-build SSH** — read this before writing any hook that reaches a remote host.
|
|
81
84
|
- `./CLI_USAGE.md` — full CLI reference + workflows.
|
|
82
|
-
- `../../
|
|
83
|
-
- `../../
|
|
84
|
-
- `../../
|
|
85
|
+
- `../../openspec/changes/build-bus-poll-cd/proposal.md` — how a change reaches the fleet.
|
|
86
|
+
- `../../reference/APP_CI_REFERENCE.md` — wire an app's CI/CD (the copy-me recipe).
|
|
87
|
+
- `../../openspec/changes/module-version-semantics/proposal.md` — what `version` means; `version_source`.
|
|
85
88
|
- `../../schemas/module-manifest.schema.json` — the manifest contract.
|
|
86
89
|
- `https://celilo.computer/docs` — hosted docs (LAN; the repo docs lead).
|
package/CELILO_CORE_MODULES.md
CHANGED
|
@@ -22,7 +22,7 @@ Each entry: `module id` — what it is — **provides** / **requires** capabilit
|
|
|
22
22
|
## Network fabric (DNS / firewall / DHCP)
|
|
23
23
|
|
|
24
24
|
- **greenwave** — GreenWave C4000XG ISP router driver; port-forwarding + public-IP discovery via REST. **provides:** `firewall`, `dhcp_server`.
|
|
25
|
-
- **iptables** — iptables firewall + NAT; cross-VLAN port exposure with recursive upstream delegation. **provides:** `firewall`.
|
|
25
|
+
- **iptables** — iptables firewall + NAT; cross-VLAN port exposure with recursive upstream delegation. Converge model: `exposeService` registers into the shared-core port-forward registry, then renders the complete ruleset and applies it atomically via `iptables-restore` (default-DROP FORWARD + coarse zone-tier matrix; SSH-free). **provides:** `firewall`.
|
|
26
26
|
- **knot-unbound-internal** — split-horizon internal DNS via Knot (authoritative) + Unbound (recursive); lightweight, plain apt, no .NET. **provides:** `dns_internal`. Ships a base-module-aspect (`modules/knot-unbound-internal/base-module-aspect/`).
|
|
27
27
|
- **technitium** — internal split-horizon DNS resolver + authoritative server (web UI + HTTP API); heavier alternative to knot-unbound. **provides:** `dns_internal`. Ships a base-module-aspect (`modules/technitium/base-module-aspect/`).
|
|
28
28
|
- **namecheap** — public DNS A-record management via Namecheap Dynamic DNS API (HTTP, no browser automation). **provides:** `dns_registrar`.
|
|
@@ -34,17 +34,17 @@ Each entry: `module id` — what it is — **provides** / **requires** capabilit
|
|
|
34
34
|
|
|
35
35
|
## Celilo's own infrastructure (self-hosted)
|
|
36
36
|
|
|
37
|
-
- **celilo-mgmt** — the celilo management server itself, deployed as a module (replaces install.sh + `system init`; ships daemon, runs migrations, self-registers). **provides:** `celilo_event_bus`, `celilo_module_deploy_worker`. **requires:** `cross_module_read`. See `
|
|
38
|
-
- **celilo-registry** — module registry server (Cargo sparse protocol); stores `.netapp` files, serves index + search/download API. **provides:** `registry_publish`. **requires:** `public_web`, `dns_registrar`.
|
|
37
|
+
- **celilo-mgmt** — the celilo management server itself, deployed as a module (replaces install.sh + `system init`; ships daemon, runs migrations, self-registers). **provides:** `celilo_event_bus`, `celilo_module_deploy_worker`. **requires:** `cross_module_read`. See `openspec/specs/management-as-module/spec.md`.
|
|
38
|
+
- **celilo-registry** — module registry server (Cargo sparse protocol); stores `.netapp` files, serves index + search/download API. On install it provisions a confidential introspection OIDC client via `idp.create_oidc_client` (SECURE_MODULE_PUBLISH.md §5[D-A]) and converges its issuer + introspection endpoint + creds onto the box for RFC 7662 token verification. **provides:** `registry_publish`. **requires:** `public_web`, `dns_registrar`, `idp`.
|
|
39
39
|
- **celilo-apt-repo** — Debian apt repository (reprepro + Bun HTTP server) serving the celilo `.deb` at apt.celilo.computer. **provides:** `apt_publish`. **requires:** `public_web`, `dns_registrar`.
|
|
40
40
|
- **celilo-website** — public docs site (static Astro) served via Caddy on celilo.computer. **requires:** `public_web`, `dns_registrar`.
|
|
41
41
|
|
|
42
42
|
## Git forge & CI pipeline
|
|
43
43
|
|
|
44
|
-
- **forgejo** — self-hosted Forgejo git forge (git-over-SSH, OIDC, public ingress). **provides:** `source_forge`. **requires:** `public_web`, `idp`, `firewall`, `dns_registrar`. See `
|
|
45
|
-
- **forgejo-runner** — host-mode (LXC) Forgejo Actions runner for unit/lint jobs; outbound-only, lives in dmz. **requires:** `source_forge`. See `
|
|
44
|
+
- **forgejo** — self-hosted Forgejo git forge (git-over-SSH, OIDC, public ingress). **provides:** `source_forge`. **requires:** `public_web`, `idp`, `firewall`, `dns_registrar`. See `openspec/specs/forgejo-runner/spec.md`.
|
|
45
|
+
- **forgejo-runner** — host-mode (LXC) Forgejo Actions runner for unit/lint jobs; outbound-only, lives in dmz. **requires:** `source_forge`. See `openspec/specs/forgejo-runner/spec.md`.
|
|
46
46
|
- **forgejo-builder** — VM-based, Docker-capable Forgejo Actions runner (`requires.system.type: vm`) for hermetic release builds + the cele2e suite. **requires:** `source_forge`.
|
|
47
|
-
- **npm-cache-node** — self-hosted npm registry: a pass-through disk cache (proxies upstream) plus a `PUT /<pkg>` publish endpoint that stores locally published `@celilo/*` tarballs as authoritative local-origin (never overwritten by upstream). PUT is gated on a static operator-set publish token (`publish_tokens` secret, SHA-256 model; fail-closed — absent/bad token → 401); reads stay open (it's a mirror). **requires:** `public_web`, `dns_registrar`. See `
|
|
47
|
+
- **npm-cache-node** — self-hosted npm registry: a pass-through disk cache (proxies upstream) plus a `PUT /<pkg>` publish endpoint that stores locally published `@celilo/*` tarballs as authoritative local-origin (never overwritten by upstream). PUT is gated on a static operator-set publish token (`publish_tokens` secret, SHA-256 model; fail-closed — absent/bad token → 401); reads stay open (it's a mirror). **requires:** `public_web`, `dns_registrar`. See `openspec/changes/private-npm-registry/proposal.md`.
|
|
48
48
|
|
|
49
49
|
## Applications
|
|
50
50
|
|
|
@@ -52,6 +52,7 @@ Each entry: `module id` — what it is — **provides** / **requires** capabilit
|
|
|
52
52
|
|
|
53
53
|
## E2E fixtures & probes (not production apps)
|
|
54
54
|
|
|
55
|
+
- **hello-foo** / **hello-bar** — hostless single-page sites (one hand-written `site/index.html`, no build step) published at a configurable `domain` + `path`. They exist so the caddy route-lifecycle e2e can put several independent routes on one caddy and watch them interact — celilo-website is the only other static-site module, and one site can't answer "does removing `/` break `/foo`?". **requires:** `public_web`, `dns_registrar`.
|
|
55
56
|
- **source-forge-probe** — hostless e2e probe that exercises the `source_forge` capability (issue/revoke runner token) against a deployed Forgejo. **requires:** `source_forge`.
|
|
56
57
|
- **test-ansible-output** — e2e fixture that runs 25×1s sleep tasks to assert Ansible output streams in real time. Not a real app.
|
|
57
58
|
- **vm-smoke** — throwaway module proving celilo's generic `type: vm` deploy path end-to-end (template clone → cloud-init → Ansible reach). No capabilities.
|
package/CELILO_SUBSYSTEMS.md
CHANGED
|
@@ -11,7 +11,7 @@ inventory / the codebase and cite what you found. Only raise a question if the s
|
|
|
11
11
|
is genuinely empty, or the real choice is between existing mechanisms.
|
|
12
12
|
|
|
13
13
|
This is an *implemented-feature* map. For terminology see `GLOSSARY.md`; for architecture
|
|
14
|
-
see `
|
|
14
|
+
see `openspec/specs/`. Companion doc: [CELILO_CORE_MODULES.md](./CELILO_CORE_MODULES.md)
|
|
15
15
|
(the production modules celilo ships).
|
|
16
16
|
|
|
17
17
|
> **All file paths below are relative to the celilo source-repo root** (e.g.
|
|
@@ -26,6 +26,7 @@ see `design/README.md`. Companion doc: [CELILO_CORE_MODULES.md](./CELILO_CORE_MO
|
|
|
26
26
|
- **IPAM (IP/VMID allocation)** — `apps/celilo/src/ipam/allocator.ts` — `allocateIPFromSubnet`, `allocateVMID`, `reserveIP`/`unreserveIP`, `inferZoneFromIP`, `getAllocation`. Auto-wrapper: `apps/celilo/src/ipam/auto-allocator.ts` — `allocateForModule` / `deallocateForModule`.
|
|
27
27
|
- **Infrastructure selection (container-service vs machine pool)** — `apps/celilo/src/services/machine-pool.ts` (`getMachineByHostname`, `addMachine`, `assignModuleToMachine`) and `apps/celilo/src/services/container-service.ts` (`getContainerServiceByName`, `addContainerService`, `verifyContainerService`). Provider API clients: `apps/celilo/src/api-clients/proxmox.ts`, `apps/celilo/src/api-clients/digitalocean.ts`.
|
|
28
28
|
- **Zone detection / system config** — `apps/celilo/src/services/zone-detector.ts` — `detectZoneFromIp` reads `network.<zone>.subnet` from the `systemConfig` table.
|
|
29
|
+
- **Host discovery ("which host serves module X?")** — `apps/celilo/src/cli/commands/module-where.ts` (`celilo module where <id> [--json]`, MCP `celilo_module_where`) — reads deployed hosts from `module_systems` via `getModuleSystems`, reconciles the live Proxmox node via `reconcilePlacement`, and adds a role-based reachability hint per zone. CI/build infra (builder VM, Forgejo runners) is out of scope (not in `module_systems`).
|
|
29
30
|
|
|
30
31
|
## Capability system (cross-module data & functions)
|
|
31
32
|
|
|
@@ -38,16 +39,53 @@ see `design/README.md`. Companion doc: [CELILO_CORE_MODULES.md](./CELILO_CORE_MO
|
|
|
38
39
|
|
|
39
40
|
| capability | impl | provider module(s) |
|
|
40
41
|
|---|---|---|
|
|
41
|
-
| `public_web` | `packages/capabilities/src/public-web.ts` (`createPublicWeb`) | caddy |
|
|
42
|
+
| `public_web` | `packages/capabilities/src/public-web.ts` (`createPublicWeb`). **Route lifecycle** is framework-owned at both ends, not per-module: removing a consumer runs `apps/celilo/src/services/web-route-cleanup.ts` (delete rows → reclaim `/srv/www/<slug>` → emit `routes_changed`) whether or not the module has an `on_uninstall`; deploying a provider runs `apps/celilo/src/services/public-web-republish.ts` (re-run every static consumer's `on_install`) so a provider rebuild refills the web roots it destroyed. | caddy |
|
|
42
43
|
| `idp` | `packages/capabilities/src/idp.ts` | authentik |
|
|
43
44
|
| `dns_registrar` | `packages/capabilities/src/dns-registrar.ts` (`registerHost`) | namecheap |
|
|
44
|
-
| `firewall` | `packages/capabilities/src/firewall.ts` (`exposeService`, `unexposeService`, `listExposedServices`) | greenwave, iptables |
|
|
45
|
+
| `firewall` | `packages/capabilities/src/firewall.ts` (`exposeService`, `unexposeService`, `listExposedServices`). **Converge model**: register into the shared-core port-forward registry → render the complete ruleset → apply atomically (see the firewall converge note below). | greenwave, iptables |
|
|
45
46
|
| `dns_internal` | `packages/capabilities/src/dns-internal.ts` | knot-unbound-internal, technitium |
|
|
46
47
|
| `dhcp_server` | `packages/capabilities/src/dhcp-server.ts` | greenwave |
|
|
47
48
|
| `source_forge` | `packages/capabilities/src/source-forge.ts` | forgejo |
|
|
48
49
|
| `registry_publish` | `packages/capabilities/src/registry-publish.ts` | celilo-registry |
|
|
49
50
|
| `cross_module_read` | `packages/capabilities/src/cross-module-read.ts` | framework (read other modules' capability data) |
|
|
50
51
|
|
|
52
|
+
## Remote-ops primitives (the SSH seam — modules never hand-build SSH)
|
|
53
|
+
|
|
54
|
+
Module hooks reach a remote box ONLY through these typed primitives
|
|
55
|
+
(openspec/changes/unified-management-no-ssh/proposal.md); a raw `ssh` string / `node:child_process` in
|
|
56
|
+
`modules/**/scripts/` is a defect. Impl: `packages/capabilities/src/remote.ts`
|
|
57
|
+
(exported from `packages/capabilities/src/index.ts`).
|
|
58
|
+
|
|
59
|
+
- **`remoteExec`** — the ONE ssh seam (`ssh root@<target> <cmd>`); everything else builds on it.
|
|
60
|
+
- **`probe`** — read-only health checks: `systemd` / `http` / `command`.
|
|
61
|
+
- **`serviceCtl`** — systemctl start/stop/restart/reload/enable/disable.
|
|
62
|
+
- **`runAppCommand` / `runAppCommandWithSecret`** — escape-hatch on-box command; the secret variant feeds the secret on **stdin** (`$SECRET`), never argv.
|
|
63
|
+
- **`streamBackup` / `streamRestore` / `fetchFile` / `pushFile`** — binary-safe streaming via local shell redirect/pipe.
|
|
64
|
+
- **`waitFor`** — predicate-poll combinator.
|
|
65
|
+
- **`applyRenderedConfig`** — converge: write rendered config → validate → apply → rollback (one round-trip). Used by caddy (Caddyfile), knot (views), iptables (ruleset).
|
|
66
|
+
- **`tailLog` / `grepLog`** — journald reads (regex / ignoreCase).
|
|
67
|
+
|
|
68
|
+
Target = `RemoteTarget` (`{ ipv4_address }`; `DeployedSystem` satisfies it). The
|
|
69
|
+
runner seam (`execRunner` real / `createMockRunner` for tests) lives in
|
|
70
|
+
`packages/capabilities/src/testing.ts`. Authoring guide: `MODULE_PRIMITIVES.md`
|
|
71
|
+
(ships in `@celilo/cli`).
|
|
72
|
+
|
|
73
|
+
## Firewall converge & port-forward registry
|
|
74
|
+
|
|
75
|
+
- **Port-forward registry (desired state)** — `port_forwards` DB table
|
|
76
|
+
(`apps/celilo/src/db/schema.ts`, migration `0014`) + the injectable
|
|
77
|
+
`PortForwardStore` (`apps/celilo/src/services/port-forwards.ts`,
|
|
78
|
+
`buildPortForwardStore`). Shared-core so any firewall provider reconciles
|
|
79
|
+
against one store; injected by the capability-loader into the firewall factory.
|
|
80
|
+
- **Ruleset renderer (pure)** — `modules/iptables/scripts/ruleset-renderer.ts`
|
|
81
|
+
(`renderRuleset`): registry + firewall state → a complete `iptables-restore`
|
|
82
|
+
file. Default-DROP FORWARD + established/related + egress + the coarse
|
|
83
|
+
zone-tier matrix (dmz→app, app→secure from `network.<zone>.subnet`) + per-service DNAT allows.
|
|
84
|
+
- **Converge** — `modules/iptables/scripts/firewall-functions.ts` (`converge`):
|
|
85
|
+
`exposeService`/`unexposeService` register into the store, then render + apply
|
|
86
|
+
atomically via `applyRenderedConfig` (`iptables-restore`). Replaces the old
|
|
87
|
+
per-rule `iptables -A`; the registry (not `iptables -L`) is the source of truth.
|
|
88
|
+
|
|
51
89
|
## Hooks & deploy
|
|
52
90
|
|
|
53
91
|
- **Hook executor / ABI** — `apps/celilo/src/hooks/executor.ts` (`invokeHook`, `executeHookScript`, `checkRequiredCapabilities`), types in `apps/celilo/src/hooks/types.ts` (`HookContext`, `HookDefinition`, `HookName`). Named-hook runner: `apps/celilo/src/hooks/run-named-hook.ts`. Manifest hook config: `apps/celilo/src/hooks/load-hook-config.ts`.
|
|
@@ -68,6 +106,10 @@ see `design/README.md`. Companion doc: [CELILO_CORE_MODULES.md](./CELILO_CORE_MO
|
|
|
68
106
|
|
|
69
107
|
- **Module packaging** — `apps/celilo/src/module/packaging/` — `build.ts` (`buildModule`), `extract.ts`, `checksum.ts`, `signature.ts` (`signChecksums`/`verifySignature`), `release-metadata.ts`, `audit.ts`.
|
|
70
108
|
- **Publish driver** — `scripts/publish.ts` shims to `apps/celilo/src/cli/commands/publish/` (workspace npm packages via `bun publish` + module registry; preflight stale-version/stale-manifest gates).
|
|
109
|
+
- **Registry publish-token admin (append-safe)** — `apps/celilo/src/cli/commands/registry-token.ts` — `celilo registry token add/rm <token>` read-modify-write the celilo-registry `publish_tokens` secret (newline-separated bootstrap/admin list). Avoids the `module secret set` full-overwrite that clobbered other holders. Runs on-mgr where the master key lives; runtime-minted scoped tokens are handled separately by the registry-server.
|
|
110
|
+
- **Contributor identity tokens (idp-issued, per-user)** — `apps/celilo/src/cli/commands/token.ts` — `celilo token obtain|list|revoke` mints/lists/revokes per-user API tokens via the `idp` capability (`create_token`/`list_tokens`/`revoke_token` on authentik). A module contributor authenticates publishes AS THEMSELVES (SECURE_MODULE_PUBLISH.md §6) — no admin/shared token on their machine; the token feeds `celilo author init`. Shown once at mint; the idp stores it hashed, celilo persists nothing. Runs on-mgr where the idp provider lives. Distinct from `registry token add/rm` (raw bootstrap list, different trust model).
|
|
111
|
+
- **Registry token verification (opaque + idp introspection)** — `packages/registry-server/src/auth.ts` (`TokenAuth` — opaque SHA-256 publish tokens, admin/per-package scope) + `packages/registry-server/src/introspection.ts` (`IntrospectionVerifier` — RFC 7662 verify-bridge, SECURE_MODULE_PUBLISH.md §5[D-A]). `authorizePackage()` in `server.ts` tries the opaque set first (unchanged), then, for a token unknown to it, `identify()`s it via the idp introspection endpoint using the registry's confidential OIDC client creds (`OIDC_INTROSPECTION_ENDPOINT`/`OIDC_CLIENT_ID`/`OIDC_CLIENT_SECRET`, provisioned on install — ce-7aa), reading `{active, sub, groups, exp}`. Fails CLOSED on any introspection error; never logs tokens/secrets. Instant revocation: revoke at the idp → next publish sees `active:false` → 401.
|
|
112
|
+
- **Registry module-owner table (hybrid group + owner authz — ce-1ch, D-C)** — `packages/registry-server/src/module-owner-store.ts` (`ModuleOwnerStore` — JSON-persisted `{moduleName, ownerSub, claimedAt, sourceGroup}`, `REGISTRY_OWNERS_FILE`/`dataDir/module-owners.json`). The verified `groups` claim gates *whether* an identity may publish (`REGISTRY_ADMIN_GROUP`→publish/reassign anything; `REGISTRY_PUBLISHER_GROUP`, default `celilo-authors`, configurable→claim+publish owned); the owner table gates *which names*. First-publish-claims: the first verified publisher of an unclaimed name owns it; a *different* publisher is then DENIED (confused-deputy defense — Author-A cannot publish Author-B's module). Admin HTTP endpoints `GET /api/v1/modules/owners`, `GET|POST /api/v1/modules/owners/{name}` (reassign). Operator front door: `celilo registry owner list|show|set` (`apps/celilo/src/cli/commands/registry-owner.ts`), admin token resolved from the local `publish_tokens` bootstrap list.
|
|
71
113
|
|
|
72
114
|
## Persistence
|
|
73
115
|
|
|
@@ -79,7 +121,7 @@ see `design/README.md`. Companion doc: [CELILO_CORE_MODULES.md](./CELILO_CORE_MO
|
|
|
79
121
|
|
|
80
122
|
## Remote API (drive the CLI over the wire)
|
|
81
123
|
|
|
82
|
-
Run any celilo command on celilo-mgr over SSH instead of screen-scraping `ssh <host> celilo …`. Typed, streamed, per-operation authz, mid-run interviews. Design: `
|
|
124
|
+
Run any celilo command on celilo-mgr over SSH instead of screen-scraping `ssh <host> celilo …`. Typed, streamed, per-operation authz, mid-run interviews. Design: `openspec/changes/replace-ssh-cli-api/proposal.md`.
|
|
83
125
|
|
|
84
126
|
- **Lightweight core (`@celilo/core`)** — `packages/core/src/` — the transport primitives lifted out of `@celilo/cli` so a consumer (e.g. the MCP server) can reach the wire without dragging Ink/React/drizzle/aws-sdk: `command-registry.ts` (`COMMANDS` + `CommandDef`/`ArgDef`/`FlagDef`), `protocol.ts`, `remote-client.ts`. Public surface: `packages/core/src/index.ts`.
|
|
85
127
|
- **Wire protocol** — `packages/core/src/protocol.ts` (`@celilo/core`) — versioned NDJSON tagged union (`command`/`progress`/`log`/`result`/`error`/`interview`/`answer`) + `translateOutputLine`.
|
|
@@ -90,7 +132,7 @@ Run any celilo command on celilo-mgr over SSH instead of screen-scraping `ssh <h
|
|
|
90
132
|
- **Mid-run interview bridge (`kind:daemon` responder)** — `apps/celilo/src/services/remote-responder.ts` — `startRemoteResponder` bridges bus `interview.required.*` ↔ wire.
|
|
91
133
|
- **Server provisioning** — the `celilo-bootstrap` deb (`packaging/celilo-bootstrap/scripts/postinst`) creates the non-root `celilo-api` landing account + sshd; membership in the `celilo` group + `/etc/sudoers.d/celilo` (`!use_pty`) gives api-serve DB access via the wrapper's sudo-drop.
|
|
92
134
|
- **Self-upgrade (apt)** — `celilo apt-upgrade` (`apps/celilo/src/cli/commands/apt-upgrade.ts`) upgrades the deb-installed `celilo`/`celilo-bootstrap` packages (`apt-get update` → `--only-upgrade install`) then spawns a fresh `celilo system migrate` (ISS-0100). It's the RW target behind the MCP's registry-derived `celilo_apt_upgrade` tool; the celilo user's two apt invocations are scoped-sudo'd by `/etc/sudoers.d/celilo-apt-upgrade`, shipped by `celilo-bootstrap`.
|
|
93
|
-
- **MCP service (`@celilo/mcp`)** — `packages/mcp/src/` — an operator-facing stdio MCP server (official `@modelcontextprotocol/sdk`, bin `celilo-mcp`) that drives a remote celilo server over the Remote API for an AI client. Two-item config (`config.ts`: `server` + `defaultUser`, env or `~/.config/celilo-mcp/config.json`). Dual-principal auth (`auth.ts`: `celilo-mcp auth setup` enrolls read-only `celilo-mcp-ro` + full `celilo-mcp-rw` ed25519 keypairs, prints the exact `celilo api grant` lines the operator runs server-side). Transport (`transport.ts`): reuses `@celilo/core` `runRemoteClient`, selecting the principal by `ssh -i <key>` and capturing structured output. Tool surface is generated LIVE from the server's command registry — `registry-fetch.ts` fetches `celilo commands --json` (+ `service list --json` for configured providers) over the RO principal on connect; `tools-from-registry.ts` (pure) projects that into one tool per runnable leaf, grouped by top-level command (`celilo_module_*`, `celilo_proxmox_*`, …), each with a Zod input schema from the leaf's args/flags and a read/write tag → RO/RW routing, plus a generic `celilo_run` escape hatch. Auto-detect hides provider-gated groups (e.g. `celilo_proxmox_*` until a Proxmox service is configured) and re-detects on a timer, emitting `notifications/tools/list_changed` when the surface changes. Coverage gate (`tests/coverage.test.ts`) asserts every registry leaf maps to a tool. Composite RO troubleshooting tools (`troubleshoot.ts` pure correlation + `troubleshoot-tools.ts` thin adapters): `celilo_assess_module <id>` and `celilo_fleet_status` correlate `celilo audit --json` (the drift backbone) with the `module list --json` roster into a per-module / fleet-wide verdict. Design: `
|
|
135
|
+
- **MCP service (`@celilo/mcp`)** — `packages/mcp/src/` — an operator-facing stdio MCP server (official `@modelcontextprotocol/sdk`, bin `celilo-mcp`) that drives a remote celilo server over the Remote API for an AI client. Two-item config (`config.ts`: `server` + `defaultUser`, env or `~/.config/celilo-mcp/config.json`). Dual-principal auth (`auth.ts`: `celilo-mcp auth setup` enrolls read-only `celilo-mcp-ro` + full `celilo-mcp-rw` ed25519 keypairs, prints the exact `celilo api grant` lines the operator runs server-side). Transport (`transport.ts`): reuses `@celilo/core` `runRemoteClient`, selecting the principal by `ssh -i <key>` and capturing structured output. Tool surface is generated LIVE from the server's command registry — `registry-fetch.ts` fetches `celilo commands --json` (+ `service list --json` for configured providers) over the RO principal on connect; `tools-from-registry.ts` (pure) projects that into one tool per runnable leaf, grouped by top-level command (`celilo_module_*`, `celilo_proxmox_*`, …), each with a Zod input schema from the leaf's args/flags and a read/write tag → RO/RW routing, plus a generic `celilo_run` escape hatch. Auto-detect hides provider-gated groups (e.g. `celilo_proxmox_*` until a Proxmox service is configured) and re-detects on a timer, emitting `notifications/tools/list_changed` when the surface changes. Coverage gate (`tests/coverage.test.ts`) asserts every registry leaf maps to a tool. Composite RO troubleshooting tools (`troubleshoot.ts` pure correlation + `troubleshoot-tools.ts` thin adapters): `celilo_assess_module <id>` and `celilo_fleet_status` correlate `celilo audit --json` (the drift backbone) with the `module list --json` roster into a per-module / fleet-wide verdict. Design: `openspec/changes/celilo-mcp-service/proposal.md`. (Distinct from the dev/ops `@celilo/mcp-server` below.)
|
|
94
136
|
|
|
95
137
|
## E2E simulation
|
|
96
138
|
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Module Primitives — the remote-ops toolkit for module hooks
|
|
2
|
+
|
|
3
|
+
**The one rule: modules never hand-build SSH.** A module hook that needs to touch
|
|
4
|
+
a deployed box does it through a typed primitive from `@celilo/capabilities` —
|
|
5
|
+
never a raw `ssh root@…` string, never `node:child_process`. SSH lives in exactly
|
|
6
|
+
one place (the `remoteExec` seam) and in Ansible's transport. A raw ssh/exec in
|
|
7
|
+
`modules/**/scripts/` is a defect (and a publish/lint gate flags it).
|
|
8
|
+
|
|
9
|
+
Why: hand-rolled ssh strings re-invent host-key handling, escaping, timeouts, and
|
|
10
|
+
secret-hygiene every time — and can't be unit-tested. The primitives give you one
|
|
11
|
+
correct, mockable implementation of each shape of remote work.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { probe, serviceCtl, applyRenderedConfig } from '@celilo/capabilities';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## The two things every primitive shares
|
|
20
|
+
|
|
21
|
+
**Target** — a `RemoteTarget` is just `{ ipv4_address: string }`. A
|
|
22
|
+
`DeployedSystem` (what your hook gets in `context.systems`) satisfies it directly;
|
|
23
|
+
for a machine addressed by config, pass `{ ipv4_address: ip }`.
|
|
24
|
+
|
|
25
|
+
**Runner** — every primitive takes an optional trailing `runner` (default
|
|
26
|
+
`execRunner`, which really shells out). In unit tests pass `createMockRunner([…])`
|
|
27
|
+
so you assert on the command strings/stdin without touching a box. A non-zero
|
|
28
|
+
remote exit comes back as `{ ok: false, … }` — **failure is data, never a throw** —
|
|
29
|
+
so your hook decides whether it's fatal (a deploy) or best-effort (a teardown).
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
// test
|
|
33
|
+
import { createMockRunner } from '@celilo/capabilities';
|
|
34
|
+
const { run, calls } = createMockRunner([
|
|
35
|
+
{ match: 'systemctl is-active caddy', result: { ok: true, stdout: 'active', stderr: '' } },
|
|
36
|
+
]);
|
|
37
|
+
expect(probe(sys, { kind: 'systemd', unit: 'caddy' }, run).healthy).toBe(true);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Which primitive when
|
|
43
|
+
|
|
44
|
+
| You want to… | Use | Not |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| Check a box is healthy (read-only) | `probe` | `ssh … systemctl is-active` |
|
|
47
|
+
| Start/stop/restart a systemd unit | `serviceCtl` | `ssh … systemctl restart` |
|
|
48
|
+
| Push a config whose content you computed from celilo's DB | `applyRenderedConfig` (converge) | `ssh … cat > file` |
|
|
49
|
+
| Wait for a condition to come true | `waitFor` | `sleep` / `setTimeout` |
|
|
50
|
+
| Back up / restore app state (binary) | `streamBackup` / `streamRestore` | `ssh … | base64` |
|
|
51
|
+
| Read a unit's journal | `tailLog` / `grepLog` | `ssh … journalctl` |
|
|
52
|
+
| Run a secret on a command line | `runAppCommandWithSecret` | secret on argv |
|
|
53
|
+
| Anything else with no capability/HTTP/converge path | `runAppCommand` (+ `// escape-hatch:`) | a raw ssh string |
|
|
54
|
+
|
|
55
|
+
**Reach for a capability method first.** If another module already provides the
|
|
56
|
+
thing (open a port → `firewall.exposeService`; register a route →
|
|
57
|
+
`public_web.registerReverseProxy`; mint a token → `idp`/`source_forge`), call
|
|
58
|
+
that, not a primitive. The primitives are for the module's *own* box.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## The primitives
|
|
63
|
+
|
|
64
|
+
### `probe(target, check, runner?, opts?) → { healthy, detail }`
|
|
65
|
+
Read-only health check. Never mutates. `check` is one of:
|
|
66
|
+
- `{ kind: 'systemd', unit }` — unit is `active`.
|
|
67
|
+
- `{ kind: 'http', url, expectStatus?, headers? }` — curl runs **on the box**, so
|
|
68
|
+
it doesn't depend on Caddy/public DNS. `expectStatus` defaults to 200; pass a
|
|
69
|
+
list (e.g. `[200, 308]`) or `headers: { Host: 'app.example.com' }` for a vhost.
|
|
70
|
+
- `{ kind: 'command', command, expectStdoutIncludes? }`.
|
|
71
|
+
```ts
|
|
72
|
+
probe(sys, { kind: 'http', url: 'http://localhost', headers: { Host: fqdn }, expectStatus: [200, 308] }, run);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### `serviceCtl(target, unit, action, runner?, opts?) → RunResult`
|
|
76
|
+
systemd control. `action ∈ start|stop|restart|reload|enable|disable`. Mutating —
|
|
77
|
+
for liveness use `probe`.
|
|
78
|
+
```ts
|
|
79
|
+
serviceCtl(sys, 'caddy', 'reload', run);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `applyRenderedConfig({ target, path, content, validate, apply, runner?, timeoutMs? }) → RunResult`
|
|
83
|
+
**The converge primitive.** For config whose *content* your hook computes at
|
|
84
|
+
deploy-time from celilo's DB (caddy's Caddyfile from `web_routes`, knot's views
|
|
85
|
+
from the DNS ledger, the firewall ruleset from the port-forward registry) —
|
|
86
|
+
content a static Ansible render can't know. One atomic on-box script, one SSH
|
|
87
|
+
round-trip: back up → write (`content` rides **stdin**, newline-safe) → `validate`
|
|
88
|
+
(rolled back + not activated if it fails) → `apply` (rolled back if it fails).
|
|
89
|
+
`{path}` is substituted into `validate`/`apply`.
|
|
90
|
+
```ts
|
|
91
|
+
applyRenderedConfig({
|
|
92
|
+
target: sys, path: '/etc/caddy/Caddyfile', content: rendered,
|
|
93
|
+
validate: 'caddy validate --config {path} --adapter caddyfile',
|
|
94
|
+
apply: 'caddy reload --config {path} --force', runner: run,
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### `waitFor(predicate, { attempts?, intervalMs?, onAttempt? }) → Promise<boolean>`
|
|
99
|
+
Poll an async predicate until true or exhausted (default 30 × 5s). A combinator —
|
|
100
|
+
it never touches the remote; wrap a `probe`/`runAppCommand` thunk. Use
|
|
101
|
+
`onAttempt` to emit a heartbeat so a long wait doesn't trip the hook idle-timeout.
|
|
102
|
+
Never `sleep`.
|
|
103
|
+
```ts
|
|
104
|
+
await waitFor(() => probe(sys, { kind: 'http', url }, run).healthy,
|
|
105
|
+
{ onAttempt: (n) => logger.info(`waiting… ${n * 5}s`) });
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `tailLog(q & { lines? }) / grepLog(q & { grep }) → RunResult`
|
|
109
|
+
Read a systemd unit's journal. `q = { target, unit, grep?, regex?, ignoreCase? }`.
|
|
110
|
+
`grepLog` requires `grep`; `regex: true` uses `grep -E`.
|
|
111
|
+
```ts
|
|
112
|
+
grepLog({ target: sys, unit: 'caddy', grep: 'certificate obtained', regex: true });
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `streamBackup / streamRestore / fetchFile / pushFile → RunResult`
|
|
116
|
+
Binary-safe streaming — bytes flow through the local shell redirect/pipe, never
|
|
117
|
+
Node's string capture, so tar/pg_dump/sqlite snapshots survive byte-for-byte.
|
|
118
|
+
```ts
|
|
119
|
+
streamBackup(sys, 'tar -cf - -C /var/lib/forgejo repositories', '/backup/repos.tar', run);
|
|
120
|
+
streamRestore(sys, '/restore/repos.tar', 'tar -xf - -C /var/lib/forgejo', run);
|
|
121
|
+
fetchFile(sys, '/etc/app/id', '/local/id', run); // pull
|
|
122
|
+
pushFile(sys, '/local/cert.pem', '/etc/app/cert.pem', run); // push
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `runAppCommandWithSecret(target, command, secret, runner?, opts?) → RunResult`
|
|
126
|
+
For a command needing a secret on its **command line** (token mint, credential
|
|
127
|
+
register) where the app has no HTTP API. The secret rides **stdin** into shell var
|
|
128
|
+
`SECRET` — never argv, so it's absent from logs/history/the recorded call.
|
|
129
|
+
Reference it as `"$SECRET"`. Escape-hatch discipline applies (comment required).
|
|
130
|
+
```ts
|
|
131
|
+
// escape-hatch: act_runner registers only via CLI; no HTTP API.
|
|
132
|
+
runAppCommandWithSecret(sys, 'act_runner register --token "$SECRET"', token, run);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `runAppCommand(target, command, runner?, opts?) → RunResult`
|
|
136
|
+
**The escape hatch.** Only when the work is neither a capability, HTTP, converge,
|
|
137
|
+
nor a service action (e.g. removing app state on teardown). **Every call site MUST
|
|
138
|
+
carry an inline `// escape-hatch: <why no capability/HTTP/converge path>` comment**
|
|
139
|
+
— the recurrence-gate lint flags an uncommented one.
|
|
140
|
+
```ts
|
|
141
|
+
// escape-hatch: purge app data dir on teardown; no capability owns this.
|
|
142
|
+
runAppCommand(sys, 'rm -rf /var/lib/app/*', run);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### `remoteExec(target, command, opts?, runner?) → RunResult`
|
|
146
|
+
The single SSH seam every other primitive builds on. You almost never call this
|
|
147
|
+
directly — prefer the specific primitive. `opts` is `{ input?, timeoutMs? }`.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Rules of thumb
|
|
152
|
+
|
|
153
|
+
- **Failure is data.** Check `.ok`; decide fatal vs best-effort. Don't wrap in
|
|
154
|
+
try/catch expecting throws.
|
|
155
|
+
- **Unit-test with `createMockRunner`.** Assert on `calls[].cmd` and `calls[].input`.
|
|
156
|
+
Controlled identifiers (systemd unit names) are passed unescaped; the outer
|
|
157
|
+
`remoteExec` escapes the whole command, so assert on bare substrings.
|
|
158
|
+
- **Secrets never on argv** — `runAppCommandWithSecret`, or a capability method.
|
|
159
|
+
- **No `sleep`** — `waitFor` on the real condition.
|
|
160
|
+
- **Computed config → `applyRenderedConfig`**, not `ssh cat >`. It validates and
|
|
161
|
+
rolls back; a broken config never goes live.
|
|
162
|
+
|
|
163
|
+
See also: `CELILO_SUBSYSTEMS.md` (the primitive impls + the firewall converge),
|
|
164
|
+
`openspec/changes/unified-management-no-ssh/proposal.md` (the design), `reference/MODULE_DEVELOPMENT_GUIDE.md`.
|
package/README.md
CHANGED
|
@@ -247,7 +247,7 @@ celilo ipam ip list-reservations
|
|
|
247
247
|
- ✅ **Bridge** - From `network.{zone}.bridge` system config
|
|
248
248
|
- ✅ **VM Resources** - cores, memory, disk, storage from manifest defaults
|
|
249
249
|
|
|
250
|
-
See [../../
|
|
250
|
+
See [../../reference/MODULE_DEVELOPMENT_GUIDE.md](../../reference/MODULE_DEVELOPMENT_GUIDE.md) for complete zero-config documentation.
|
|
251
251
|
|
|
252
252
|
### Database Location
|
|
253
253
|
|
|
@@ -262,7 +262,7 @@ See [../../design/MODULE_DEVELOPMENT_GUIDE.md](../../design/MODULE_DEVELOPMENT_G
|
|
|
262
262
|
|
|
263
263
|
### Testing Strategy
|
|
264
264
|
|
|
265
|
-
**Three test tiers** (see [TESTING_STRATEGY.md](../../
|
|
265
|
+
**Three test tiers** (see [TESTING_STRATEGY.md](../../reference/TESTING_STRATEGY.md) for details):
|
|
266
266
|
|
|
267
267
|
**Tier 1: Unit Tests** (< 1 second)
|
|
268
268
|
```bash
|
|
@@ -1539,9 +1539,9 @@ When debugging issues, check:
|
|
|
1539
1539
|
|
|
1540
1540
|
**Documentation**:
|
|
1541
1541
|
- [CLAUDE.md](../../CLAUDE.md) - Engineering standards
|
|
1542
|
-
- [TESTING_STRATEGY.md](../../
|
|
1543
|
-
- [IDENTIFIER_NAMING_CONVENTIONS.md](../../
|
|
1544
|
-
- [TEMPLATE_VARIABLE_SYNTAX.md](../../
|
|
1542
|
+
- [TESTING_STRATEGY.md](../../reference/TESTING_STRATEGY.md) - Testing approach
|
|
1543
|
+
- [IDENTIFIER_NAMING_CONVENTIONS.md](../../reference/IDENTIFIER_NAMING_CONVENTIONS.md) - Naming rules
|
|
1544
|
+
- [TEMPLATE_VARIABLE_SYNTAX.md](../../reference/TEMPLATE_VARIABLE_SYNTAX.md) - Variable syntax
|
|
1545
1545
|
|
|
1546
1546
|
**Debugging Tools**:
|
|
1547
1547
|
- Drizzle Studio: `bun run db:studio`
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
CREATE TABLE `port_forwards` (
|
|
2
|
+
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
3
|
+
`firewall_ip` text NOT NULL,
|
|
4
|
+
`internal_ip` text NOT NULL,
|
|
5
|
+
`port` integer NOT NULL,
|
|
6
|
+
`protocol` text NOT NULL,
|
|
7
|
+
`ingress_ip` text,
|
|
8
|
+
`description` text DEFAULT '' NOT NULL,
|
|
9
|
+
`created_at` integer DEFAULT (unixepoch()) NOT NULL
|
|
10
|
+
);
|
|
11
|
+
--> statement-breakpoint
|
|
12
|
+
CREATE UNIQUE INDEX `port_forwards_unique_idx` ON `port_forwards` (`firewall_ip`,`internal_ip`,`port`,`protocol`,`ingress_ip`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celilo/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Celilo — home lab orchestration CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"tsconfig.json",
|
|
15
15
|
"AGENTS.md",
|
|
16
16
|
"CELILO_SUBSYSTEMS.md",
|
|
17
|
-
"CELILO_CORE_MODULES.md"
|
|
17
|
+
"CELILO_CORE_MODULES.md",
|
|
18
|
+
"MODULE_PRIMITIVES.md"
|
|
18
19
|
],
|
|
19
20
|
"keywords": [
|
|
20
21
|
"celilo",
|
|
@@ -55,10 +56,10 @@
|
|
|
55
56
|
},
|
|
56
57
|
"dependencies": {
|
|
57
58
|
"@aws-sdk/client-s3": "^3.1024.0",
|
|
58
|
-
"@celilo/capabilities": "
|
|
59
|
-
"@celilo/cli-display": "
|
|
60
|
-
"@celilo/core": "
|
|
61
|
-
"@celilo/event-bus": "
|
|
59
|
+
"@celilo/capabilities": "workspace:^",
|
|
60
|
+
"@celilo/cli-display": "workspace:^",
|
|
61
|
+
"@celilo/core": "workspace:^",
|
|
62
|
+
"@celilo/event-bus": "workspace:^",
|
|
62
63
|
"@clack/prompts": "^1.1.0",
|
|
63
64
|
"ajv": "^8.18.0",
|
|
64
65
|
"drizzle-orm": "^0.36.4",
|
|
@@ -79,6 +80,5 @@
|
|
|
79
80
|
"ink-testing-library": "^4.0.0",
|
|
80
81
|
"typescript": "^5.9.3",
|
|
81
82
|
"zod-to-json-schema": "^3.25.2"
|
|
82
|
-
}
|
|
83
|
-
"gitHead": "8fe2dff30ef6a59a0aed48e5501403e43be8429e"
|
|
83
|
+
}
|
|
84
84
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"network.dmz.subnet": {
|
|
14
14
|
"type": "string",
|
|
15
15
|
"pattern": "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}/\\d{1,2}$",
|
|
16
|
-
"description": "DMZ subnet CIDR (not defaulted — appears only when a firewall module provides this zone; see
|
|
16
|
+
"description": "DMZ subnet CIDR (not defaulted — appears only when a firewall module provides this zone; see openspec/specs/progressive-zone-disclosure/spec.md)"
|
|
17
17
|
},
|
|
18
18
|
"network.dmz.gateway": {
|
|
19
19
|
"type": "string",
|
|
@@ -97,7 +97,7 @@ describe('generateHostsIni', () => {
|
|
|
97
97
|
|
|
98
98
|
test('emits ansible_connection=local for a local machine (no SSH host/key)', () => {
|
|
99
99
|
// The management box deploying to itself uses Ansible's local
|
|
100
|
-
// connection (
|
|
100
|
+
// connection (openspec/specs/bootstrap-meta-package/spec.md).
|
|
101
101
|
const hosts: InventoryHost[] = [
|
|
102
102
|
{
|
|
103
103
|
hostname: 'celilo-mgr',
|
package/src/ansible/inventory.ts
CHANGED
|
@@ -30,7 +30,7 @@ export interface InventoryHost {
|
|
|
30
30
|
/**
|
|
31
31
|
* The management box deploying to itself (127.0.0.1) uses Ansible's
|
|
32
32
|
* local connection — no SSH, no ansible_host/key. See
|
|
33
|
-
*
|
|
33
|
+
* openspec/specs/bootstrap-meta-package/spec.md.
|
|
34
34
|
*/
|
|
35
35
|
local?: boolean;
|
|
36
36
|
}
|
|
@@ -201,7 +201,7 @@ export function buildHostVars(moduleId: string, db: DbClient): Record<string, un
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
// The host's IP is sourced from the deployed-systems model now, not a
|
|
204
|
-
// `target_ip` config row (
|
|
204
|
+
// `target_ip` config row (openspec/specs/module-systems-addressing/spec.md). Emit it as the
|
|
205
205
|
// `target_ip` host_var so Ansible templates that reference `{{ target_ip }}`
|
|
206
206
|
// (zone files, DNS record tasks) keep working. Single-system modules have one.
|
|
207
207
|
const recordedSystems = getModuleSystems(moduleId, db);
|
|
@@ -266,7 +266,7 @@ export function extractInventoryHost(moduleId: string, db: DbClient): InventoryH
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
// The host's IP now comes from the deployed-systems model, not a `target_ip`
|
|
269
|
-
// config row (
|
|
269
|
+
// config row (openspec/specs/module-systems-addressing/spec.md). Inject it so the
|
|
270
270
|
// ansible_host derivation below and the Ansible `{{ target_ip }}` host_var
|
|
271
271
|
// (zone files, DNS record tasks) resolve. Single-system modules have one.
|
|
272
272
|
const recordedSystems = getModuleSystems(moduleId, db);
|