@homeflare/alchemy 0.37.1 → 0.37.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/proxmox/backup-job-config.d.ts +95 -0
- package/dist/proxmox/backup-job-config.d.ts.map +1 -0
- package/dist/proxmox/backup-job-distilled.d.ts +46 -0
- package/dist/proxmox/backup-job-distilled.d.ts.map +1 -0
- package/dist/proxmox/backup-job.d.ts +9 -26
- package/dist/proxmox/backup-job.d.ts.map +1 -1
- package/dist/proxmox/ceph-fs-distilled.d.ts +17 -10
- package/dist/proxmox/ceph-fs-distilled.d.ts.map +1 -1
- package/dist/proxmox/ceph-fs-wire.d.ts +5 -22
- package/dist/proxmox/ceph-fs-wire.d.ts.map +1 -1
- package/dist/proxmox/ceph-fs.d.ts +3 -3
- package/dist/proxmox/distilled-pbs.d.ts +12 -0
- package/dist/proxmox/distilled-pbs.d.ts.map +1 -0
- package/dist/proxmox/fake-pbs-jobs.d.ts +23 -0
- package/dist/proxmox/fake-pbs-jobs.d.ts.map +1 -0
- package/dist/proxmox/fake-pbs-notify.d.ts.map +1 -1
- package/dist/proxmox/fake-pve.d.ts +1 -1
- package/dist/proxmox/fake-pve.d.ts.map +1 -1
- package/dist/proxmox/group-wire.d.ts +2 -2
- package/dist/proxmox/group-wire.d.ts.map +1 -1
- package/dist/proxmox/index.js +1382 -506
- package/dist/proxmox/index.js.map +51 -27
- package/dist/proxmox/live-core-fixtures.d.ts +40 -0
- package/dist/proxmox/live-core-fixtures.d.ts.map +1 -0
- package/dist/proxmox/metric-server-config.d.ts +49 -0
- package/dist/proxmox/metric-server-config.d.ts.map +1 -0
- package/dist/proxmox/metric-server-distilled.d.ts +43 -0
- package/dist/proxmox/metric-server-distilled.d.ts.map +1 -0
- package/dist/proxmox/metric-server.d.ts +2 -10
- package/dist/proxmox/metric-server.d.ts.map +1 -1
- package/dist/proxmox/node-network-wire.d.ts +1 -1
- package/dist/proxmox/node-network-wire.d.ts.map +1 -1
- package/dist/proxmox/notification-matcher-distilled.d.ts +9 -0
- package/dist/proxmox/notification-matcher-distilled.d.ts.map +1 -0
- package/dist/proxmox/notification-matcher-lifecycle.d.ts +27 -0
- package/dist/proxmox/notification-matcher-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/notification-matcher.d.ts +5 -5
- package/dist/proxmox/notification-matcher.d.ts.map +1 -1
- package/dist/proxmox/notification-sdk-input.d.ts +22 -0
- package/dist/proxmox/notification-sdk-input.d.ts.map +1 -0
- package/dist/proxmox/notification-target-distilled.d.ts +12 -0
- package/dist/proxmox/notification-target-distilled.d.ts.map +1 -0
- package/dist/proxmox/notification-target-form.d.ts +2 -0
- package/dist/proxmox/notification-target-form.d.ts.map +1 -1
- package/dist/proxmox/notification-target-lifecycle.d.ts +29 -0
- package/dist/proxmox/notification-target-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/notification-target-spec.d.ts +5 -0
- package/dist/proxmox/notification-target-spec.d.ts.map +1 -0
- package/dist/proxmox/notification-target.d.ts +4 -5
- package/dist/proxmox/notification-target.d.ts.map +1 -1
- package/dist/proxmox/pbs-datastore-endpoint.d.ts +1 -1
- package/dist/proxmox/pbs-datastore-form.d.ts +18 -42
- package/dist/proxmox/pbs-datastore-form.d.ts.map +1 -1
- package/dist/proxmox/pbs-datastore-guard.d.ts +7 -4
- package/dist/proxmox/pbs-datastore-guard.d.ts.map +1 -1
- package/dist/proxmox/pbs-datastore-lifecycle.d.ts +56 -0
- package/dist/proxmox/pbs-datastore-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/pbs-datastore-wire.d.ts +21 -0
- package/dist/proxmox/pbs-datastore-wire.d.ts.map +1 -0
- package/dist/proxmox/pbs-datastore.d.ts +5 -6
- package/dist/proxmox/pbs-datastore.d.ts.map +1 -1
- package/dist/proxmox/pbs-notification-input.d.ts +18 -0
- package/dist/proxmox/pbs-notification-input.d.ts.map +1 -0
- package/dist/proxmox/pbs-notification-matcher-distilled.d.ts +11 -0
- package/dist/proxmox/pbs-notification-matcher-distilled.d.ts.map +1 -0
- package/dist/proxmox/pbs-notification-matcher.d.ts +4 -3
- package/dist/proxmox/pbs-notification-matcher.d.ts.map +1 -1
- package/dist/proxmox/pbs-notification-target-distilled.d.ts +20 -0
- package/dist/proxmox/pbs-notification-target-distilled.d.ts.map +1 -0
- package/dist/proxmox/pbs-notification-target-lifecycle.d.ts +4 -4
- package/dist/proxmox/pbs-notification-target-lifecycle.d.ts.map +1 -1
- package/dist/proxmox/pbs-notification-target.d.ts +1 -1
- package/dist/proxmox/pbs-prune-job-lifecycle.d.ts +22 -0
- package/dist/proxmox/pbs-prune-job-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/pbs-prune-job-spec.d.ts +5 -0
- package/dist/proxmox/pbs-prune-job-spec.d.ts.map +1 -0
- package/dist/proxmox/pbs-prune-job-wire.d.ts +11 -0
- package/dist/proxmox/pbs-prune-job-wire.d.ts.map +1 -0
- package/dist/proxmox/pbs-prune-job.d.ts +9 -8
- package/dist/proxmox/pbs-prune-job.d.ts.map +1 -1
- package/dist/proxmox/pbs-sync-job-attributes.d.ts +34 -0
- package/dist/proxmox/pbs-sync-job-attributes.d.ts.map +1 -0
- package/dist/proxmox/pbs-sync-job-form.d.ts +21 -22
- package/dist/proxmox/pbs-sync-job-form.d.ts.map +1 -1
- package/dist/proxmox/pbs-sync-job-lifecycle.d.ts +22 -0
- package/dist/proxmox/pbs-sync-job-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/pbs-sync-job-wire.d.ts +11 -0
- package/dist/proxmox/pbs-sync-job-wire.d.ts.map +1 -0
- package/dist/proxmox/pbs-sync-job.d.ts +9 -39
- package/dist/proxmox/pbs-sync-job.d.ts.map +1 -1
- package/dist/proxmox/pbs-verify-job-lifecycle.d.ts +22 -0
- package/dist/proxmox/pbs-verify-job-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/pbs-verify-job-spec.d.ts +16 -0
- package/dist/proxmox/pbs-verify-job-spec.d.ts.map +1 -0
- package/dist/proxmox/pbs-verify-job-wire.d.ts +11 -0
- package/dist/proxmox/pbs-verify-job-wire.d.ts.map +1 -0
- package/dist/proxmox/pbs-verify-job.d.ts +12 -12
- package/dist/proxmox/pbs-verify-job.d.ts.map +1 -1
- package/dist/proxmox/pool.d.ts +10 -1
- package/dist/proxmox/pool.d.ts.map +1 -1
- package/dist/proxmox/storage-wire.d.ts +2 -2
- package/dist/proxmox/storage-wire.d.ts.map +1 -1
- package/dist/proxmox/user-wire.d.ts +2 -2
- package/dist/proxmox/user-wire.d.ts.map +1 -1
- package/dist/proxmox/zfs-pool-wire.d.ts +1 -1
- package/dist/proxmox/zfs-pool-wire.d.ts.map +1 -1
- package/docs/changelog/README.md +22 -0
- package/docs/changelog/archive-01.md +176 -0
- package/docs/changelog/archive-02.md +180 -0
- package/docs/changelog/archive-03.md +179 -0
- package/docs/changelog/archive-04.md +178 -0
- package/docs/changelog/archive-05.md +177 -0
- package/docs/changelog/archive-06.md +180 -0
- package/docs/changelog/archive-07.md +180 -0
- package/docs/changelog/archive-08.md +178 -0
- package/docs/changelog/archive-09.md +181 -0
- package/docs/changelog/archive-10.md +182 -0
- package/docs/changelog/archive-11.md +182 -0
- package/docs/changelog/archive-12.md +183 -0
- package/docs/changelog/archive-13.md +180 -0
- package/docs/changelog/archive-14.md +178 -0
- package/docs/changelog/archive-15.md +181 -0
- package/docs/changelog/archive-16.md +182 -0
- package/docs/changelog/archive-17.md +183 -0
- package/docs/changelog/archive-18.md +55 -0
- package/docs/pbs-distilled-jobs.md +31 -0
- package/docs/pbs-notifications.md +8 -2
- package/docs/proxmox-notification-sdk.md +28 -0
- package/docs/proxmox-sdk-migration.md +60 -0
- package/docs/proxmox.md +9 -3
- package/docs/upstream-conformance-families.md +6 -2
- package/package.json +3 -3
- package/src/proxmox/adopt-noop.test.ts +4 -5
- package/src/proxmox/backup-job-config.ts +95 -0
- package/src/proxmox/backup-job-distilled.ts +86 -0
- package/src/proxmox/backup-job.ts +78 -116
- package/src/proxmox/ceph-fs-delete.test.ts +5 -8
- package/src/proxmox/ceph-fs-destroy-sdk.test.ts +98 -0
- package/src/proxmox/ceph-fs-distilled.ts +41 -13
- package/src/proxmox/ceph-fs-wire.ts +5 -93
- package/src/proxmox/ceph-fs.ts +7 -7
- package/src/proxmox/constraints.test.ts +9 -8
- package/src/proxmox/distilled-pbs.test.ts +87 -0
- package/src/proxmox/distilled-pbs.ts +50 -0
- package/src/proxmox/fake-pbs-jobs.ts +99 -0
- package/src/proxmox/fake-pbs-notify.ts +4 -1
- package/src/proxmox/fake-pve.ts +3 -2
- package/src/proxmox/live-core-fixtures.ts +126 -0
- package/src/proxmox/live-core-lifecycle.test.ts +115 -0
- package/src/proxmox/live-core-wire.test.ts +190 -0
- package/src/proxmox/metric-server-config.ts +87 -0
- package/src/proxmox/metric-server-distilled.ts +81 -0
- package/src/proxmox/metric-server.ts +77 -88
- package/src/proxmox/notification-distilled.test.ts +162 -0
- package/src/proxmox/notification-matcher-distilled.ts +60 -0
- package/src/proxmox/notification-matcher-lifecycle.ts +71 -0
- package/src/proxmox/notification-matcher.ts +9 -26
- package/src/proxmox/notification-replacement.test.ts +190 -0
- package/src/proxmox/notification-sdk-input.ts +39 -0
- package/src/proxmox/notification-target-distilled.ts +160 -0
- package/src/proxmox/notification-target-form.ts +2 -0
- package/src/proxmox/notification-target-lifecycle.ts +75 -0
- package/src/proxmox/notification-target-spec.ts +68 -0
- package/src/proxmox/notification-target.ts +6 -68
- package/src/proxmox/pbs-datastore-endpoint.ts +1 -1
- package/src/proxmox/pbs-datastore-form.ts +18 -49
- package/src/proxmox/pbs-datastore-guard.ts +7 -4
- package/src/proxmox/pbs-datastore-lifecycle.ts +114 -0
- package/src/proxmox/pbs-datastore-wire.ts +94 -0
- package/src/proxmox/pbs-datastore.ts +7 -133
- package/src/proxmox/pbs-jobs-delete.test.ts +40 -0
- package/src/proxmox/pbs-jobs-sdk.test.ts +215 -0
- package/src/proxmox/pbs-notification-errors.test.ts +93 -0
- package/src/proxmox/pbs-notification-input.ts +25 -0
- package/src/proxmox/pbs-notification-mail.test.ts +64 -0
- package/src/proxmox/pbs-notification-matcher-distilled.ts +65 -0
- package/src/proxmox/pbs-notification-matcher.ts +70 -17
- package/src/proxmox/pbs-notification-target-distilled.ts +124 -0
- package/src/proxmox/pbs-notification-target-lifecycle.ts +26 -17
- package/src/proxmox/pbs-notification-target.ts +1 -1
- package/src/proxmox/pbs-notifications-stack.test.ts +25 -0
- package/src/proxmox/pbs-prune-job-lifecycle.ts +52 -0
- package/src/proxmox/pbs-prune-job-spec.ts +53 -0
- package/src/proxmox/pbs-prune-job-wire.ts +68 -0
- package/src/proxmox/pbs-prune-job.ts +10 -58
- package/src/proxmox/pbs-sync-job-attributes.ts +33 -0
- package/src/proxmox/pbs-sync-job-form.ts +7 -6
- package/src/proxmox/pbs-sync-job-lifecycle.ts +46 -0
- package/src/proxmox/pbs-sync-job-wire.ts +71 -0
- package/src/proxmox/pbs-sync-job.ts +10 -41
- package/src/proxmox/pbs-verify-job-lifecycle.ts +52 -0
- package/src/proxmox/pbs-verify-job-spec.ts +76 -0
- package/src/proxmox/pbs-verify-job-wire.ts +58 -0
- package/src/proxmox/pbs-verify-job.ts +13 -85
- package/src/proxmox/pool.ts +92 -10
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Alchemy changelog archive 14
|
|
2
|
+
|
|
3
|
+
[Current changelog](../../CHANGELOG.md) · [Archive index](./README.md)
|
|
4
|
+
|
|
5
|
+
⛔ This is a bug only a consumer could see, and only one that obeys the rules. A stack may use
|
|
6
|
+
Resources the package entry exports and must not deep-import, so it could be handed
|
|
7
|
+
`BaoProxmoxRole` and still be unable to name its props — leaving it to write the shape out and
|
|
8
|
+
hope it stayed in step. MEASURED 2026-09-22 in homeflare-openbao, which did exactly that for
|
|
9
|
+
`BaoProxmoxRole` while declaring the VPS proxmox engine. For that family the cost is highest:
|
|
10
|
+
`mount` + `name` + `mintUser` + `ttl` + `maxTtl` IS the whole role, so a hand-written copy
|
|
11
|
+
duplicates the entire server-side state of a family whose `mintUser` is its security boundary.
|
|
12
|
+
|
|
13
|
+
Additive and type-only: no value, signature or runtime behaviour changes, and nothing that was
|
|
14
|
+
importable stops being importable. `src/openbao/index-types.test.ts` pins the surface with a
|
|
15
|
+
type-only test — `tsc --noEmit` is the assertion, so a family whose props stop being reachable
|
|
16
|
+
from the package entry fails here instead of in another repo's next consumer.
|
|
17
|
+
|
|
18
|
+
- [#107](https://github.com/taslabs-net/homeflare-kit/pull/107) [`734ef60`](https://github.com/taslabs-net/homeflare-kit/commit/734ef6067e504be20c65ce7ec9c221c54554d7e7) Thanks [@taslabs-net](https://github.com/taslabs-net)! - The provisioning baseline takes a comment per object, so it can describe a cluster that
|
|
19
|
+
already exists.
|
|
20
|
+
|
|
21
|
+
`provisionBaseline` and `provisionBootstrap` carried ONE `comment` for the mint group and
|
|
22
|
+
both mint users. That can only describe a cluster this baseline made. The common case is
|
|
23
|
+
the other one: a cluster that already has its mint group and its read user, each with its
|
|
24
|
+
own live comment, both of them already declared at those values by the stack that adopted
|
|
25
|
+
them. A single comment made the generated script modify all three, and the next deploy of
|
|
26
|
+
that stack wrote them back — a loop that reads like drift and is not. Measured on an
|
|
27
|
+
estate cluster on 2026-09-22, where the mint group had no comment at all and the read user
|
|
28
|
+
named its own mount.
|
|
29
|
+
|
|
30
|
+
`ProvisionNames` now adds `groupComment`, `provisionComment` and `readComment`, each
|
|
31
|
+
defaulting to `comment`, so the generic case is still one string and an override changes
|
|
32
|
+
exactly one object:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
provisionBootstrap({
|
|
36
|
+
role: 'LXCProvisioner',
|
|
37
|
+
groupComment: '', // live: no comment at all
|
|
38
|
+
readComment: 'mint target: read (ops)', // live: its own wording
|
|
39
|
+
provisionComment: 'mint target: provision (ops)', // the one new object
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The script then prints `group hf-mint: ok` and `user hf-read@pve: ok` and its only writes
|
|
44
|
+
are the role, the new user and its grant. A test runs exactly that against the CLI fake,
|
|
45
|
+
with the one-shared-comment run beside it as a negative control.
|
|
46
|
+
|
|
47
|
+
Also:
|
|
48
|
+
|
|
49
|
+
- Each comment is checked like `comment` was, and a problem is **named by where the value
|
|
50
|
+
came from** — a bad shared `comment` is still one problem called `comment`, not three
|
|
51
|
+
called after overrides the caller never passed.
|
|
52
|
+
- `readComment` goes with its lane: a `null` `readUser` drops the user, so the field is
|
|
53
|
+
neither used nor checked.
|
|
54
|
+
- `:` joins the characters a comment may hold. It is special in neither `sh` nor a Perl
|
|
55
|
+
`q{}`, and it is how real mint-user comments are written (`mint target: read`).
|
|
56
|
+
- `CoreProvisionNames` is the six names `PROVISION_DEFAULTS` resolves, split out so that
|
|
57
|
+
type keeps its exact shape; `ProvisionNames` extends it. `ProvisionComments` is the
|
|
58
|
+
resolved comment per object. Both are exported.
|
|
59
|
+
|
|
60
|
+
- [#102](https://github.com/taslabs-net/homeflare-kit/pull/102) [`4fb005e`](https://github.com/taslabs-net/homeflare-kit/commit/4fb005e4892f34bbcef01c5227d464ae36e80299) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Vendor schema constraints, generated and enforced at plan time. `homeflare-proxmox`'s first
|
|
61
|
+
`deploy:pbs` adopted ten objects and then failed its one create on `PVE POST config/verify -> 400:
|
|
62
|
+
parameter verification failed - comment: value may only be 128 characters long`. Nothing local
|
|
63
|
+
caught it, because the only place the number 128 existed was PBS's published schema: the generated
|
|
64
|
+
types keep `type`, `enum` and `optional` and drop every `maxLength`, `minLength`, `minimum`,
|
|
65
|
+
`maximum` and `pattern` the vendor states.
|
|
66
|
+
|
|
67
|
+
A new `codegen/` reads the cluster's own `apidoc.js` and emits machine-readable constraint tables
|
|
68
|
+
(`packages/alchemy/src/proxmox/generated/constraints/`, one file per vendor area, all inside the
|
|
69
|
+
250-line house cap). `constraints.ts` is a pure validator over a form and its endpoint's table, and
|
|
70
|
+
`resource.ts`'s shared `pveHandlers` runs it on both the create form and the update form — so every
|
|
71
|
+
family that declares an `endpoint` gets it, with no per-resource copy. `Pbs.Datastore`, which writes
|
|
72
|
+
its own handlers, gets the same check through `pbs-datastore-endpoint.ts`. Nineteen families are
|
|
73
|
+
wired, covering 37 endpoints: PBS datastore/prune/sync/verify/matchers, PVE acl, groups, roles,
|
|
74
|
+
users, backup, firewall aliases, HA resources and rules, matchers, replication, SDN vnets and zones,
|
|
75
|
+
pools and storage. Presence of a vendor-required parameter is checked on CREATE only — an update
|
|
76
|
+
form is partial by design.
|
|
77
|
+
|
|
78
|
+
Provenance is committed with it. `codegen/manifest.json` records each schema's vendor, product,
|
|
79
|
+
version as the host reports it (pve-manager 9.2.11, proxmox-backup-server 4.2.6-1), source host
|
|
80
|
+
ROLE and absolute path, sha256, byte size and fetch time; every generated header names its manifest
|
|
81
|
+
entry, version and sha256 prefix. The raw 5.8 MB blobs stay out of git in a documented cache
|
|
82
|
+
directory, and the generator refuses to run when a cached file's sha256 does not match.
|
|
83
|
+
`tests/schema-manifest.test.ts` recomputes the tables' digest on every run and, when the cache is
|
|
84
|
+
present, runs `bun codegen/constraints.ts --check` so a stale generation fails with the exact
|
|
85
|
+
refresh command. The two UniFi OpenAPI documents (Network 10.4.57, Site Manager 1.0.0) are recorded
|
|
86
|
+
as available and consumed by nothing — there is no UniFi provider family yet.
|
|
87
|
+
|
|
88
|
+
⛔ Patterns are translated through a whitelist, not copied. Measured over both whole schemas: PBS
|
|
89
|
+
prints its Rust regex through `Display`, so every PBS pattern arrives wrapped in slashes, and it
|
|
90
|
+
uses POSIX classes — `new RegExp` accepts `/^[[:^cntrl:]]*$/` and `[[:^cntrl:]]` SILENTLY and means
|
|
91
|
+
something else in both cases, which would have refused every legal comment. PVE's `(?^:…)` throws.
|
|
92
|
+
Anything the whitelist cannot carry over faithfully is recorded verbatim as `patternSource` and left
|
|
93
|
+
unenforced, including PVE's 216 server-side `format` validators and PBS `schedule`, which publishes
|
|
94
|
+
no pattern at all.
|
|
95
|
+
|
|
96
|
+
`resource.ts` is split: the `PveSpec` shape and its argument move to `resource-spec.ts` (re-exported,
|
|
97
|
+
so no importer changes) to keep both files inside the house cap.
|
|
98
|
+
|
|
99
|
+
### Patch Changes
|
|
100
|
+
|
|
101
|
+
- [#103](https://github.com/taslabs-net/homeflare-kit/pull/103) [`173b736`](https://github.com/taslabs-net/homeflare-kit/commit/173b7365742921bfde6f3a3114fca22ff7978c46) Thanks [@taslabs-net](https://github.com/taslabs-net)! - `Proxmox.Lxc`'s guide now says what a container's **inside** is, and pins it with a test.
|
|
102
|
+
|
|
103
|
+
`Proxmox.Lxc` declares the keys in `/nodes/{node}/lxc/{vmid}/config` and nothing within the guest's
|
|
104
|
+
filesystem. Every consumer that meets that limit goes looking for the resource that must surely
|
|
105
|
+
exist — an exec, a file write, a cloud-init. **For containers it does not exist in PVE's API at
|
|
106
|
+
all.** QEMU VMs have `POST …/qemu/{vmid}/agent/exec`, `…/agent/file-write`, `…/agent/file-read` and
|
|
107
|
+
a `cloudinit` subtree; the complete `/nodes/{node}/lxc/{vmid}/…` endpoint set has no counterpart.
|
|
108
|
+
The only reach inside is `termproxy` / `vncwebsocket`, an interactive console for a person.
|
|
109
|
+
|
|
110
|
+
- `docs/proxmox-lxc.md` gains that table under **Gaps**, and says plainly that a generic,
|
|
111
|
+
vendor-API-based Resource for a container's interior cannot be written: there is nothing to
|
|
112
|
+
wrap. What is left, in the order that keeps a change declarative — bake it into the template
|
|
113
|
+
(⚠️ `ostemplate` is create-only, so changing it REPLACES the guest), a first-boot artifact, or a
|
|
114
|
+
recorded one-time human step named as undeclared.
|
|
115
|
+
- ⛔ It also says why an exec-over-SSH resource is not option zero: it needs a credential and a
|
|
116
|
+
network path _to the guest_, so when the guest is what provides credentials, naming or reach to
|
|
117
|
+
others, it inverts the bootstrap — the new system's first boot depends on its own output. The
|
|
118
|
+
`HostRunner` seam in `@homeflare/alchemy/launchd` is where such a runner plugs in, and the kit
|
|
119
|
+
ships only `localRunner()` and `sudoRunner()` on purpose.
|
|
120
|
+
- ⚠️ **Sibling families are not at parity.** QEMU and LXC sit under the same `/nodes/{node}/…` tree
|
|
121
|
+
with completely different reach; the guide now says not to infer one from the other.
|
|
122
|
+
- `src/proxmox/lxc-interior.test.ts` checks this against the generated schema on every run, so it
|
|
123
|
+
fails the day PVE adds such an endpoint — which is exactly when the kit would want to wrap it.
|
|
124
|
+
Its positive control asserts QEMU's three are present, so a change to the generated file's shape
|
|
125
|
+
fails the test instead of making every absence assertion pass for free.
|
|
126
|
+
|
|
127
|
+
Docs and a test only. No resource, type or behaviour changed.
|
|
128
|
+
|
|
129
|
+
- [#105](https://github.com/taslabs-net/homeflare-kit/pull/105) [`b1e4af3`](https://github.com/taslabs-net/homeflare-kit/commit/b1e4af38b2bf71986bb8b85974aa432e3b30ec99) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Generated Proxmox API coverage report, with provenance.
|
|
130
|
+
|
|
131
|
+
`docs/api-coverage.md` and `docs/api-coverage.json` map every PVE and PBS endpoint that can
|
|
132
|
+
create, update or delete state to the Resource that owns it, or to nothing — derived from the
|
|
133
|
+
vendor schemas named in `schemas/manifest.json` (product version, source host role, sha256, size,
|
|
134
|
+
fetch time), not hand-counted. PVE `pve-manager/9.2.4/5e5ae681198514d4`: 88 of 335 write
|
|
135
|
+
endpoints owned. PBS `proxmox-backup-server 4.2.6-1`: 27 of 182.
|
|
136
|
+
|
|
137
|
+
Each row also names the parameters carrying a vendor `maxLength`, `minLength`, `minimum`,
|
|
138
|
+
`maximum`, `pattern` or `format` that nothing local enforces — 545 on the PVE endpoints we own and
|
|
139
|
+
144 on the PBS ones, including the `comment` on `POST /config/verify` whose 128-character limit
|
|
140
|
+
failed a `Pbs.VerifyJob` create at apply time.
|
|
141
|
+
|
|
142
|
+
Regenerate with `bun run api:coverage`; `--check` exits 1 when the committed report is stale.
|
|
143
|
+
`tests/api-coverage.test.ts` fails if a Resource claims an endpoint the schema no longer has, if a
|
|
144
|
+
path the source calls unreachable turns out to exist, or if the report has been hand-edited. No
|
|
145
|
+
package code changed.
|
|
146
|
+
|
|
147
|
+
## 0.14.0
|
|
148
|
+
|
|
149
|
+
### Minor Changes
|
|
150
|
+
|
|
151
|
+
- [#98](https://github.com/taslabs-net/homeflare-kit/pull/98) [`89ce4fd`](https://github.com/taslabs-net/homeflare-kit/commit/89ce4fdf606d6cc6e635164f0413b4ca6187379f) Thanks [@taslabs-net](https://github.com/taslabs-net)! - **New subpath: `@homeflare/alchemy/github` — one repository's merge policy in one call.**
|
|
152
|
+
|
|
153
|
+
- `declareRepoPolicy(id, options)` declares a `GitHub.Repository` and a `GitHub.Ruleset` over its
|
|
154
|
+
default branch: squash-only merges, auto-merge, head branches deleted on merge, no branch
|
|
155
|
+
deletion, no force pushes, and the status-check contexts you name required with
|
|
156
|
+
`strict_required_status_checks_policy` off. Both resources retain; `adopt` is piped only when
|
|
157
|
+
asked. Generic and parameterized — `rulesetName`, `include`/`exclude`, `bypassActors`,
|
|
158
|
+
`enforcement`, `baseUrl` (applied to both resources or to neither), and a `settings` bag for
|
|
159
|
+
everything that is not merge policy, merged underneath so it cannot re-open a merge method.
|
|
160
|
+
- `repoPolicy(options)` is the same policy as two plain prop objects, pure and type-only, for a
|
|
161
|
+
test or a stack that wants to declare the resources itself.
|
|
162
|
+
|
|
163
|
+
What it refuses, because each of these failures is silent:
|
|
164
|
+
|
|
165
|
+
- ⛔ **Auto-merge with nothing to wait for merges the pull request immediately.** Auto-merge is
|
|
166
|
+
a queue only while something is outstanding, and three inputs produce "nothing
|
|
167
|
+
outstanding": no `checks` and no `requiredApprovals`; an `enforcement` that is not `active`
|
|
168
|
+
(the rules are listed and none of them block); and an explicitly empty `include` (the ruleset
|
|
169
|
+
matches no ref while GitHub still shows it as active). A required review counts as outstanding,
|
|
170
|
+
so `requiredApprovals` with an empty `checks` is allowed. `checks: []` alone is accepted only
|
|
171
|
+
alongside `autoMerge: false` — the honest description of a repo with no green run to require yet.
|
|
172
|
+
- ⛔ **A blank check context** is refused: GitHub stores it and no job ever reports it, so every
|
|
173
|
+
pull request waits on a check that cannot come.
|
|
174
|
+
- ⛔ **`requiredApprovals: 0` is refused rather than treated as "no reviews".** Zero approvals is
|
|
175
|
+
the solo-maintainer shape and needs `require_extra_approval_for_unattributed_changes: false`,
|
|
176
|
+
which `alchemy@2.0.0-beta.79`'s `Ruleset` cannot send and GitHub defaults to `true`. Omitting
|
|
177
|
+
`requiredApprovals` declares no `pull_request` rule at all, which is a different and honest
|
|
178
|
+
thing.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Alchemy changelog archive 15
|
|
2
|
+
|
|
3
|
+
[Current changelog](../../CHANGELOG.md) · [Archive index](./README.md)
|
|
4
|
+
|
|
5
|
+
⚠️ **The ruleset half cannot adopt.** Alchemy's `Ruleset` reports nothing without prior state and
|
|
6
|
+
creates unconditionally, and GitHub allows two rulesets with one name — so a first deploy onto a
|
|
7
|
+
repository that already has one adds a second, both enforcing. `GitHub.Repository` does not share
|
|
8
|
+
the problem. Check `gh api repos/<owner>/<repo>/rulesets` first, or pass your own `rulesetName`.
|
|
9
|
+
See `docs/repo-policy.md`.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#101](https://github.com/taslabs-net/homeflare-kit/pull/101) [`2e43257`](https://github.com/taslabs-net/homeflare-kit/commit/2e4325741b1264feb4069712e2da1dc28c7f9ac3) Thanks [@taslabs-net](https://github.com/taslabs-net)! - `repoPolicy` refuses two more ways to build a ruleset that matches no ref: a blank ref
|
|
14
|
+
pattern (`include: [' ']` has length 1, so the empty-array guard passed it) and an
|
|
15
|
+
`exclude` that cancels every `include` (exclusions win in a GitHub ruleset, so it reads
|
|
16
|
+
as a narrowing and acts as an off switch). Both produced an `active` ruleset over nothing
|
|
17
|
+
with `allowAutoMerge: true` — the end state the auto-merge guard exists to prevent.
|
|
18
|
+
Include and exclude patterns are now trimmed, de-duplicated and sorted like `checks`.
|
|
19
|
+
|
|
20
|
+
`docs/repo-policy.md` also records, measured against live GitHub rather than inferred,
|
|
21
|
+
that the ruleset half never plans a no-op, and that its `rules` and `bypass_actors` are
|
|
22
|
+
replaced wholesale rather than merged.
|
|
23
|
+
|
|
24
|
+
## 0.13.0
|
|
25
|
+
|
|
26
|
+
### Minor Changes
|
|
27
|
+
|
|
28
|
+
- [#95](https://github.com/taslabs-net/homeflare-kit/pull/95) [`4fc9a38`](https://github.com/taslabs-net/homeflare-kit/commit/4fc9a38d5f2f9513d75edad99edaf8b9005afc30) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Proxmox notifications that page. `@homeflare/alchemy/proxmox` adds three adopt-capable families and
|
|
29
|
+
a webhook body:
|
|
30
|
+
|
|
31
|
+
- **`PbsNotificationTarget`** — PBS `webhook`, `smtp` and `sendmail` targets. Secret fields are
|
|
32
|
+
**write-only**: a webhook `secret`, the smtp `password` and any credential-bearing `header` are
|
|
33
|
+
declared as `{ fromEnv: 'VARIABLE' }` and read by the deploying process at call time. No value
|
|
34
|
+
reaches Alchemy state — the store keeps names, a fixed-salt scrypt digest of the live headers, and
|
|
35
|
+
a random-salt seal of what the provider last wrote. A plan diffs on those (hash or presence), a
|
|
36
|
+
plan without the variables is presence-only, a rotated value is PUT alone, and a write that needs
|
|
37
|
+
a missing variable fails by the variable's name before any request.
|
|
38
|
+
- **`PbsNotificationMatcher`** and **`ProxmoxNotificationMatcher`** — `match-severity`,
|
|
39
|
+
`match-field`, `match-calendar`, `targets`, `mode`, `invert-match`, `comment`, `disable`. A matcher
|
|
40
|
+
is compared as its whole rule; both built-in `default-matcher`s adopt as-is with no write.
|
|
41
|
+
- **`alertmanagerAlertBody()`** — a PBS webhook body template that posts one Alertmanager v2 alert
|
|
42
|
+
(`/api/v2/alerts`; labels `alertname`, `severity`, `source`, `job_type`, `job_id`, `datastore`,
|
|
43
|
+
`hostname`; annotations `summary`, `description`). Every value goes through `json`, and every
|
|
44
|
+
optional field is guarded, so a GC failure (no `job-id`) and the UI's field-less Test notification
|
|
45
|
+
both render valid JSON.
|
|
46
|
+
- `FromEnv` is exported. The PVE and PBS generated API types now cover the notification endpoints
|
|
47
|
+
and matchers, and the shared PVE read treats a `{"data": null}` answer as absent instead of
|
|
48
|
+
throwing.
|
|
49
|
+
|
|
50
|
+
Guide: `docs/pbs-notifications.md`.
|
|
51
|
+
|
|
52
|
+
### Patch Changes
|
|
53
|
+
|
|
54
|
+
- [#97](https://github.com/taslabs-net/homeflare-kit/pull/97) [`1c746d2`](https://github.com/taslabs-net/homeflare-kit/commit/1c746d26334444d8ef8270fcba857035391b11e0) Thanks [@taslabs-net](https://github.com/taslabs-net)! - `Pbs.NotificationTarget` now refuses a literal credential in a plain prop, and does so at plan,
|
|
55
|
+
before anything is stored.
|
|
56
|
+
|
|
57
|
+
- The target refuses an `Authorization`, `Proxy-Authorization` or `Cookie` header, a header or URL
|
|
58
|
+
query parameter named like `token`, `key`, `secret`, `password` or `signature`, and a password in
|
|
59
|
+
the URL's userinfo. Each must be declared `{ fromEnv }` or read `{{ secrets.<name> }}`. Before
|
|
60
|
+
this change, such a value planned, deployed, and stayed in the state store. A target already
|
|
61
|
+
deployed that way now fails its plan until the literal is moved; the next deploy then replaces
|
|
62
|
+
the stored props.
|
|
63
|
+
- Refusals now run in Alchemy's adoption probe as well as in `diff`. A new target used to be
|
|
64
|
+
refused only in `reconcile`, after Alchemy had already committed its props to state.
|
|
65
|
+
- `alertmanagerAlertBody()` refuses a `generatorURL` that is not an absolute http(s) URL.
|
|
66
|
+
Alertmanager rejects the whole post for one.
|
|
67
|
+
- Docs: `docs/pbs-alertmanager-body.md` shows the exact template text. The old block had been
|
|
68
|
+
reflowed by the formatter. PVE's per-matcher read needs `Mapping.Audit` or `Mapping.Modify`;
|
|
69
|
+
`Mapping.Use` is not enough.
|
|
70
|
+
|
|
71
|
+
## 0.12.0
|
|
72
|
+
|
|
73
|
+
### Minor Changes
|
|
74
|
+
|
|
75
|
+
- [#90](https://github.com/taslabs-net/homeflare-kit/pull/90) [`de65267`](https://github.com/taslabs-net/homeflare-kit/commit/de652677804581b1dd431d3930d6656ac760037f) Thanks [@taslabs-net](https://github.com/taslabs-net)! - **New: the provisioning baseline, one description for every cluster and node.**
|
|
76
|
+
`@homeflare/alchemy/proxmox` now exports:
|
|
77
|
+
|
|
78
|
+
- `PROVISION_PRIVILEGES`: the provision role's 27 privileges as one sorted, frozen constant. It is
|
|
79
|
+
the union of what every family's reconcile needs, including what the lane needs to manage the
|
|
80
|
+
baseline itself.
|
|
81
|
+
- `PROVISION_DEFAULTS` and `provisionBaseline(names)`: generic names (role `HfProvisioner`, users
|
|
82
|
+
`hf-provision@pve` and `hf-read@pve`, group `hf-mint`, read role `PVEAuditor`), overridable per
|
|
83
|
+
site. Names that are not PVE-shaped or would need shell quoting are refused.
|
|
84
|
+
- `declareProvisionBaseline(id, target, names?, { adopt? })`: declares the role, the mint group,
|
|
85
|
+
one user per lane (its group membership included) and the grant for each lane on `/`. Every
|
|
86
|
+
resource retains, and `adopt` is piped only when asked.
|
|
87
|
+
- `provisionBootstrap(names?)`: a pure generator of the one-time root commands for a new cluster
|
|
88
|
+
or node, as a POSIX `sh` script. It checks each object before changing it, so it is idempotent,
|
|
89
|
+
and it never creates a token or sets a password.
|
|
90
|
+
|
|
91
|
+
The provision lane cannot create itself, so root bootstraps it once, then the stack adopts it and
|
|
92
|
+
keeps it. After the bootstrap, the declaration is a clean adoption that writes nothing. See
|
|
93
|
+
`docs/provision-baseline.md`.
|
|
94
|
+
|
|
95
|
+
### Patch Changes
|
|
96
|
+
|
|
97
|
+
- [#89](https://github.com/taslabs-net/homeflare-kit/pull/89) [`94fbc2f`](https://github.com/taslabs-net/homeflare-kit/commit/94fbc2f0f8471e64164b4c77418140c0fcf28cce) Thanks [@taslabs-net](https://github.com/taslabs-net)! - The adopt verifier's guide now says what `alchemy drift` does in alchemy 2.0.0-beta.79. It has no dry run and no `--yes`, although its docs page lists one: `alchemy drift --yes` fails with `Unrecognized flag`. A non-interactive run prints the repair plan and exits `0` even when something drifted, so it cannot gate a deploy. `--repair` restores the props saved at the last deploy, not what the code declares now, and it writes without a prompt, outside the deploy gate. The ownership guide now says that recovering from a wiped state store, or from `alchemy state delete`, needs `--adopt` for every family that follows the ownership rule, and for `MeshNode` and `R2BucketLock`. Alchemy's docs say objects with no ownership marker re-import without the flag, but many of Alchemy's own marker-less providers refuse them too, as the kit does. The exceptions are a `CaddyConfig` running the declared config, and the families whose `read` never answers `Unowned`: the `pveHandlers`, `forgejoHandlers` and Talos resources.
|
|
98
|
+
|
|
99
|
+
- [#94](https://github.com/taslabs-net/homeflare-kit/pull/94) [`745d941`](https://github.com/taslabs-net/homeflare-kit/commit/745d94161b5cdd63c8d8ebd40a730c8b8b931ff7) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Docs only. The changelog marks 0.9.0 as never published (no npm version, no git tag): its changes first shipped in 0.10.0, and the README, the openbao README and the ownership guide now say so where they cite 0.9.0. The `Proxmox.Storage` header no longer says the estate's provision role lacks `Datastore.Allocate`: it was widened, and the provisioning baseline carries it.
|
|
100
|
+
|
|
101
|
+
- [#93](https://github.com/taslabs-net/homeflare-kit/pull/93) [`d3c332b`](https://github.com/taslabs-net/homeflare-kit/commit/d3c332bd4f175cc3510b7ae06ff98f4b426f0c52) Thanks [@taslabs-net](https://github.com/taslabs-net)! - The published sources, docs and examples no longer name the maintainer's own infrastructure.
|
|
102
|
+
Node names, cluster and pool names, NICs, VLANs, addresses, hostnames, guest ids, principals and
|
|
103
|
+
policy names in comments, fixtures and examples are now neutral placeholders: nodes `node-a`…
|
|
104
|
+
`node-d`, a reference cluster `C1`, documentation addresses (RFC 5737), `bao.example.internal`.
|
|
105
|
+
Measured facts are unchanged; only the names are. `site.example.json` names its hosts `node-a`…
|
|
106
|
+
`node-c`. One runtime message changed: `forgejo-bootstrap` now says to run on "the host where
|
|
107
|
+
Forgejo runs". The historical CHANGELOG entries are unchanged.
|
|
108
|
+
|
|
109
|
+
## 0.11.0
|
|
110
|
+
|
|
111
|
+
### Minor Changes
|
|
112
|
+
|
|
113
|
+
- [#87](https://github.com/taslabs-net/homeflare-kit/pull/87) [`520926f`](https://github.com/taslabs-net/homeflare-kit/commit/520926fd81230025bb5337a2b0ea72992c0ccf7f) Thanks [@taslabs-net](https://github.com/taslabs-net)! - **Breaking: a `ProxmoxLxc` adoption never changes a guest.** When a guest is adopted — found by
|
|
114
|
+
the adoption probe with no state, or an interrupted create resumed under `--adopt` that the plan
|
|
115
|
+
could not prove its own — any key the declaration says otherwise now FAILS the plan, naming the
|
|
116
|
+
keys and never their values:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
CT 100 on pve1: adopting it would change memory, net1. An adoption never changes a guest, so
|
|
120
|
+
nothing is written. …
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Only an exact match adopts, and its deploy writes nothing. Before, such a plan said `adopted`,
|
|
124
|
+
only logged the keys as a warning, and `deploy --adopt --yes` wrote them. The deploy asks again
|
|
125
|
+
against a fresh read, so a hand edit between plan and deploy is refused rather than written back.
|
|
126
|
+
To change a guest, adopt it as it runs first; the change is then an ordinary `update`. The
|
|
127
|
+
ownership rules are unchanged: nothing is adopted without `--adopt` or `adopt(true)`, and an
|
|
128
|
+
interrupted create proven its own still resumes and writes. See `docs/proxmox-lxc-adopt.md`.
|
|
129
|
+
|
|
130
|
+
## 0.10.1
|
|
131
|
+
|
|
132
|
+
### Patch Changes
|
|
133
|
+
|
|
134
|
+
- [#85](https://github.com/taslabs-net/homeflare-kit/pull/85) [`61249dc`](https://github.com/taslabs-net/homeflare-kit/commit/61249dce57ec7a3ba319246074ba7e431a9927fc) Thanks [@taslabs-net](https://github.com/taslabs-net)! - **Fix: `hf-adopt-verify` no longer passes a drifted adoption whose provider's `diff` never looks at
|
|
135
|
+
the live object.** Alchemy gives an adopted row's `diff` the declaration as its recorded props, so
|
|
136
|
+
a diff that compares recorded props with the declaration says `noop` whatever the cloud holds.
|
|
137
|
+
Alchemy's own `Cloudflare.R2Bucket` diff works that way. The verifier printed `ok` and exited `0`
|
|
138
|
+
while listing the drift under `changed`, and the deploy then wrote it. Now an adopted `noop` with
|
|
139
|
+
something under `changed` gets its `diff` run a second time, with the live values of those fields
|
|
140
|
+
passed as the recorded props (still read-only, and write paths are still refused). A second
|
|
141
|
+
`noop` passes with a note that the family does not manage those fields. Any other answer fails
|
|
142
|
+
the row. The answer appears as `recheck` in the JSON report. No kit PVE/PBS family is affected:
|
|
143
|
+
each one's `diff` re-reads the cluster.
|
|
144
|
+
|
|
145
|
+
**Fix: the default report no longer hides a `create` when a rename hands its old id to a new
|
|
146
|
+
resource.** A row now counts as stateful only when it plans from the state row it reads, rather
|
|
147
|
+
than any row that happens to sit at its FQN.
|
|
148
|
+
|
|
149
|
+
## 0.10.0
|
|
150
|
+
|
|
151
|
+
### Minor Changes
|
|
152
|
+
|
|
153
|
+
- [#84](https://github.com/taslabs-net/homeflare-kit/pull/84) [`eb2fd8b`](https://github.com/taslabs-net/homeflare-kit/commit/eb2fd8b0a7f332731316ceedfa9af3a619616e95) Thanks [@taslabs-net](https://github.com/taslabs-net)! - ⚠️ **BEHAVIOUR CHANGE — three silent takeovers in 0.9.0's ownership rule closed, and `sudoRunner()`
|
|
154
|
+
refuses more.** Found by an adversarial review of 0.9.0, each measured through Alchemy's own plan
|
|
155
|
+
and apply before the fix, and each now refused, writing nothing. It narrows two 0.9.0 notes: crash
|
|
156
|
+
recovery without `--adopt` needs a row that can prove the object ours, and `--adopt` at apply
|
|
157
|
+
never covers a fresh replace's new generation.
|
|
158
|
+
|
|
159
|
+
- **A `Bao.*` create killed before its ownership check no longer resumes onto someone else's object.**
|
|
160
|
+
Apply writes the `creating` row before `reconcile` runs, and drops any prop still an `Output`
|
|
161
|
+
from it. With the name an Output, the next deploy "resumed" that create and wrote over another
|
|
162
|
+
owner's role (all nine role and MFA families, and `Bao.Mount` by path). With a knob an Output,
|
|
163
|
+
`Bao.Mount` and `Bao.AuthMethod` read the missing prop as "not managed", adopted another owner's
|
|
164
|
+
mount and tuned it. A state row now proves an object ours only when it carries every value the
|
|
165
|
+
declaration names, and a resume is let through only when the family's own `read` proves the
|
|
166
|
+
object that generation's — for every `Bao.*` family and `ProxmoxLxc`. **So a create killed
|
|
167
|
+
while a prop was still an Output now needs `--adopt` to resume.**
|
|
168
|
+
- **An interrupted `Bao.*` replace no longer writes over what another owner put at its new
|
|
169
|
+
identity since**, unless `--adopt`.
|
|
170
|
+
- **`--adopt` at apply now covers a create or an interrupted generation, never a fresh replace's
|
|
171
|
+
new generation**, for every `Bao.*` family, `HostFile` and `LaunchdJob`. The planner never
|
|
172
|
+
offers adoption there, yet a deploy-wide `--adopt` let a `HostFile` whose path changed overwrite
|
|
173
|
+
a file it did not own at the new path.
|
|
174
|
+
- **`sudoRunner()` also refuses** (⚠️ a prefix 0.9.0 accepted can now fail): a directory _above_ the
|
|
175
|
+
prefix, from `/` down, that root does not own alone (whoever may write the prefix's parent can
|
|
176
|
+
swap the prefix itself; a root-owned symlink such as `/etc` is still followed), and any ACL entry
|
|
177
|
+
from `/` down to the file that grants a write right (read with `ls -lden`, as the operator; deny
|
|
178
|
+
entries pass; unreadable ACLs refuse). Both run before sudo and, through `checkWrite`, at plan
|
|
179
|
+
time.
|
|
180
|
+
|
|
181
|
+
`docs/ownership.md` and `docs/launchd-sudo.md` carry the details and the limits.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Alchemy changelog archive 16
|
|
2
|
+
|
|
3
|
+
[Current changelog](../../CHANGELOG.md) · [Archive index](./README.md)
|
|
4
|
+
|
|
5
|
+
- [#83](https://github.com/taslabs-net/homeflare-kit/pull/83) [`01c54cf`](https://github.com/taslabs-net/homeflare-kit/commit/01c54cfcf3892388369e4c01765ca5f69d843e8b) Thanks [@taslabs-net](https://github.com/taslabs-net)! - **New: `hf-adopt-verify` and `@homeflare/alchemy/verify` — prove a deploy's adoptions are no-ops
|
|
6
|
+
before it runs.** Alchemy prints `adopted` for an object that already matches and for one that
|
|
7
|
+
drifts alike (beta.79 `Plan.ts` turns the diff's `noop` into an update after the adoption probe),
|
|
8
|
+
and the deploy reconciles both. The verifier plans the stack with Alchemy's own planner, with
|
|
9
|
+
every provider watched and every write path refused. For each row without a state row it reports
|
|
10
|
+
the provider's `read`, its own `diff` before the engine forced it, and the declared fields that
|
|
11
|
+
differ (names only). It exits `0` only when all are no-ops, `1` when any is not, `2` when the plan
|
|
12
|
+
could not be computed.
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
bunx --bun hf-adopt-verify --config alchemy.run.ts --stage live [--all] [--json]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`verifyStack(target)` and `verifySession({ stack, context })` are the same thing as functions.
|
|
19
|
+
|
|
20
|
+
**Fix: adopting a `Proxmox.CephPool` that already matches no longer writes.** Its reconcile PUT
|
|
21
|
+
`setpool` whenever the pool existed, so every adoption forked a `cephsetpool` worker under the
|
|
22
|
+
provision token. On TB4 that was six tasks, one per pool, on 2026-09-13 and 2026-09-20. It now
|
|
23
|
+
skips the PUT when its own `matches` holds, like every other PVE/PBS family. The predicate is
|
|
24
|
+
shared in one place (`update-guard.ts`). `docs/adopted-deploys.md` traces what a deploy of an
|
|
25
|
+
adopted row does for every family the Proxmox and PBS stacks use.
|
|
26
|
+
|
|
27
|
+
- [#80](https://github.com/taslabs-net/homeflare-kit/pull/80) [`66d9374`](https://github.com/taslabs-net/homeflare-kit/commit/66d937416287b8b657da4091eecdd3824666087a) Thanks [@taslabs-net](https://github.com/taslabs-net)! - `@homeflare/alchemy/proxmox` now exports `ProxmoxLxc` as a Resource. It declares a Proxmox VE container: you can adopt one that runs today, or create one from a template.
|
|
28
|
+
|
|
29
|
+
- **The props are PVE's own keys and spellings**, taken from `/nodes/{node}/lxc/{vmid}/config`: `hostname`, `cores`, `memory`, `swap`, `rootfs`, `mpN`, `netN`, `devN`, `features`, `unprivileged`, `onboot`, `startup`, `description`, `tags`, and the rest. To adopt a guest, paste its `pvesh get` output. An undeclared key is unmanaged. A key declared as `''` is removed, where PVE allows that. `password`, `ssh-public-keys` and `env` are typed `never`.
|
|
30
|
+
- **Values are compared as PVE stores them.** Key order and written-out defaults are ignored. A MAC PVE generated is ignored, and a NIC write keeps the live MAC. `storage:GiB` equals the volume it allocated. An existing volume is always written back with its live volume id.
|
|
31
|
+
- **Changes are made in place.** Config changes use one `PUT …/config` carrying the config `digest`. A larger disk uses `PUT …/resize`. Create, resize and delete wait for their PVE task. If nothing differs, nothing is written, including on the first deploy after an adoption.
|
|
32
|
+
- **Nothing plans a replace.** These changes fail the plan with a sentence, and nothing is written: a new `vmid`, a `node` the guest is not on, another `ostemplate`, an `unprivileged` flip, a smaller disk, another storage, or detaching a mount point.
|
|
33
|
+
- **Keys only root@pam can write are refused at plan.** These are `devN`, bind or device mounts, features other than `nesting`, and any feature on a privileged guest. PVE never treats an API token as `root@pam`, so the refusal prints the `pct set` to run on the node instead.
|
|
34
|
+
- **A read failure is not "absent".** A config read counts as absent only when it answers 500 and the cluster lists the vmid nowhere. Any other failure fails the plan. A vmid held by another node, or by a QEMU VM, fails the plan and says where it is. After HA or `pct migrate` has moved a guest, setting `node` to where it is now is an update that writes nothing.
|
|
35
|
+
- **Nothing is adopted without `adopt(true)` or `--adopt`, not even a guest that matches the declaration.** This is the rule of `docs/ownership.md`, which every `Bao.*` family, `HostFile` and `LaunchdJob` follow. Matching is not proof of ownership: once state claims a guest, `RemovalPolicy.destroy()` deletes it and its volumes. Without adoption on, the plan fails with "Cannot adopt". A create interrupted after its POST still resumes without `--adopt` when the guest matches what it declared.
|
|
36
|
+
- **An adoption's plan always says `adopted`.** Alchemy prints no diff for it, so a warning names each key a deploy would write.
|
|
37
|
+
- **A create only ever allocates.** A create naming an existing volume id (rather than `storage:GiB`) is refused, because PVE would unpack the template onto that volume; so is any key the resource does not manage. A deploy that planned a create never takes over a guest it then finds at that vmid. Without adoption on, it fails and forgets its `creating` row. With it on, a matching guest is recorded with no write and any other is refused. A guest the cluster lost while state still holds it plans `update` with a warning that the deploy creates it again, or fails the plan when it cannot be created.
|
|
38
|
+
- **State keeps managed keys only.** The `config` attribute is an allowlist, so a key a newer PVE adds (such as `entrypoint`) is not stored.
|
|
39
|
+
- **It retains by default.** Dropping the declaration leaves the guest running. Only `RemovalPolicy.destroy()` deletes it, only while the guest still matches its last declaration, and it never forces the delete or stops the guest first.
|
|
40
|
+
|
|
41
|
+
Breaking, for anyone who deep-imported the old provider-only version: `storage` is gone (declare `rootfs: 'storage:GiB'`), `ostemplate` is optional, the attributes are now `{ node, vmid, config, rawKeys }`, and `hostname` no longer defaults to `ct<vmid>`. The guide is `docs/proxmox-lxc.md`.
|
|
42
|
+
|
|
43
|
+
### Patch Changes
|
|
44
|
+
|
|
45
|
+
- [#82](https://github.com/taslabs-net/homeflare-kit/pull/82) [`462368f`](https://github.com/taslabs-net/homeflare-kit/commit/462368fa263ef541bac7c0e70070fb656b4fcda7) Thanks [@taslabs-net](https://github.com/taslabs-net)! - The ownership and `ProxmoxLxc` guides now say how to read an adoption's plan before it writes. `alchemy plan` has no `--adopt` flag in alchemy 2.0.0-beta.79, so without adoption on it stops at "Cannot adopt" before any resource can warn what taking the object over would write. Run `alchemy deploy --adopt --dry-run` instead, or declare `.pipe(adopt(true))` and run `alchemy plan`. The LXC guide also warns that a drift warning does not stop the deploy: `deploy --adopt --yes` writes a `net0` declared without the live `tag=` without it, and the guest leaves its VLAN.
|
|
46
|
+
|
|
47
|
+
## 0.9.0
|
|
48
|
+
|
|
49
|
+
> ⚠️ Never published: no npm version and no git tag. These changes first shipped in 0.10.0.
|
|
50
|
+
|
|
51
|
+
### Minor Changes
|
|
52
|
+
|
|
53
|
+
- [#78](https://github.com/taslabs-net/homeflare-kit/pull/78) [`8de1015`](https://github.com/taslabs-net/homeflare-kit/commit/8de1015993f147ffb2f88116204cba6efb449268) Thanks [@taslabs-net](https://github.com/taslabs-net)! - ⚠️ **BEHAVIOUR CHANGE — `@homeflare/alchemy/openbao` no longer adopts anything silently.** Until
|
|
54
|
+
0.8.0, every `Bao.*` family adopted a live object it had no state for: a new declaration of a
|
|
55
|
+
policy, role, mount, auth method, plugin or MFA object that already existed was taken over, and
|
|
56
|
+
rewritten, without being asked. **A stack that relied on that must now add `adopt(true)` to those
|
|
57
|
+
resources, or deploy once with `--adopt`.** Otherwise its next plan fails with
|
|
58
|
+
`OwnedBySomeoneElse` ("Cannot adopt resource … Re-run with `--adopt`").
|
|
59
|
+
|
|
60
|
+
- **Every `Bao.*` family (all 14).** With no state row, a live object reads as `Unowned`, even when it
|
|
61
|
+
is identical to the declaration. Identical is not proof of ownership: under `destroy`, the old
|
|
62
|
+
owner's delete would remove the object the new declaration had just claimed. The plan fails
|
|
63
|
+
unless adoption is on. This is the rule `HostFile`, `LaunchdJob` and `CaddyConfig` already
|
|
64
|
+
follow. The 0.8.0 swap (a new logical id for a live name, then the old id's delete) now fails the
|
|
65
|
+
plan and writes nothing.
|
|
66
|
+
- **Crash recovery still works without `--adopt`.** Alchemy's recovery read for an interrupted
|
|
67
|
+
create carries that row's own instance id, and the object is ours when it also matches the row's
|
|
68
|
+
props. An interrupted replace resumes through a note that its `diff` leaves for the apply. A
|
|
69
|
+
create interrupted between two writes (a mount enabled but not tuned) is not proven ours, so it
|
|
70
|
+
needs `--adopt`.
|
|
71
|
+
- **The same check at apply.** Alchemy skips the probe while a prop is still an Output, and never
|
|
72
|
+
probes the new generation of a replace. Each family's `reconcile` now reads the object first and
|
|
73
|
+
refuses the takeover before any write, unless `--adopt` or the resource's own `adopt(…)` allows
|
|
74
|
+
it, resolved as the planner resolves it. A refused create also forgets the `creating` row Apply
|
|
75
|
+
wrote, so the next plan does not adopt what the apply refused. A `BaoMount` / `BaoAuthMethod`
|
|
76
|
+
create with `remountFrom` refuses to move a live mount the stack holds no state for.
|
|
77
|
+
- **`HostFile` and `LaunchdJob`: `--adopt` now works at apply.** Their `reconcile` refused a
|
|
78
|
+
foreign file or job even under `--adopt`, where the probe had been skipped. A create now takes it
|
|
79
|
+
over when adoption is on. `adopt(false)` still wins over the flag. A rename onto an occupied
|
|
80
|
+
path or label stays refused.
|
|
81
|
+
- **`sudoRunner()` refuses more, before sudo** (⚠️ a declaration that 0.8.0 accepted can now fail):
|
|
82
|
+
- a root-owned file under a prefix that would be group- or world-writable, setuid or setgid
|
|
83
|
+
(`mode & 0o6022`; an omitted owner is root);
|
|
84
|
+
- any directory between the prefix and the file that root does not own, or that group or other
|
|
85
|
+
may write. Before, only the prefix itself was checked.
|
|
86
|
+
- A `HostFile` or `LaunchdJob` plan that will write now runs these checks too, through the new
|
|
87
|
+
optional `HostRunner.checkWrite`, so the refusal fails the plan instead of the apply. It only
|
|
88
|
+
reads, as the operator: a plan still never calls sudo.
|
|
89
|
+
|
|
90
|
+
New: `docs/ownership.md` (the rule, where it is checked, recovery, limits). `docs/launchd-sudo.md`,
|
|
91
|
+
`docs/launchd.md` and the openbao README and REPLACE.md are updated.
|
|
92
|
+
|
|
93
|
+
## 0.8.0
|
|
94
|
+
|
|
95
|
+
### Minor Changes
|
|
96
|
+
|
|
97
|
+
- [#74](https://github.com/taslabs-net/homeflare-kit/pull/74) [`cd6d437`](https://github.com/taslabs-net/homeflare-kit/commit/cd6d437c134ca726a6d6ca5b28053ed01a2762e6) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Rename safety for the other nine name- or catalog-keyed families in `@homeflare/alchemy/openbao`: `BaoAuthRole`, `BaoPkiRole`, `BaoJwtRole`, `BaoKubernetesRole`, `BaoJwtAuthConfig`, `BaoMfaTotpMethod`, `BaoMfaLoginEnforcement`, `BaoSshRole` and `BaoPlugin`. They now get the same checks as `BaoPolicy`, `BaoCloudflareRole` and `BaoProxmoxRole`, through one shared helper.
|
|
98
|
+
|
|
99
|
+
Behaviour changes:
|
|
100
|
+
|
|
101
|
+
- A rename or move onto a name, path or catalog entry that already exists now fails the plan, before anything is written. Before, two roles that swapped names under `RemovalPolicy.destroy()` both planned `replace`, and a green deploy deleted both of them. This happened for `BaoAuthRole`, `BaoPkiRole`, `BaoJwtRole`, `BaoKubernetesRole`, `BaoSshRole` and `BaoPlugin`. It also applies under `retain`: a swap now takes two deploys through a free name. For `BaoPlugin` it includes a version bump onto a version already registered by hand. For `BaoJwtAuthConfig` it includes a move onto a mount whose config is already set.
|
|
102
|
+
- The name or mount is now checked while other props are still pending Outputs. Before, a rename in the same deploy as any pending Output planned `update`, and the old object stayed live with no state record. For `BaoMfaTotpMethod` that wrote a second method. Its rename is now refused at plan in that case too.
|
|
103
|
+
- When the name itself is an Output not known until apply, reconcile now refuses the `update` before writing anything. The next deploy plans `replace`, or, for `BaoMfaTotpMethod`, refuses the rename.
|
|
104
|
+
- `BaoKubernetesRole` and `BaoJwtRole` compare names lowercased, as OpenBao keys them, so a change of case is not a move. Both still refuse an upper-case name.
|
|
105
|
+
- `hostAppRoles` now lets a host sit in several classes, with one role per class. It only refuses the same host listed twice in one class. Before, any host listed twice was refused.
|
|
106
|
+
- `hostAppRoles` refuses a host whose class names an inherited object key such as `constructor`. Before, that host passed as a class with no policies and no `secretIdTtl`, and got a role whose secret_id never expires.
|
|
107
|
+
|
|
108
|
+
Documentation: a new logical id for a name that is already live adopts it, and under `RemovalPolicy.destroy()` the old id's delete then removes it, in a green deploy. Change a logical id with Alchemy's `renamedFrom`, and take a name another resource is leaving in the deploy after the move (REPLACE.md). Also: deleting an AppRole role does not revoke the tokens it issued. OpenBao 2.6.2 deletes the role's secret_ids and role_id, so no new login succeeds, but issued tokens live to their TTL and only fail to renew. The `BaoAuthRole` comments said the delete revoked every token.
|
|
109
|
+
|
|
110
|
+
- [#75](https://github.com/taslabs-net/homeflare-kit/pull/75) [`85dd10c`](https://github.com/taslabs-net/homeflare-kit/commit/85dd10cd3fb16e9daf48592d21340449fb381b56) Thanks [@taslabs-net](https://github.com/taslabs-net)! - `CaddyConfig` in `@homeflare/alchemy/caddy` no longer adopts a running Caddy silently. In 0.7.0, the first read adopted whatever config a Caddy was running, and the next apply loaded over it.
|
|
111
|
+
|
|
112
|
+
Behaviour changes:
|
|
113
|
+
|
|
114
|
+
- With no state, the first read is Alchemy's adoption probe. A Caddy whose running config is the declared one is adopted as-is, and nothing is loaded. A Caddy serving nothing (`null`, or no apps) plans a create. Any other config reads as `Unowned`, so the plan refuses it unless the deploy runs with `--adopt`. A Caddyfile that cannot be compared at probe time also reads as `Unowned`, with a warning saying why, never an error: the engine replays this read to recover an interrupted create, and an error would fail every later plan.
|
|
115
|
+
- Where Alchemy skips that probe (props holding an Output, as on `caddyWithFile()`'s first deploy), the apply refuses the same takeover before any `/load` (the HostFile is written by then). It resolves adoption as the planner does: the resource's own `adopt(…)`, else `--adopt`. So `.pipe(adopt(false))` still refuses under `--adopt`, and `.pipe(adopt(true))` takes over without it.
|
|
116
|
+
- The state vouches only for the Caddy it was applied to. When the transport now reaches a Caddy at another endpoint, the apply needs adoption (`--adopt`, or the resource's `adopt(true)`) unless that Caddy runs the config the state last stored, the declared one, or nothing.
|
|
117
|
+
|
|
118
|
+
Docs: `docs/caddy.md` has the adoption table, and records that managed Caddies run `caddy run --resume` with their own `XDG_CONFIG_HOME`, set in the launchd job rather than the envfile. A restart then runs the last config Caddy accepted, and after a resumed start SIGUSR1 has no file to reload. The admin endpoint section moves to `docs/caddy-admin.md`, and the README's reasons for each peer and override pin move to `docs/peers.md`.
|
|
119
|
+
|
|
120
|
+
- [#77](https://github.com/taslabs-net/homeflare-kit/pull/77) [`9ec684c`](https://github.com/taslabs-net/homeflare-kit/commit/9ec684c612c5d9c0987b4e9ca462e1f683bf954e) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Add `sudoRunner()` to `@homeflare/alchemy/launchd`, so a host stack can deploy as the operator
|
|
121
|
+
instead of as root.
|
|
122
|
+
|
|
123
|
+
- **Only the calls that need root use `sudo -n`, in fixed argv shapes.** These are
|
|
124
|
+
`launchctl bootstrap | bootout | kickstart` in the system domain, and `install` / `rm` of a file
|
|
125
|
+
under a prefix the stack declares. A file is written as the operator to a private `0600` temp
|
|
126
|
+
file, then copied into place with `install -S -m <mode> -o <uid> -g <gid>`. Nothing else runs
|
|
127
|
+
as root, and a plan never calls sudo.
|
|
128
|
+
- **It is opt-in:** `launchdProviders(sudoRunner({ prefixes }))`. `localRunner()` stays the
|
|
129
|
+
default and never elevates, and nothing falls back to sudo.
|
|
130
|
+
- **It never prompts.** A password-required `sudo -n` fails at once with `SudoRefusedError`, and
|
|
131
|
+
the message says to run `sudo -v` or to grant exactly these commands `NOPASSWD`.
|
|
132
|
+
- **Each privileged argv is logged before it runs.** The log holds the argv only, never file
|
|
133
|
+
content.
|
|
134
|
+
- **These are refused before sudo is asked, with `SudoRefusedError`:** any argv outside the
|
|
135
|
+
allowlist (such as a bare `bootout system`, a directory `bootstrap`, or a plist anywhere but
|
|
136
|
+
`/Library/LaunchDaemons/<label>.plist`), a system `bootstrap` / `bootout` without
|
|
137
|
+
`/Library/LaunchDaemons` among the prefixes, a prefix that is not a real directory only root may
|
|
138
|
+
write, a path outside every prefix that needs root, a symlink between the prefix and the file, a
|
|
139
|
+
file the operator could not read back, and another user's `gui/<uid>` domain.
|
|
140
|
+
|
|
141
|
+
`docs/launchd-sudo.md` has the full list, the sudoers cautions, and the limits.
|
|
142
|
+
|
|
143
|
+
## 0.7.0
|
|
144
|
+
|
|
145
|
+
### Minor Changes
|
|
146
|
+
|
|
147
|
+
- [#72](https://github.com/taslabs-net/homeflare-kit/pull/72) [`294518d`](https://github.com/taslabs-net/homeflare-kit/commit/294518dc205c1981bf8f48aaf087bdb3869d4123) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Rename safety for `BaoPolicy`, `BaoCloudflareRole` and `BaoProxmoxRole` in `@homeflare/alchemy/openbao`.
|
|
148
|
+
|
|
149
|
+
Behaviour changes:
|
|
150
|
+
|
|
151
|
+
- A changed `name` on `BaoPolicy`, or a changed `mount` or `name` on `BaoCloudflareRole` or `BaoProxmoxRole`, now plans `replace`. Before, it planned `update`: the new object was written and the old one stayed live with no state record, even under `RemovalPolicy.destroy()`. Now the old one is deleted after the new one is written, or kept under the default `retain`, and the apply says so.
|
|
152
|
+
- `BaoPolicy` compares names the way OpenBao stores them, trimmed and lowercased, so a change of case is not a rename.
|
|
153
|
+
- A `BaoProxmoxRole` rename that also changes `mintUser` now fails the plan unless `allowMintUserChange` names the old mint user, the same rule an in-place re-scope already had.
|
|
154
|
+
- A rename or move onto a name or path that already exists live now fails the plan, before anything is written. Without this, two policies or roles that swapped names under `RemovalPolicy.destroy()` both planned `replace` and ended with both deleted. The check also applies under `retain`, because a diff cannot see the removal policy: a swap, or a move back onto a retained old generation, now takes two deploys through a free name, or removing the target by hand.
|
|
155
|
+
- When the new name is an Output that is not known until apply, reconcile now refuses the `update` before writing anything. The next deploy plans `replace`. A `BaoProxmoxRole` rename whose `allowMintUserChange` is still an Output defers the same way instead of failing the plan.
|
|
156
|
+
|
|
157
|
+
The rename is checked even while other props are still pending Outputs. `src/openbao/REPLACE.md` has the measured engine behaviour and what `retain` leaves live for each of the three.
|
|
158
|
+
|
|
159
|
+
- [#70](https://github.com/taslabs-net/homeflare-kit/pull/70) [`8cafb48`](https://github.com/taslabs-net/homeflare-kit/commit/8cafb48d84a9734997d7562311801025f2fa4236) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Add the `@homeflare/alchemy/caddy` subpath: a running Caddy's config, declared as Caddyfile text and applied through Caddy's own admin API.
|
|
160
|
+
|
|
161
|
+
- `CaddyConfig` / `CaddyConfigProvider` (`Caddy.Config`): the whole Caddyfile as one prop. Apply is `POST /adapt` (validate), `POST /load` with `text/caddyfile` (graceful reload), then `GET /config/`, which must hash to the adapted config, or the deploy fails. When Caddy refuses a config, it keeps the old one, and the error gives Caddy's reason and confirms whether the old config is still running. This includes Caddy's refusal that arrives in a 200 response after adapter warnings. Drift compares SHA-256 digests of canonical adapted JSON: declared, live and stored. A hand edit or a restart with a different file plans an update. A plan-time `/adapt` fails the plan on a bad Caddyfile. With no state, a running Caddy is adopted. `replace` is never planned. Delete never unloads or stops Caddy, and `retain` is the default.
|
|
162
|
+
- `caddyWithFile()`: the same Caddyfile is also written with launchd's `HostFile` to the file Caddy starts from, so a restart keeps it. The file is written first, then `/load`, and both are retained. `sourceFile` is sent as `Caddy-Config-Source-File` so SIGUSR1 reload-from-file keeps working. `docs/caddy.md` covers the order, `--resume`/autosave and the refused-config window.
|
|
163
|
+
- `CaddyAdmin`, `localCaddyAdmin()`, `caddyAdminLayer()` and `caddyProviders()`: every admin call goes through one injectable transport. The local transport uses `node:http` on both Bun and Node, accepts only loopback `http://` or `unix://`, and sends `Host`/`Origin` the way the Caddy CLI does. `hostHeader` covers narrowed `origins` and SSH-forwarded ports. It retries only refused connections and then rejects with `CaddyUnreachableError`. A stopped Caddy does not fail the plan, because its launchd job may be the fix: read and diff plan the load with a warning, and the apply fails until Caddy answers.
|
|
164
|
+
- Refused before anything is sent: an empty Caddyfile or one that adapts to no apps; literal secrets (a PEM key, a literal after `dns <provider>`, secret-named subdirectives, literal `Authorization` headers, token and password-hash shapes, and a `{$NAME:default}` whose default is one of these); and an adapted `admin` block that would turn the API off, move it off loopback or away from the transport (another port, socket or loopback address, or no address at all when the transport is not at Caddy's default), allow no Host the transport sends, set `enforce_origin` over a unix socket, enable `remote`, or pull config. Secrets go in `{env.NAME}` or `{file./path}` placeholders.
|
|
165
|
+
|
|
166
|
+
- [#71](https://github.com/taslabs-net/homeflare-kit/pull/71) [`73736ae`](https://github.com/taslabs-net/homeflare-kit/commit/73736aec0a686af36cb4ef7ecfba42d40e543fc7) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Add `MeshNode` (`Cloudflare.MeshNode`) and `fetchMeshNodeToken` to `@homeflare/alchemy/cloudflare`.
|
|
167
|
+
|
|
168
|
+
- **`MeshNode`** declares a Cloudflare Mesh node (a `warp_connector`) with `name` and `ha`. It never reads the node token, so the token never reaches Alchemy state. Alchemy's `Cloudflare.Tunnel.WarpConnector` fetches it on every read and stores it, and it has no `ha`. The attributes are `id`, `accountId`, `name`, `status` and `ha`.
|
|
169
|
+
- A `name` change renames the node in place (`PATCH`), keeping its id, token and enrolled replicas.
|
|
170
|
+
- `ha` is required and create-only (Cloudflare: "cannot be changed afterward"). A change replaces the node: delete-first while the name stays (names are unique per account), create-first when the name changes too. An account change is a create-first replace.
|
|
171
|
+
- **It defaults to `RemovalPolicy.retain`**, like the kit's other resources whose deletion breaks their consumers: deleting a node cuts every enrolled replica off the Mesh, and a new one means a new token and Mesh IPs. Dropping the declaration or `alchemy destroy` leaves the node live. Opt in with `.pipe(RemovalPolicy.destroy())`.
|
|
172
|
+
- Under that default a same-name `ha` change **refuses and writes nothing**: the engine keeps the old node, which still holds the name, and a create never reuses a node it did not create (that would record the wrong `ha`). The sentence names the ways on: deploy once with `.pipe(RemovalPolicy.destroy())` (delete-first), delete the old node by hand, or keep it (`alchemy state rm` the row, then `adopt(true)`; reverting `ha` alone refuses again). A create-first replace leaves the old node live. When a create-first replace's new name is already held by another node, the sentence does not call it the old node and says not to delete it. Every way back from inside a replace starts with `alchemy state rm`, because `adopt(true)` alone does not act on a `replacing` row; the 1013-retry sentence says so too. All measured through Alchemy's real plan/apply against the fake.
|
|
173
|
+
- A door (a node with no routes) is documented as `ha: false`: HA fails over routes, and each replica has its own Mesh IP. A second door is a second `MeshNode`.
|
|
174
|
+
- An existing node is adopted by exact name and returned `Unowned`. A create answered code 1013 after a clean lookup (distilled retries a create whose response was lost) names the node that appeared rather than blaming another tunnel type.
|
|
175
|
+
- `list` is empty and `nuke` skips the type, because Alchemy's WarpConnector already lists every `warp_connector`.
|
|
176
|
+
- **`fetchMeshNodeToken({ accountId, id | name })`** returns the node token `Redacted`, on demand, for a one-off enrolment step. Callers write it to a root-owned `0600` file on the node and nowhere else. An empty token fails, and a 403 names the Write permission the endpoint needs. Before any request it refuses the Global API Key, an empty API token, and a set `DISTILLED_DEBUG_HTTP` (distilled would print the token to stderr).
|
|
177
|
+
- Built on `@distilled.cloud/cloudflare`, the SDK Alchemy's own Cloudflare providers use. The `cloudflare@4.5.0` SDK cannot create an HA node. It is a new **required peer**, pinned to the version alchemy pins (`1.0.0-rc.12`): add it to your install line.
|
|
178
|
+
- `providers()` now also resolves Alchemy's Cloudflare credentials and account for `MeshNode`, the same way `Cloudflare.providers()` does (a profile, or `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID`).
|
|
179
|
+
|
|
180
|
+
Guide: `docs/mesh-node.md`.
|
|
181
|
+
|
|
182
|
+
## 0.6.0
|