@homeflare/alchemy 0.10.1 → 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/README.md +3 -3
- package/dist/caddy/admin-guard.d.ts +1 -1
- package/dist/caddy/index.js.map +4 -4
- package/dist/cloudflare/client.d.ts +1 -1
- package/dist/cloudflare/index.js.map +2 -2
- package/dist/cloudflare/lock-rules.d.ts +1 -1
- package/dist/forgejo/branch-protection-form.d.ts +1 -1
- package/dist/forgejo/branch-protection.d.ts +1 -1
- package/dist/forgejo/index.js.map +10 -10
- package/dist/forgejo/org-actions-secrets.d.ts +1 -1
- package/dist/forgejo/org-label.d.ts +1 -1
- package/dist/forgejo/org-team.d.ts +1 -1
- package/dist/forgejo/repo-webhook.d.ts +1 -1
- package/dist/forgejo/repository.d.ts +2 -2
- package/dist/forgejo/repository.d.ts.map +1 -1
- package/dist/forgejo/resource.d.ts +2 -2
- package/dist/forgejo/team-member.d.ts +1 -1
- package/dist/launchd/index.js.map +2 -2
- package/dist/openbao/auth-role.d.ts +1 -1
- package/dist/openbao/bao-http.d.ts +4 -4
- package/dist/openbao/cloudflare-permission-groups.d.ts +1 -1
- package/dist/openbao/cloudflare-role-form.d.ts +2 -2
- package/dist/openbao/cloudflare-role-form.d.ts.map +1 -1
- package/dist/openbao/cloudflare-roles-expand.d.ts +1 -1
- package/dist/openbao/digest.d.ts +1 -1
- package/dist/openbao/forgejo-bootstrap-parse.d.ts +2 -2
- package/dist/openbao/forgejo-bootstrap-parse.d.ts.map +1 -1
- package/dist/openbao/forgejo-bootstrap.d.ts +1 -1
- package/dist/openbao/index.js +10 -8
- package/dist/openbao/index.js.map +18 -18
- package/dist/openbao/mount.d.ts +1 -1
- package/dist/openbao/pki-role.d.ts +1 -1
- package/dist/openbao/policy.d.ts +2 -2
- package/dist/openbao/proxmox-role-form.d.ts +2 -2
- package/dist/openbao/proxmox-role-form.d.ts.map +1 -1
- package/dist/openbao/proxmox-role.d.ts +10 -10
- package/dist/openbao/ssh-role.d.ts +3 -3
- package/dist/ownership/adopting.d.ts +34 -0
- package/dist/ownership/adopting.d.ts.map +1 -0
- package/dist/ownership/probe.d.ts +15 -5
- package/dist/ownership/probe.d.ts.map +1 -1
- package/dist/ownership/rows.d.ts +12 -0
- package/dist/ownership/rows.d.ts.map +1 -1
- package/dist/ownership/store-fixture.d.ts +10 -0
- package/dist/ownership/store-fixture.d.ts.map +1 -0
- package/dist/proxmox/acl.d.ts +2 -2
- package/dist/proxmox/acl.d.ts.map +1 -1
- package/dist/proxmox/api-token-form.d.ts +2 -2
- package/dist/proxmox/api-token.d.ts +11 -11
- package/dist/proxmox/ceph-daemon-form.d.ts +3 -3
- package/dist/proxmox/ceph-daemon.d.ts +19 -19
- package/dist/proxmox/ceph-daemon.d.ts.map +1 -1
- package/dist/proxmox/ceph-flag.d.ts +5 -5
- package/dist/proxmox/ceph-fs-wire.d.ts +5 -5
- package/dist/proxmox/ceph-fs.d.ts +16 -16
- package/dist/proxmox/ceph-osd-tree.d.ts +16 -16
- package/dist/proxmox/ceph-osd.d.ts +5 -5
- package/dist/proxmox/ceph-pool-form.d.ts +13 -13
- package/dist/proxmox/ceph-pool-form.d.ts.map +1 -1
- package/dist/proxmox/ceph-pool.d.ts +3 -3
- package/dist/proxmox/client.d.ts +2 -2
- package/dist/proxmox/credentials.d.ts +8 -8
- package/dist/proxmox/credentials.d.ts.map +1 -1
- package/dist/proxmox/fake-pve-lxc.d.ts +8 -0
- package/dist/proxmox/fake-pve-lxc.d.ts.map +1 -1
- package/dist/proxmox/firewall-alias-form.d.ts +1 -1
- package/dist/proxmox/firewall-alias.d.ts +1 -1
- package/dist/proxmox/generated/pbs.d.ts +1 -1
- package/dist/proxmox/generated/pve.d.ts +1 -1
- package/dist/proxmox/group.d.ts +8 -8
- package/dist/proxmox/ha-resource.d.ts +3 -2
- package/dist/proxmox/ha-resource.d.ts.map +1 -1
- package/dist/proxmox/ha-rule-form.d.ts +5 -5
- package/dist/proxmox/ha-rule.d.ts +3 -3
- package/dist/proxmox/index.d.ts +6 -0
- package/dist/proxmox/index.d.ts.map +1 -1
- package/dist/proxmox/index.js +465 -217
- package/dist/proxmox/index.js.map +63 -58
- package/dist/proxmox/lease-cache.d.ts +4 -4
- package/dist/proxmox/lxc-adoption.d.ts +32 -0
- package/dist/proxmox/lxc-adoption.d.ts.map +1 -0
- package/dist/proxmox/lxc-harness.d.ts +12 -0
- package/dist/proxmox/lxc-harness.d.ts.map +1 -1
- package/dist/proxmox/lxc-props.d.ts +2 -2
- package/dist/proxmox/lxc.d.ts +8 -8
- package/dist/proxmox/lxc.d.ts.map +1 -1
- package/dist/proxmox/members.d.ts +3 -3
- package/dist/proxmox/network-apply-read.d.ts +5 -5
- package/dist/proxmox/network-apply.d.ts +9 -9
- package/dist/proxmox/node-network-form.d.ts +6 -6
- package/dist/proxmox/node-network.d.ts +17 -17
- package/dist/proxmox/node-network.d.ts.map +1 -1
- package/dist/proxmox/notification-target.d.ts +3 -2
- package/dist/proxmox/notification-target.d.ts.map +1 -1
- package/dist/proxmox/pbs-datastore-guard.d.ts +1 -1
- package/dist/proxmox/pbs-datastore.d.ts +10 -10
- package/dist/proxmox/pbs-prune-job.d.ts +2 -2
- package/dist/proxmox/pbs-sync-job.d.ts +4 -4
- package/dist/proxmox/pbs-verify-job.d.ts +1 -1
- package/dist/proxmox/pool.d.ts +4 -4
- package/dist/proxmox/provision-baseline.d.ts +74 -0
- package/dist/proxmox/provision-baseline.d.ts.map +1 -0
- package/dist/proxmox/provision-bootstrap.d.ts +33 -0
- package/dist/proxmox/provision-bootstrap.d.ts.map +1 -0
- package/dist/proxmox/provision-cli-fake.d.ts +38 -0
- package/dist/proxmox/provision-cli-fake.d.ts.map +1 -0
- package/dist/proxmox/provision-declare.d.ts +25 -0
- package/dist/proxmox/provision-declare.d.ts.map +1 -0
- package/dist/proxmox/replication-job.d.ts +9 -8
- package/dist/proxmox/replication-job.d.ts.map +1 -1
- package/dist/proxmox/resource.d.ts +2 -2
- package/dist/proxmox/resource.d.ts.map +1 -1
- package/dist/proxmox/sdn-apply-read.d.ts +7 -7
- package/dist/proxmox/sdn-apply.d.ts +1 -1
- package/dist/proxmox/sdn-subnet-form.d.ts +1 -1
- package/dist/proxmox/sdn-subnet.d.ts +5 -5
- package/dist/proxmox/sdn-vnet.d.ts +5 -6
- package/dist/proxmox/sdn-vnet.d.ts.map +1 -1
- package/dist/proxmox/storage.d.ts +4 -3
- package/dist/proxmox/storage.d.ts.map +1 -1
- package/dist/proxmox/user.d.ts +3 -2
- package/dist/proxmox/user.d.ts.map +1 -1
- package/dist/proxmox/values.d.ts +1 -1
- package/dist/proxmox/zfs-pool-write.d.ts +1 -1
- package/dist/proxmox/zfs-pool.d.ts +5 -5
- package/dist/talos/credentials.d.ts +1 -1
- package/dist/talos/credentials.d.ts.map +1 -1
- package/dist/talos/index.js.map +1 -1
- package/docs/adopt-verify.md +21 -5
- package/docs/adopted-deploys.md +5 -3
- package/docs/ownership.md +27 -2
- package/docs/provision-baseline.md +106 -0
- package/docs/proxmox-lxc-adopt.md +83 -0
- package/docs/proxmox-lxc.md +12 -49
- package/docs/proxmox.md +8 -0
- package/package.json +1 -1
- package/src/caddy/admin-guard.ts +1 -1
- package/src/caddy/local-admin.ts +1 -1
- package/src/cloudflare/client.ts +1 -1
- package/src/cloudflare/lock-rules.ts +1 -1
- package/src/forgejo/branch-protection-form.ts +1 -1
- package/src/forgejo/branch-protection.ts +1 -1
- package/src/forgejo/client.ts +1 -1
- package/src/forgejo/org-actions-secrets.ts +1 -1
- package/src/forgejo/org-label.ts +1 -1
- package/src/forgejo/org-team.ts +1 -1
- package/src/forgejo/repo-webhook.ts +1 -1
- package/src/forgejo/repository.ts +2 -2
- package/src/forgejo/resource.ts +2 -2
- package/src/forgejo/team-member.ts +1 -1
- package/src/openbao/README.md +4 -4
- package/src/openbao/auth-role.ts +1 -1
- package/src/openbao/bao-http.test.ts +2 -2
- package/src/openbao/bao-http.ts +6 -6
- package/src/openbao/bao-status.test.ts +1 -1
- package/src/openbao/bao-status.ts +1 -1
- package/src/openbao/cloudflare-group-scope.test.ts +1 -1
- package/src/openbao/cloudflare-permission-groups.test.ts +1 -1
- package/src/openbao/cloudflare-permission-groups.ts +3 -3
- package/src/openbao/cloudflare-policy.ts +1 -1
- package/src/openbao/cloudflare-role-form.ts +2 -2
- package/src/openbao/cloudflare-roles-expand.ts +1 -1
- package/src/openbao/digest.ts +1 -1
- package/src/openbao/forgejo-bootstrap-parse.test.ts +3 -3
- package/src/openbao/forgejo-bootstrap-parse.ts +2 -2
- package/src/openbao/forgejo-bootstrap.ts +4 -4
- package/src/openbao/kubernetes-role-form.test.ts +5 -5
- package/src/openbao/mount.ts +1 -1
- package/src/openbao/pki-role.ts +1 -1
- package/src/openbao/policy-wire.test.ts +3 -3
- package/src/openbao/policy.ts +2 -2
- package/src/openbao/proxmox-role-form.ts +5 -5
- package/src/openbao/proxmox-role.ts +11 -11
- package/src/openbao/ssh-role.ts +3 -3
- package/src/ownership/adopting.test.ts +63 -0
- package/src/ownership/adopting.ts +46 -0
- package/src/ownership/ownership.test.ts +1 -34
- package/src/ownership/probe.ts +27 -14
- package/src/ownership/rows.ts +8 -2
- package/src/ownership/store-fixture.ts +50 -0
- package/src/proxmox/acl.ts +4 -4
- package/src/proxmox/adopt-noop.test.ts +6 -6
- package/src/proxmox/api-token-form.ts +4 -4
- package/src/proxmox/api-token.ts +13 -13
- package/src/proxmox/ceph-daemon-form.ts +3 -3
- package/src/proxmox/ceph-daemon.ts +20 -20
- package/src/proxmox/ceph-flag.ts +7 -7
- package/src/proxmox/ceph-fs-wire.ts +7 -7
- package/src/proxmox/ceph-fs.ts +18 -18
- package/src/proxmox/ceph-osd-tree.ts +16 -16
- package/src/proxmox/ceph-osd.ts +6 -6
- package/src/proxmox/ceph-pool-adopt.test.ts +14 -14
- package/src/proxmox/ceph-pool-form.ts +13 -13
- package/src/proxmox/ceph-pool.ts +6 -6
- package/src/proxmox/client.test.ts +3 -3
- package/src/proxmox/client.ts +3 -3
- package/src/proxmox/credentials.test.ts +6 -8
- package/src/proxmox/credentials.ts +8 -8
- package/src/proxmox/fake-pve-lxc.ts +13 -1
- package/src/proxmox/firewall-alias-form.ts +1 -1
- package/src/proxmox/firewall-alias.ts +1 -1
- package/src/proxmox/generated/pbs.ts +1 -1
- package/src/proxmox/generated/pve.ts +1 -1
- package/src/proxmox/group.ts +10 -10
- package/src/proxmox/ha-resource.ts +3 -2
- package/src/proxmox/ha-rule-form.ts +5 -5
- package/src/proxmox/ha-rule.ts +3 -3
- package/src/proxmox/index.test.ts +7 -0
- package/src/proxmox/index.ts +14 -0
- package/src/proxmox/lease-cache.test.ts +19 -19
- package/src/proxmox/lease-cache.ts +4 -4
- package/src/proxmox/lxc-adopt.test.ts +16 -23
- package/src/proxmox/lxc-adoption.ts +50 -0
- package/src/proxmox/lxc-create-form.ts +1 -1
- package/src/proxmox/lxc-guards.test.ts +10 -17
- package/src/proxmox/lxc-harness.ts +27 -4
- package/src/proxmox/lxc-judge.test.ts +5 -5
- package/src/proxmox/lxc-lifecycle.ts +1 -1
- package/src/proxmox/lxc-ownership.test.ts +19 -14
- package/src/proxmox/lxc-props.ts +2 -2
- package/src/proxmox/lxc-read.test.ts +3 -3
- package/src/proxmox/lxc-strict-adopt.test.ts +104 -0
- package/src/proxmox/lxc-volume.test.ts +9 -9
- package/src/proxmox/lxc-volume.ts +1 -1
- package/src/proxmox/lxc-wire.ts +1 -1
- package/src/proxmox/lxc.ts +19 -15
- package/src/proxmox/members.test.ts +1 -1
- package/src/proxmox/members.ts +3 -3
- package/src/proxmox/metric-server-otel.ts +2 -2
- package/src/proxmox/network-apply-read.ts +10 -10
- package/src/proxmox/network-apply.ts +10 -10
- package/src/proxmox/node-network-form.ts +7 -7
- package/src/proxmox/node-network.ts +18 -18
- package/src/proxmox/notification-target.ts +3 -2
- package/src/proxmox/pbs-datastore-guard.ts +1 -1
- package/src/proxmox/pbs-datastore.ts +10 -10
- package/src/proxmox/pbs-prune-job.ts +2 -2
- package/src/proxmox/pbs-sync-job.ts +4 -4
- package/src/proxmox/pbs-verify-job.ts +1 -1
- package/src/proxmox/pool.test.ts +2 -2
- package/src/proxmox/pool.ts +4 -4
- package/src/proxmox/provision-baseline.test.ts +87 -0
- package/src/proxmox/provision-baseline.ts +162 -0
- package/src/proxmox/provision-bootstrap.test.ts +132 -0
- package/src/proxmox/provision-bootstrap.ts +135 -0
- package/src/proxmox/provision-cli-fake.ts +217 -0
- package/src/proxmox/provision-declare.test.ts +111 -0
- package/src/proxmox/provision-declare.ts +103 -0
- package/src/proxmox/replication-job.ts +9 -8
- package/src/proxmox/resource.ts +3 -3
- package/src/proxmox/sdn-apply-read.test.ts +16 -16
- package/src/proxmox/sdn-apply-read.ts +7 -7
- package/src/proxmox/sdn-apply.ts +3 -3
- package/src/proxmox/sdn-subnet-form.ts +1 -1
- package/src/proxmox/sdn-subnet.ts +5 -5
- package/src/proxmox/sdn-vnet.ts +5 -6
- package/src/proxmox/storage.ts +4 -3
- package/src/proxmox/user.ts +3 -2
- package/src/proxmox/values.test.ts +2 -2
- package/src/proxmox/values.ts +1 -1
- package/src/proxmox/zfs-pool-write.ts +1 -1
- package/src/proxmox/zfs-pool.ts +6 -6
- package/src/talos/credentials.ts +3 -3
- package/src/talos/values.test.ts +9 -9
- package/src/verify/cli.test.ts +1 -1
package/docs/adopt-verify.md
CHANGED
|
@@ -9,8 +9,8 @@ bunx --bun hf-adopt-verify --config alchemy.run.ts --stage live # rows
|
|
|
9
9
|
bunx --bun hf-adopt-verify --config alchemy.pbs.ts --stage live --all # every row + deletions
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Wrap it in the same credential lane the deploy uses (
|
|
13
|
-
|
|
12
|
+
Wrap it in the same credential lane the deploy uses (the wrapper that mints
|
|
13
|
+
its OpenBao token): a lane that cannot read an object sees it as absent, and the verifier
|
|
14
14
|
then reports `create` for something that exists.
|
|
15
15
|
|
|
16
16
|
## Why a plan line is not enough
|
|
@@ -24,8 +24,8 @@ Measured in alchemy `2.0.0-beta.79`:
|
|
|
24
24
|
- So `alchemy plan` prints `(+) adopted` for a matching object **and** for a drifting one, and
|
|
25
25
|
the deploy reconciles both. Whether that writes is up to each family's `reconcile`.
|
|
26
26
|
|
|
27
|
-
Measured on
|
|
28
|
-
|
|
27
|
+
Measured on C1, `pvesh get /cluster/tasks`, 2026-09-21: six `cephsetpool` tasks by
|
|
28
|
+
the provision user, one per pool, on 2026-09-13 and again on 2026-09-20 — the deploys that
|
|
29
29
|
adopted the three Ceph pools. `Proxmox.CephPool` sent `setpool` whenever the pool existed,
|
|
30
30
|
whatever its diff said. It was the one PVE/PBS family that wrote on a no-op adoption, and it is
|
|
31
31
|
fixed. [adopted-deploys.md](./adopted-deploys.md) shows what every family does.
|
|
@@ -37,10 +37,26 @@ fixed. [adopted-deploys.md](./adopted-deploys.md) shows what every family does.
|
|
|
37
37
|
| `alchemy plan` (CLI) | the forced action only; `--detailed` is declared YAML, no JSON |
|
|
38
38
|
| `Plan.describePlan` | serializable rows, again only the forced action |
|
|
39
39
|
| `forceUpdateAfterAdoption` | a local variable; no log, no warning, no field on the node |
|
|
40
|
-
| `alchemy drift` | rows **with** state only,
|
|
40
|
+
| `alchemy drift` | rows **with** state only, against the last deploy; exits `0` |
|
|
41
41
|
| `deploy --adopt --dry-run` | the adoption plan (`plan` has no `--adopt`), same forced action |
|
|
42
42
|
| **the provider service** | the raw `read` / `diff` answer, per FQN — used |
|
|
43
43
|
|
|
44
|
+
`alchemy drift` has no dry run to gate on (beta.79, `Cli/commands/drift.ts`, `Drift.ts`). Its
|
|
45
|
+
flags are `--repair`, `--config`, `--env-file`, `--stage` and `--profile`: no `--dry-run`, and no
|
|
46
|
+
`--yes`, although [its page](https://alchemy.run/cli/drift) lists one (measured 2026-09-21:
|
|
47
|
+
`Unrecognized flag: --yes in command alchemy drift`, exit `1`). Without `--repair`, a terminal
|
|
48
|
+
run asks Repair or Cancel (default Cancel). A non-interactive run (no TTY, `CI`, `--no-input`,
|
|
49
|
+
`ALCHEMY_PLAIN=1` or `ALCHEMY_NO_TUI=1`, or an agent such as Claude Code) prints the repair plan
|
|
50
|
+
and exits `0` even when something drifted. It skips a row whose provider has no `read`, or whose
|
|
51
|
+
status is not `created` / `updated`. `--repair` restores the props saved at the last deploy, not
|
|
52
|
+
what the code declares now, and recreates a missing object. `--all` (below) asks each
|
|
53
|
+
provider's `diff` about the declaration, as a deploy does, and reads every row again.
|
|
54
|
+
|
|
55
|
+
⛔ **Repair is a write outside the deploy gate.** Choosing Repair, or passing `--repair` (which
|
|
56
|
+
never prompts), calls each drifted row's `reconcile` directly (`Drift.ts`): no `hf-adopt-verify`,
|
|
57
|
+
no approved plan of the declaration. A HomeFlare gate never runs it. Deploy the declaration
|
|
58
|
+
through the gate instead.
|
|
59
|
+
|
|
44
60
|
The engine finds every provider in the Effect context by resource type. The verifier opens the
|
|
45
61
|
stack the way `alchemy plan` does (`Alchemist.open`). It swaps each provider for a watched copy
|
|
46
62
|
(`src/verify/spy.ts`) and runs `Plan.make` itself, so output resolution, state lookup, renames,
|
package/docs/adopted-deploys.md
CHANGED
|
@@ -57,8 +57,10 @@ lease (`readRole`, resource.ts). That is still a read.
|
|
|
57
57
|
² The PVE notification endpoints: `smtp` (the mail target), `sendmail`, `gotify`, `webhook`. The
|
|
58
58
|
secrets are never props (notification-target.ts), so no write is ever needed to "reset" them.
|
|
59
59
|
³ Rewritten 2026-09-21 (PR 80, [proxmox-lxc.md](./proxmox-lxc.md)): its own reconcile, and its
|
|
60
|
-
probe answers `Unowned`, so the deploy needs `--adopt`.
|
|
61
|
-
|
|
60
|
+
probe answers `Unowned`, so the deploy needs `--adopt`. An adoption that would write fails the
|
|
61
|
+
plan instead, naming the keys ([proxmox-lxc-adopt.md](./proxmox-lxc-adopt.md)), so only the
|
|
62
|
+
no-write row applies. Pinned by `src/proxmox/lxc-adopt.test.ts` ("deploys with no write") and
|
|
63
|
+
`src/proxmox/lxc-strict-adopt.test.ts`.
|
|
62
64
|
⁴ `updateBody` sends `target_size_ratio`, but `matches` does not compare it (float equality). A
|
|
63
65
|
declared ratio that differs from the live one is never written on a no-op adoption and never
|
|
64
66
|
reported as a diff.
|
|
@@ -69,7 +71,7 @@ reported as a diff.
|
|
|
69
71
|
`pg_autoscale_mode`, `crush_rule` (and any declared hints) to `nodes/{node}/ceph/pool/{name}`.
|
|
70
72
|
That PUT forks a `cephsetpool` worker under the provision token. PVE's `set_pool` skips each
|
|
71
73
|
setting whose value is unchanged (Tools.pm:293-300), so a matching pool was not re-tuned. It was
|
|
72
|
-
still a provision-lease write and a task on the node every time the pool was adopted.
|
|
74
|
+
still a provision-lease write and a task on the node every time the pool was adopted. C1's task
|
|
73
75
|
list shows six such tasks (2026-09-13 and 2026-09-20), one per declared pool per deploy.
|
|
74
76
|
|
|
75
77
|
## How it is pinned
|
package/docs/ownership.md
CHANGED
|
@@ -4,7 +4,8 @@ A live object this stack holds **no state for** is never taken over silently. Th
|
|
|
4
4
|
`Bao.*` family, `HostFile`, `LaunchdJob`, `CaddyConfig` and `ProxmoxLxc`, and it holds even when the
|
|
5
5
|
live object is **identical** to the declaration: identical is not ours. Another stack, a person or an old
|
|
6
6
|
script put it there, and once state claims it, a delete under `RemovalPolicy.destroy()` removes it
|
|
7
|
-
from its real owner. (Decided 2026-09-21; the Bao families adopted silently until 0.9.0.
|
|
7
|
+
from its real owner. (Decided 2026-09-21; the Bao families adopted silently until 0.9.0. 0.9.0 was
|
|
8
|
+
never published, so 0.10.0 is the first release with the rule.)
|
|
8
9
|
|
|
9
10
|
To take an object over, say so:
|
|
10
11
|
|
|
@@ -92,6 +93,30 @@ read, a resume reads as a fresh create and asks for `--adopt`. `alchemy deploy`
|
|
|
92
93
|
session both share one. An orphan's interrupted create (no longer declared) cannot be compared with
|
|
93
94
|
a declaration, so Apply leaves its object in place with a note instead of deleting it.
|
|
94
95
|
|
|
96
|
+
## After a wiped store or `state delete`, it needs `--adopt`
|
|
97
|
+
|
|
98
|
+
Alchemy's documented repair for a bad record is `alchemy state delete`, then `alchemy deploy`,
|
|
99
|
+
escalating to `--adopt` only on `OwnedBySomeoneElse`
|
|
100
|
+
([inspecting state](https://alchemy.run/cli/inspecting-state#recover-from-bad-state)). For the
|
|
101
|
+
families above that escalation always comes (decided 2026-09-21): an object whose row is gone is
|
|
102
|
+
`Unowned`, so the deploy needs `--adopt`, or `adopt(true)` on its declaration. `MeshNode`
|
|
103
|
+
([mesh-node.md](./mesh-node.md)) and `R2BucketLock` (`src/cloudflare/r2-bucket-lock.ts`) need it
|
|
104
|
+
too. A `CaddyConfig` running the declared config re-imports without it, and so does every family
|
|
105
|
+
whose `read` never answers `Unowned`: the `pveHandlers` resources (Limits), the `forgejoHandlers`
|
|
106
|
+
resources and the Talos resources. Read the takeover first with `hf-adopt-verify`
|
|
107
|
+
([adopt-verify.md](./adopt-verify.md)).
|
|
108
|
+
|
|
109
|
+
★ This departs from Alchemy's docs, not from its code. The docs say a provider with no ownership
|
|
110
|
+
marker answers plain attributes, so its objects re-import silently (`AdoptPolicy.ts`,
|
|
111
|
+
[adopting resources](https://alchemy.run/cli/adopting-resources),
|
|
112
|
+
[provider](https://alchemy.run/infrastructure-as-code/provider#read)). Many of Alchemy's own
|
|
113
|
+
marker-less providers answer `Unowned` to a read with no state instead, as the kit's families do
|
|
114
|
+
(beta.79: `Cloudflare/Snippets/Snippet.ts`, `Cloudflare/Tunnel/WarpConnector.ts`,
|
|
115
|
+
`Cloudflare/Zone/Hold.ts`). After a wiped store those need `--adopt` too.
|
|
116
|
+
|
|
117
|
+
⚠️ **That page's `alchemy state delete myapp/pr-42` fails.** A stage or a whole stack is a
|
|
118
|
+
directory, and deleting one needs `-r` (beta.79 `State/Tree.ts`). A single row does not.
|
|
119
|
+
|
|
95
120
|
## Limits
|
|
96
121
|
|
|
97
122
|
- ⚠️ **A check, then a write: not a lock.** Two resources that declare the same name in **one**
|
|
@@ -108,6 +133,6 @@ a declaration, so Apply leaves its object in place with a note instead of deleti
|
|
|
108
133
|
- ⚠️ **`CaddyConfig` keeps its own probe.** A running config identical to the declaration reads as
|
|
109
134
|
ours, because adopting it changes nothing Caddy serves ([caddy.md](./caddy.md#adoption)).
|
|
110
135
|
`ProxmoxLxc` does not share that exception: its delete removes a guest and its volumes
|
|
111
|
-
([proxmox-lxc.md](./proxmox-lxc.md
|
|
136
|
+
([proxmox-lxc-adopt.md](./proxmox-lxc-adopt.md)).
|
|
112
137
|
- ⚠️ **The other `@homeflare/alchemy/proxmox` resources do not follow this rule yet.** Those built
|
|
113
138
|
on `pveHandlers` (`src/proxmox/resource.ts`) still read a live object with no state as ours.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Provisioning baseline — every cluster, every node
|
|
2
|
+
|
|
3
|
+
Every `@homeflare/alchemy/proxmox` resource runs as a short-lived PVE token that OpenBao mints for
|
|
4
|
+
a **provision user**. That user, and the access it holds, is the baseline a cluster needs before a
|
|
5
|
+
stack can manage anything on it. This subpath ships it as one description with two readers:
|
|
6
|
+
|
|
7
|
+
| export | what it is |
|
|
8
|
+
| --------------------------- | ------------------------------------------------------------------------------------------- |
|
|
9
|
+
| `PROVISION_PRIVILEGES` | the provision role's privileges: 27, sorted, the exact set |
|
|
10
|
+
| `PROVISION_DEFAULTS` | the generic names in the last table, each overridable |
|
|
11
|
+
| `provisionBaseline(names)` | the resolved baseline both readers use; refuses names that are not PVE-shaped |
|
|
12
|
+
| `declareProvisionBaseline` | the baseline as `Proxmox.Role`, `Proxmox.Group`, `Proxmox.User` and `Proxmox.Acl` resources |
|
|
13
|
+
| `provisionBootstrap(names)` | the one-time root commands for a new cluster or node, as a `sh` script |
|
|
14
|
+
|
|
15
|
+
## The chicken and the egg
|
|
16
|
+
|
|
17
|
+
⛔ **The provision lane cannot create itself.** A plan needs a minted token, and a mint needs the
|
|
18
|
+
provision user to exist, be in the mint group, and hold the role on `/`. On a new cluster none of
|
|
19
|
+
that exists, so no plan can run, including the plan that would create it.
|
|
20
|
+
|
|
21
|
+
So root makes it once, and the stack takes it over from there:
|
|
22
|
+
|
|
23
|
+
1. **Bootstrap, as root, once per cluster.** Generate the script and run it on any one node:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
bun -e "import { provisionBootstrap } from '@homeflare/alchemy/proxmox';
|
|
27
|
+
process.stdout.write(provisionBootstrap())" | ssh root@node-a sh
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Read it first: it is plain text with no secret in it. It checks each object and changes only
|
|
31
|
+
what differs, naming each step (`role HfProvisioner: adding`, `user hf-read@pve: ok`). It
|
|
32
|
+
never creates a token and never sets a password.
|
|
33
|
+
|
|
34
|
+
2. **Point OpenBao at the users.** The proxmox secrets engine mints for them through a
|
|
35
|
+
`Bao.ProxmoxRole` per lane (`mintUser: PROVISION_DEFAULTS.provisionUser`, and the read user).
|
|
36
|
+
The engine's own credential, a user allowed to manage tokens for members of the mint group, is
|
|
37
|
+
set up with the engine. It is not part of this baseline.
|
|
38
|
+
|
|
39
|
+
3. **Declare the baseline in the stack, and adopt it once:**
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import * as Effect from 'effect/Effect';
|
|
43
|
+
import { declareProvisionBaseline } from '@homeflare/alchemy/proxmox';
|
|
44
|
+
|
|
45
|
+
export const access = Effect.gen(function* () {
|
|
46
|
+
return yield* declareProvisionBaseline('pve', target);
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The first deploy finds everything live with no state. Run it with `--adopt`, or pass
|
|
51
|
+
`{ adopt: true }`. Because the script made exactly what the declaration holds, the plan is a
|
|
52
|
+
clean adoption and the deploy writes nothing. From then on, a hand edit (a privilege added in
|
|
53
|
+
the UI, a user dropped from the group) shows up as a diff on the next plan.
|
|
54
|
+
|
|
55
|
+
## Every cluster, every node
|
|
56
|
+
|
|
57
|
+
- **A new cluster:** run the bootstrap on one node, then deploy.
|
|
58
|
+
- **A node joining a cluster:** nothing to do. Users, groups, roles and ACLs live in
|
|
59
|
+
`/etc/pve/user.cfg`, which the cluster shares. Running the script there prints `ok` for every
|
|
60
|
+
step and writes nothing.
|
|
61
|
+
- **A standalone node:** it is its own cluster. Bootstrap it.
|
|
62
|
+
|
|
63
|
+
## Your own names
|
|
64
|
+
|
|
65
|
+
Every name has a generic default, and a site passes its own to both readers:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const names = { role: 'Provisioner', mintGroup: 'mint', readUser: null };
|
|
69
|
+
provisionBootstrap(names); // the script
|
|
70
|
+
declareProvisionBaseline('pve', target, names); // the resources
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- `readUser: null` gives a baseline with no read lane.
|
|
74
|
+
- `readRole` must already exist (`PVEAuditor` is built in). The script stops before any write if
|
|
75
|
+
it does not.
|
|
76
|
+
- A name that is not PVE-shaped, or that would need quoting in a shell, is refused by both
|
|
77
|
+
readers before either one runs.
|
|
78
|
+
|
|
79
|
+
## What the baseline holds
|
|
80
|
+
|
|
81
|
+
| object | detail |
|
|
82
|
+
| -------------------------------- | ---------------------------------------------------------- |
|
|
83
|
+
| role `HfProvisioner` | exactly `PROVISION_PRIVILEGES`, nothing more, nothing less |
|
|
84
|
+
| group `hf-mint` | the fence: OpenBao may mint only for its members |
|
|
85
|
+
| user `hf-provision@pve` | in `hf-mint` only, enabled, no password, no expiry |
|
|
86
|
+
| user `hf-read@pve` | the same, for the read lane |
|
|
87
|
+
| `/`, `hf-provision@pve`, role | `HfProvisioner`, propagated |
|
|
88
|
+
| `/`, `hf-read@pve`, `PVEAuditor` | propagated |
|
|
89
|
+
|
|
90
|
+
★ **One privilege list for every family.** It is the union of what each resource's reconcile
|
|
91
|
+
needs. That includes managing this baseline itself (`Sys.Modify`, `Realm.AllocateUser`,
|
|
92
|
+
`User.Modify`, `Group.Allocate`, `Permissions.Modify`), so declaring it can never narrow the lane
|
|
93
|
+
out of its own repair. It does not include writes PVE keeps for `root@pam` (device passthrough,
|
|
94
|
+
bind mounts). Those are refused at plan with the command to run instead.
|
|
95
|
+
|
|
96
|
+
⚠️ **The role is the exact set.** Both the script and `Proxmox.Role` replace the privileges, never
|
|
97
|
+
add to them. A privilege added by hand is removed on the next run or deploy. If you need more,
|
|
98
|
+
declare your own role and grant.
|
|
99
|
+
|
|
100
|
+
⛔ **Everything retains.** Dropping `declareProvisionBaseline` from a stack drops its state and
|
|
101
|
+
leaves the role, users, group and grants in place. Deleting the provision user would revoke every
|
|
102
|
+
lease at once, including the one the next plan needs.
|
|
103
|
+
|
|
104
|
+
⚠️ **Adoption today.** `Proxmox.Role`, `Group`, `User` and `Acl` still read a live object with no
|
|
105
|
+
state as their own (the `pveHandlers` limit in [ownership.md](./ownership.md)). The `adopt`
|
|
106
|
+
option says what the stack means, so it keeps holding when those families follow the rule.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Proxmox.Lxc — adopting a live guest
|
|
2
|
+
|
|
3
|
+
The adoption half of [proxmox-lxc.md](./proxmox-lxc.md). Two rules, both decided 2026-09-21:
|
|
4
|
+
|
|
5
|
+
1. ⛔ **No live guest is adopted without `adopt(true)` or `--adopt`, not even one that matches the
|
|
6
|
+
declaration** — the kit-wide rule in [ownership.md](./ownership.md). Without either, the plan
|
|
7
|
+
fails with "Cannot adopt" and nothing is written. Matching is not proof the guest is yours: a
|
|
8
|
+
pasted config or a mistyped vmid reads the same, and once state claims a guest,
|
|
9
|
+
`RemovalPolicy.destroy()` deletes it and its volumes. `.pipe(adopt(false))` stays refused
|
|
10
|
+
under `--adopt`.
|
|
11
|
+
2. ⛔ **An adoption never changes a guest.** It is taken over exactly as it runs, or not at all.
|
|
12
|
+
With adoption on, a declaration that differs from the live config in any key fails the plan:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
CT 900 on pve1: adopting it would change memory, net1. An adoption never changes a guest, so
|
|
16
|
+
nothing is written. Declare what is live for these keys (the plan names keys, never values) and
|
|
17
|
+
plan again; change them after the adoption, as an update.
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- **Keys are named, values never.** A live config can carry anything (`description` is where
|
|
21
|
+
people paste credentials), and plan output lands in CI logs.
|
|
22
|
+
- **The fix is the declaration.** Copy the live value for each named key and plan again. A plan
|
|
23
|
+
that says `adopted` and fails nothing is the zero-change check: the deploy writes nothing (two
|
|
24
|
+
GETs), and the next plan is `noop`.
|
|
25
|
+
- **To change the guest, adopt it first.** Once state holds it, a changed declaration is an
|
|
26
|
+
ordinary `update`, and its plan warns with each key the deploy writes:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Proxmox.Lxc pve1/900: live config differs from the declaration in memory -- a deploy writes these.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- **The deploy asks again.** A key edited by hand between the plan and the deploy is refused at
|
|
33
|
+
apply, with nothing written. The adoption's state row stays, and every plan refuses the same way
|
|
34
|
+
until the declaration says what is live. Then it adopts with no write.
|
|
35
|
+
- **It comes before the other refusals.** A root@pam-only key, a smaller disk or an unprivileged
|
|
36
|
+
flip would print a declared value and a `pct set` to run. For an adoption the fix runs the other
|
|
37
|
+
way, so the adoption refusal is the one you see.
|
|
38
|
+
|
|
39
|
+
★ **Why it is strict.** Alchemy 2.0.0-beta.79 plans a cold adoption as `adopted` whatever it would
|
|
40
|
+
write, and prints no property diff for it. Until this rule, `diff` only logged the keys, and
|
|
41
|
+
`deploy --adopt --yes` then wrote them: a `net0` pasted without the live `tag=` took the guest off
|
|
42
|
+
its VLAN on the deploy that was meant to change nothing.
|
|
43
|
+
|
|
44
|
+
⚠️ **`alchemy plan` has no `--adopt` flag** (2.0.0-beta.79: only `deploy` declares it). Without
|
|
45
|
+
adoption on, the plan stops at "Cannot adopt" before the adoption check runs. To read the
|
|
46
|
+
adoption's plan, run `alchemy deploy --adopt --dry-run`, or declare `.pipe(adopt(true))` and run
|
|
47
|
+
`alchemy plan`.
|
|
48
|
+
|
|
49
|
+
## What counts as an adoption
|
|
50
|
+
|
|
51
|
+
State says, at plan and at apply alike (`src/ownership/adopting.ts`):
|
|
52
|
+
|
|
53
|
+
| the guest reached state… | adoption? |
|
|
54
|
+
| ------------------------------------------------------------------------------- | --------- |
|
|
55
|
+
| through the adoption probe: no state for it, found live | yes |
|
|
56
|
+
| on the deploy after that probe, until the adoption commits | yes |
|
|
57
|
+
| through an interrupted create the plan could not prove its own, under `--adopt` | yes |
|
|
58
|
+
| through an interrupted create whose guest still matches what it wrote | no |
|
|
59
|
+
| through this resource's own create, or an adoption that has committed | no |
|
|
60
|
+
|
|
61
|
+
An interrupted create proven its own is not an adoption. If its declaration changed meanwhile, the
|
|
62
|
+
deploy writes the change.
|
|
63
|
+
|
|
64
|
+
## Creates that find a guest
|
|
65
|
+
|
|
66
|
+
A deploy that planned a **create** never takes over a guest it then finds at that vmid (the plan
|
|
67
|
+
skips its adoption read while a prop is an unresolved Output, and a guest can appear after the
|
|
68
|
+
plan). Without adoption on, the deploy fails and forgets its `creating` row, so the next plan asks.
|
|
69
|
+
With it on, a matching guest is recorded with no write; any other fails until a plan can show it.
|
|
70
|
+
|
|
71
|
+
A create interrupted after its POST resumes on the next deploy **without** `--adopt` when the guest
|
|
72
|
+
still matches what it declared. If someone changed it meanwhile, the plan says "Cannot resume
|
|
73
|
+
creating". `--adopt` then adopts it as it now runs: declare what is live first.
|
|
74
|
+
|
|
75
|
+
A guest the cluster no longer lists at all, with state still held, plans `update` and warns that
|
|
76
|
+
the deploy **creates it again** from `ostemplate`, with new volumes. The plan fails instead when
|
|
77
|
+
there is no `ostemplate`, or when `rootfs`/`mpN` are declared by live volume id (as a paste is):
|
|
78
|
+
a create never reuses a volume.
|
|
79
|
+
|
|
80
|
+
## Leases
|
|
81
|
+
|
|
82
|
+
`GET …/config` needs `VM.Audit`, which the `read` lease has. A config PUT uses the cached
|
|
83
|
+
`provision` lease. A create, resize or delete mints a fresh one and polls its task with it.
|
package/docs/proxmox-lxc.md
CHANGED
|
@@ -16,7 +16,7 @@ export const guests = Effect.gen(function* () {
|
|
|
16
16
|
const ct = yield* ProxmoxLxc('ct', {
|
|
17
17
|
target,
|
|
18
18
|
node: 'pve1',
|
|
19
|
-
vmid:
|
|
19
|
+
vmid: 900,
|
|
20
20
|
hostname: 'ct-example',
|
|
21
21
|
cores: 4,
|
|
22
22
|
memory: 8192,
|
|
@@ -24,8 +24,8 @@ export const guests = Effect.gen(function* () {
|
|
|
24
24
|
onboot: 1,
|
|
25
25
|
unprivileged: 1,
|
|
26
26
|
features: 'nesting=1',
|
|
27
|
-
rootfs: 'local-zfs:subvol-
|
|
28
|
-
mp0: 'tank:subvol-
|
|
27
|
+
rootfs: 'local-zfs:subvol-900-disk-0,size=40G',
|
|
28
|
+
mp0: 'tank:subvol-900-disk-0,mp=/data,backup=0,size=200G',
|
|
29
29
|
net0: 'name=eth0,bridge=vmbr0,hwaddr=00:00:5E:00:53:01,ip=192.0.2.10/24,type=veth',
|
|
30
30
|
dev0: '/dev/dri/renderD128,uid=0,gid=44,mode=0660',
|
|
31
31
|
}).pipe(adopt(true));
|
|
@@ -90,7 +90,8 @@ Every prop is the key and spelling PVE uses in `/nodes/{node}/lxc/{vmid}/config`
|
|
|
90
90
|
|
|
91
91
|
The `digest` makes PVE refuse the write if the config changed since the deploy read it. ⚠️ That
|
|
92
92
|
read is the deploy's, not the plan's: a declared key edited by hand between `plan` and `deploy`
|
|
93
|
-
is written back to the declaration, even after a plan that warned of nothing.
|
|
93
|
+
is written back to the declaration, even after a plan that warned of nothing. (An adoption
|
|
94
|
+
refuses it instead.)
|
|
94
95
|
|
|
95
96
|
On a running guest, PVE may keep a change as pending until the next restart. A read returns the
|
|
96
97
|
pending value, so the plan still converges.
|
|
@@ -128,7 +129,8 @@ guest that needs `/dev/net/tun` (a Mesh door) or `/dev/dri` is created in two st
|
|
|
128
129
|
1. Declare the guest without the device, and deploy.
|
|
129
130
|
2. Run the `pct set` on the node, then add `dev0` to the declaration.
|
|
130
131
|
|
|
131
|
-
`nesting` on an unprivileged guest is allowed. It needs `VM.Allocate`, which
|
|
132
|
+
`nesting` on an unprivileged guest is allowed. It needs `VM.Allocate`, which the
|
|
133
|
+
[provisioning baseline](./provision-baseline.md)'s role has.
|
|
132
134
|
|
|
133
135
|
### Gaps
|
|
134
136
|
|
|
@@ -141,51 +143,12 @@ guest that needs `/dev/net/tun` (a Mesh door) or `/dev/dri` is created in two st
|
|
|
141
143
|
- **Pool membership, HA, firewall rules, snapshots, replication** are not declared here. Use their
|
|
142
144
|
own resources, or manage them by hand.
|
|
143
145
|
|
|
144
|
-
## Adopting
|
|
146
|
+
## Adopting
|
|
145
147
|
|
|
146
|
-
⛔ **No live guest is adopted without `adopt(true)` or `--adopt`,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
deletes it and its volumes. `.pipe(adopt(false))` stays refused under `--adopt`.
|
|
151
|
-
|
|
152
|
-
With adoption on, a cold adoption plans **`adopted`** even when the deploy would write. Alchemy
|
|
153
|
-
2.0.0-beta.79 forces an update after it adopts, and prints no property diff for that update. So
|
|
154
|
-
`diff` logs a warning naming each key a deploy would write:
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
Proxmox.Lxc pve1/100: live config differs from the declaration in memory, net1 -- a deploy writes these.
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
- **No warning** means the declaration is the live config. The deploy writes nothing (two GETs),
|
|
161
|
-
and the next plan is `noop`. ★ That is the zero-change adoption check: read the plan first.
|
|
162
|
-
- **A warning** means fix the declaration, or accept the named writes. Keys are named, values are
|
|
163
|
-
not.
|
|
164
|
-
|
|
165
|
-
⛔ **A warning does not stop the deploy.** `deploy --adopt --yes` writes each named key onto the
|
|
166
|
-
live guest. A `net0` declared without the live `tag=` is written without it, and the guest leaves
|
|
167
|
-
its VLAN. Only the plan you read makes an adoption zero-change.
|
|
168
|
-
|
|
169
|
-
⚠️ **`alchemy plan` has no `--adopt` flag** (2.0.0-beta.79: only `deploy` declares it). Without
|
|
170
|
-
adoption on, the plan stops at "Cannot adopt" before `diff` can warn. To read the adoption's plan,
|
|
171
|
-
run `alchemy deploy --adopt --dry-run`, or declare `.pipe(adopt(true))` and run `alchemy plan`.
|
|
172
|
-
|
|
173
|
-
A deploy that planned a **create** never takes over a guest it then finds at that vmid (the plan
|
|
174
|
-
skips its adoption read while a prop is an unresolved Output, and a guest can appear after the
|
|
175
|
-
plan). Without adoption on, the deploy fails and forgets its `creating` row, so the next plan asks.
|
|
176
|
-
With it on, a matching guest is recorded with no write; any other fails until a plan can show it.
|
|
177
|
-
|
|
178
|
-
A create interrupted after its POST resumes on the next deploy **without** `--adopt` when the guest
|
|
179
|
-
still matches what it declared. If someone changed it meanwhile, the plan says "Cannot resume
|
|
180
|
-
creating", and `--adopt` resumes it (the warning names the keys it writes back).
|
|
181
|
-
|
|
182
|
-
A guest the cluster no longer lists at all, with state still held, plans `update` and warns that
|
|
183
|
-
the deploy **creates it again** from `ostemplate`, with new volumes. The plan fails instead when
|
|
184
|
-
there is no `ostemplate`, or when `rootfs`/`mpN` are declared by live volume id (as a paste is):
|
|
185
|
-
a create never reuses a volume.
|
|
186
|
-
|
|
187
|
-
`GET …/config` needs `VM.Audit`, which the `read` lease has. A config PUT uses the cached
|
|
188
|
-
`provision` lease. A create, resize or delete mints a fresh one and polls its task with it.
|
|
148
|
+
⛔ **No live guest is adopted without `adopt(true)` or `--adopt`, and an adoption never changes
|
|
149
|
+
a guest.** It is taken over exactly as it runs, or the plan fails naming each key that differs.
|
|
150
|
+
What the plan says, what counts as an adoption, and the leases each step uses:
|
|
151
|
+
[proxmox-lxc-adopt.md](./proxmox-lxc-adopt.md).
|
|
189
152
|
|
|
190
153
|
## Removing
|
|
191
154
|
|
package/docs/proxmox.md
CHANGED
|
@@ -8,5 +8,13 @@ or creates one from a template.
|
|
|
8
8
|
flip or another template fails the plan. Destroy retains unless `RemovalPolicy.destroy()`.
|
|
9
9
|
- ⛔ **It adopts nothing without `--adopt` or `adopt(true)`**, a matching guest included
|
|
10
10
|
([ownership.md](./ownership.md)): under `destroy`, a claimed guest's disks go with it.
|
|
11
|
+
- ⛔ **An adoption never changes a guest.** Any key that differs from the live config fails the
|
|
12
|
+
plan, named without its value ([proxmox-lxc-adopt.md](./proxmox-lxc-adopt.md)).
|
|
11
13
|
- ⛔ **root@pam-only keys** (`devN`, bind mounts, features beyond `nesting`) are refused at plan,
|
|
12
14
|
with the `pct set` to run instead. Guide: [proxmox-lxc.md](./proxmox-lxc.md).
|
|
15
|
+
- ★ **One provisioning baseline for every cluster and node**: the privilege list, a helper that
|
|
16
|
+
declares it, and the one-time root commands that bootstrap it
|
|
17
|
+
([provision-baseline.md](./provision-baseline.md)).
|
|
18
|
+
- ★ **Measurements in the source name a reference cluster with placeholders**: `C1`, three nodes
|
|
19
|
+
`node-b`, `node-c` and `node-d` with Ceph on a Thunderbolt mesh, and documentation addresses
|
|
20
|
+
(RFC 5737). The shapes and counts are measured; the names and addresses are not the real ones.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homeflare/alchemy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Custom Alchemy providers for Cloudflare, Proxmox, OpenBao, Forgejo, Talos, launchd and Caddy, and a verifier that proves adoptions are no-ops.",
|
|
5
5
|
"homepage": "https://github.com/taslabs-net/homeflare-kit/tree/main/packages/alchemy#readme",
|
|
6
6
|
"bugs": "https://github.com/taslabs-net/homeflare-kit/issues",
|
package/src/caddy/admin-guard.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* Refused unless the transport is at that default; declaring `admin <address>` is the fix.
|
|
21
21
|
* · a Host the new config does not allow: `origins` without it, or — with no `origins` — a Host
|
|
22
22
|
* outside Caddy's loopback defaults (`localhost`, `[::1]`, `127.0.0.1` at the listen port).
|
|
23
|
-
* Every later call answers 403 (admin.go allowedOrigins / checkHost; MEASURED on
|
|
23
|
+
* Every later call answers 403 (admin.go allowedOrigins / checkHost; MEASURED on a Mac host's
|
|
24
24
|
* Caddy 2.11.4 — `host not allowed`). The transport also sends `Origin: http://<Host>`, which
|
|
25
25
|
* Caddy checks against the same list (originAllowed), so one check covers both.
|
|
26
26
|
* · `enforce_origin` over a unix socket: this transport, like Caddy's CLI (cmd/commandfuncs.go
|
package/src/caddy/local-admin.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* ★ HOST AND ORIGIN, AS CADDY's OWN CLI SENDS THEM (cmd/commandfuncs.go AdminAPIRequest): over
|
|
13
13
|
* TCP, `Host` and `Origin: http://<host>`; over a socket, `Host: 127.0.0.1` and no Origin, because
|
|
14
14
|
* Caddy skips the Host check on unix listeners (admin.go allowedOrigins). MEASURED against the
|
|
15
|
-
*
|
|
15
|
+
* a Mac host's Caddy 2.11.4: any other Host gets 403 `host not allowed`, a forwarded port included —
|
|
16
16
|
* hence `hostHeader`.
|
|
17
17
|
*/
|
|
18
18
|
import { request as httpRequest } from 'node:http';
|
package/src/cloudflare/client.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* ⛔ THE SDK WAS ALREADY IN THE STORE AND DECLARED BY NOBODY. Measured 2026-09-15:
|
|
6
6
|
* `node_modules/.pnpm/cloudflare@4.5.0` exists as a transitive dependency of `alchemy`, and
|
|
7
7
|
* `pnpm ls cloudflare --depth 0` at the root and in every workspace package returns nothing.
|
|
8
|
-
*
|
|
8
|
+
* `<estate>/local/scripts/vendor-sdk-ratchet.ts` row R2 says exactly that — "not declared anywhere
|
|
9
9
|
* in the workspace yet … the row adds the dependency once, then converts the TS callers" — so
|
|
10
10
|
* this package is that row's first half, and the twenty ratcheted offences keep their rows.
|
|
11
11
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The vocabulary of an R2 lock rule, and the comparison that decides whether to write one.
|
|
3
3
|
*
|
|
4
|
-
* ★ ITS OWN MODULE FOR THE REASON
|
|
4
|
+
* ★ ITS OWN MODULE FOR THE REASON `<estate>/litellm/src/equal.ts` IS ONE: the comparison is the
|
|
5
5
|
* riskiest function in the provider and it deserves to be readable and testable without the
|
|
6
6
|
* lifecycle around it. An equality that returned true for everything would make every reconcile
|
|
7
7
|
* a no-op and the resource would silently never write anything — which looks exactly like a
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Create/PATCH bodies for `Forgejo.BranchProtection` — extracted for the 250-line cap.
|
|
3
3
|
*
|
|
4
|
-
* ★ READ OFF
|
|
4
|
+
* ★ READ OFF `<estate>/mcp-servers/docs/api/upstream/forgejo.json` — create posts
|
|
5
5
|
* `CreateBranchProtectionOption`; update sends `EditBranchProtectionOption` fields only.
|
|
6
6
|
*/
|
|
7
7
|
import type { BranchProtectionProps } from './branch-protection.ts';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `Forgejo.BranchProtection` — one rule at `/repos/{owner}/{repo}/branch_protections/{name}`.
|
|
3
3
|
*
|
|
4
|
-
* ★ READ OFF
|
|
4
|
+
* ★ READ OFF `<estate>/mcp-servers/docs/api/upstream/forgejo.json`: create is POST collection;
|
|
5
5
|
* read/update/delete use the branch/rule name in the path segment `{name}`. Response carries
|
|
6
6
|
* `rule_name` (preferred) and deprecated `branch_name`.
|
|
7
7
|
*
|
package/src/forgejo/client.ts
CHANGED
|
@@ -87,7 +87,7 @@ export const forgejo = <T>(
|
|
|
87
87
|
* (src/Hetzner/Providers.ts builds on `FetchHttpClient.layer`; there is no bare fetch in its
|
|
88
88
|
* Docker, Kubernetes or GitHub providers). It buys a typed error channel, interruption when
|
|
89
89
|
* a plan is cancelled, the runtime's tracing, and a client a test can replace without
|
|
90
|
-
* monkey-patching a global.
|
|
90
|
+
* monkey-patching a global. <estate>/proxmox/src/resource.ts carries the same note.
|
|
91
91
|
*/
|
|
92
92
|
const client = yield* HttpClient.HttpClient;
|
|
93
93
|
/**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* `Forgejo.OrgSecret` — org-level Actions secret metadata at `/orgs/{org}/actions/secrets/{name}`.
|
|
3
3
|
*
|
|
4
4
|
* ★ CHOICE: AUTHORED WITH WRITE-ONLY VALUE VIA ENV, NOT REFUSED. The OpenAPI in
|
|
5
|
-
*
|
|
5
|
+
* `<estate>/mcp-servers/docs/api/upstream/forgejo.json` shows `Secret` returns only `name` and
|
|
6
6
|
* `created_at` — never the `data` field — so attributes stay metadata-only. Create/update is PUT
|
|
7
7
|
* with body `{ data }`, which does not fit POST/PATCH; this family uses the `upsert` seam on
|
|
8
8
|
* `ForgejoSpec` instead of hand-rolling provider handlers.
|
package/src/forgejo/org-label.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `Forgejo.OrgLabel` — one org-level issue label shared across every repository in the org.
|
|
3
3
|
*
|
|
4
|
-
* ★ RETIRES `
|
|
4
|
+
* ★ RETIRES the estate's hand-run `labels-reconcile.sh`, which
|
|
5
5
|
* converged the whole `labels.yaml` file in one shot. That script CREATED and PATCHED labels
|
|
6
6
|
* from the spec and REPORTED `EXTRA` labels present live but absent from the file — it never
|
|
7
7
|
* deleted them, because removing a label strips it from every issue that carries it.
|
package/src/forgejo/org-team.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `Forgejo.OrgTeam` — one organization team under `/orgs/{org}/teams`.
|
|
3
3
|
*
|
|
4
|
-
* ★ READ OFF
|
|
4
|
+
* ★ READ OFF `<estate>/mcp-servers/docs/api/upstream/forgejo.json` (Forgejo 16.x OpenAPI): create is
|
|
5
5
|
* `POST /orgs/{org}/teams`; read/update/delete use numeric id at `/teams/{id}` — not the name.
|
|
6
6
|
*
|
|
7
7
|
* ⚠️ PATCH AND DELETE USE `/teams/{id}` — same seam as org labels. `locate` lists by name;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `Forgejo.RepoWebhook` — one repository webhook under `/repos/{owner}/{repo}/hooks`.
|
|
3
3
|
*
|
|
4
|
-
* ★ READ OFF
|
|
4
|
+
* ★ READ OFF `<estate>/mcp-servers/docs/api/upstream/forgejo.json`: create is POST collection; read/update/delete
|
|
5
5
|
* use numeric hook id at `/repos/{owner}/{repo}/hooks/{id}`. `locate` lists and matches on `name`.
|
|
6
6
|
*
|
|
7
7
|
* ⛔ NO WEBHOOK `secret` IN PROPS OR ATTRIBUTES. Optional HMAC material is env-only at write time
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `Forgejo.Repository` — an organization-owned git repository.
|
|
3
3
|
*
|
|
4
|
-
* ★ MEASURED 2026-09-13 against the live daemon (`GET /api/v1/repos
|
|
4
|
+
* ★ MEASURED 2026-09-13 against the live daemon (`GET /api/v1/repos/<org>/<repo>`):
|
|
5
5
|
* responses are flat JSON with `private`, `has_issues`, `default_branch`, etc. Create is
|
|
6
6
|
* `POST /orgs/{org}/repos`; read/update/delete use `/repos/{owner}/{repo}`.
|
|
7
7
|
*
|
|
@@ -15,7 +15,7 @@ import { type ForgejoRequirements, forgejoHandlers } from './resource.ts';
|
|
|
15
15
|
import { bool, text } from './values.ts';
|
|
16
16
|
|
|
17
17
|
export interface RepositoryProps {
|
|
18
|
-
/** Organization login — the owner segment in
|
|
18
|
+
/** Organization login — the owner segment in `<org>/<repo>`. */
|
|
19
19
|
org: string;
|
|
20
20
|
/** Repository name. Changing it is a replace, not an update. */
|
|
21
21
|
name: string;
|
package/src/forgejo/resource.ts
CHANGED
|
@@ -28,7 +28,7 @@ import { ForgejoError, forgejo } from './client.ts';
|
|
|
28
28
|
/**
|
|
29
29
|
* ★ WHAT EVERY OPERATION HERE NEEDS FROM THE RUNTIME — and it is NOT what the first draft said.
|
|
30
30
|
*
|
|
31
|
-
* These resources declared `ChildProcessSpawner`, copied from
|
|
31
|
+
* These resources declared `ChildProcessSpawner`, copied from <estate>/proxmox, where it is there
|
|
32
32
|
* because a PVE credential is minted by shelling out to `bao`. Nothing in this package shells
|
|
33
33
|
* out: the token comes from `FORGEJO_TOKEN` in the environment. Declaring a service you never
|
|
34
34
|
* use is not harmless — it makes a stack provide a layer for nothing, and it hides the one
|
|
@@ -152,7 +152,7 @@ export const forgejoOperations = <Props extends object, Attributes>(
|
|
|
152
152
|
* The five provider handlers for a spec'd Forgejo object, wired once.
|
|
153
153
|
*
|
|
154
154
|
* ⛔ IT STOPS AT THE HANDLERS AND DOES NOT RETURN THE LAYER — same reasoning as pveHandlers in
|
|
155
|
-
*
|
|
155
|
+
* <estate>/proxmox: wrapping `Provider.effect` here needs casts against Alchemy's `Props<R>`.
|
|
156
156
|
*
|
|
157
157
|
* ⚠️ `list` answers empty — Forgejo index endpoints return the whole org; adoption stays explicit.
|
|
158
158
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* ★ WHY IT EXISTS: forgejo-provision, the service account the Forgejo engine mints as, belongs to the
|
|
5
5
|
* HomeFlare Owners team — Tim, 2026-09-14. Membership is what its tokens can reach; the role's
|
|
6
|
-
* scopes only cap it (
|
|
6
|
+
* scopes only cap it (<estate>/openbao/src/forgejo-bootstrap.ts).
|
|
7
7
|
*
|
|
8
8
|
* ★ READ OFF THE LIVE 16.0.3 SWAGGER (127.0.0.1:3000/swagger.v1.json, 2026-09-14): GET, PUT and
|
|
9
9
|
* DELETE `/teams/{id}/members/{username}` answer 200, 204 and 204, and 404 when absent. The team
|
package/src/openbao/README.md
CHANGED
|
@@ -17,7 +17,7 @@ plus `FetchHttpClient.layer`.
|
|
|
17
17
|
- ⛔ A rename or move onto a name or path that already exists fails the plan, before anything is
|
|
18
18
|
written (every family but the mounts, whose moves are below). Move through a free name, in two
|
|
19
19
|
deploys, or remove the target first.
|
|
20
|
-
- ⛔ **Nothing is adopted without `--adopt`** (0.9.0), not even an object identical to the
|
|
20
|
+
- ⛔ **Nothing is adopted without `--adopt`** (0.9.0, first published as 0.10.0), not even an object identical to the
|
|
21
21
|
declaration: a live name with no state fails the plan, or the apply when a prop was an Output.
|
|
22
22
|
Our own interrupted create still resumes when its row can prove it — not one killed while a
|
|
23
23
|
prop was still an Output. [docs/ownership.md](../../docs/ownership.md). Change
|
|
@@ -105,10 +105,10 @@ export const machines = Effect.gen(function* () {
|
|
|
105
105
|
});
|
|
106
106
|
yield* BaoKubernetesRole('pod', {
|
|
107
107
|
aliasNameSource: 'serviceaccount_name',
|
|
108
|
-
boundServiceAccountNames: ['
|
|
108
|
+
boundServiceAccountNames: ['llm-gateway'],
|
|
109
109
|
boundServiceAccountNamespaces: ['ai'],
|
|
110
|
-
name: 'ai-
|
|
111
|
-
tokenPolicies: ['ai-
|
|
110
|
+
name: 'ai-gateway',
|
|
111
|
+
tokenPolicies: ['ai-gateway'],
|
|
112
112
|
});
|
|
113
113
|
});
|
|
114
114
|
```
|
package/src/openbao/auth-role.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* ★ `defaultRemovalPolicy: 'retain'` — deleting a role locks out every host that logs in with it:
|
|
9
9
|
* its secret_ids go with it (the ⚠️ on `delete` below). Opt in with
|
|
10
|
-
* `.pipe(RemovalPolicy.destroy())`; see resource.ts in
|
|
10
|
+
* `.pipe(RemovalPolicy.destroy())`; see resource.ts in <estate>/proxmox.
|
|
11
11
|
*/
|
|
12
12
|
import { Resource } from 'alchemy';
|
|
13
13
|
import { isResolved } from 'alchemy/Diff';
|