@homeflare/alchemy 0.31.0 → 0.31.2

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.
Files changed (133) hide show
  1. package/README.md +5 -4
  2. package/dist/grafana/index.d.ts +6 -5
  3. package/dist/grafana/index.d.ts.map +1 -1
  4. package/dist/proxmox/acl-wire.d.ts +26 -0
  5. package/dist/proxmox/acl-wire.d.ts.map +1 -0
  6. package/dist/proxmox/acl.d.ts.map +1 -1
  7. package/dist/proxmox/api-token-form.d.ts +28 -26
  8. package/dist/proxmox/api-token-form.d.ts.map +1 -1
  9. package/dist/proxmox/api-token.d.ts +48 -89
  10. package/dist/proxmox/api-token.d.ts.map +1 -1
  11. package/dist/proxmox/ceph-fs-wire.d.ts +2 -2
  12. package/dist/proxmox/ceph-fs-wire.d.ts.map +1 -1
  13. package/dist/proxmox/ceph-osd-tree.d.ts +1 -1
  14. package/dist/proxmox/ceph-osd-tree.d.ts.map +1 -1
  15. package/dist/proxmox/ceph-osd-write.d.ts +2 -2
  16. package/dist/proxmox/ceph-osd-write.d.ts.map +1 -1
  17. package/dist/proxmox/ceph-pool-settle.d.ts +1 -1
  18. package/dist/proxmox/ceph-pool-settle.d.ts.map +1 -1
  19. package/dist/proxmox/client.d.ts +2 -2
  20. package/dist/proxmox/client.d.ts.map +1 -1
  21. package/dist/proxmox/credential-errors.d.ts +27 -0
  22. package/dist/proxmox/credential-errors.d.ts.map +1 -0
  23. package/dist/proxmox/credentials.d.ts +0 -38
  24. package/dist/proxmox/credentials.d.ts.map +1 -1
  25. package/dist/proxmox/distilled-guard.d.ts +31 -0
  26. package/dist/proxmox/distilled-guard.d.ts.map +1 -0
  27. package/dist/proxmox/distilled-pve.d.ts +11 -4
  28. package/dist/proxmox/distilled-pve.d.ts.map +1 -1
  29. package/dist/proxmox/generated/constraints/index.d.ts +1 -1
  30. package/dist/proxmox/generated/constraints/pve-access.d.ts +1 -1
  31. package/dist/proxmox/generated/constraints/pve-access.d.ts.map +1 -1
  32. package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts +1 -1
  33. package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts +1 -1
  34. package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts +1 -1
  35. package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts +1 -1
  36. package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts +1 -1
  37. package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts +1 -1
  38. package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts +1 -1
  39. package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts +1 -1
  40. package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts +1 -1
  41. package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts +1 -1
  42. package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts +1 -1
  43. package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts +1 -1
  44. package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts +1 -1
  45. package/dist/proxmox/generated/constraints/pve-pools.d.ts +1 -1
  46. package/dist/proxmox/generated/constraints/pve-storage.d.ts +1 -1
  47. package/dist/proxmox/group-wire.d.ts +68 -0
  48. package/dist/proxmox/group-wire.d.ts.map +1 -0
  49. package/dist/proxmox/group.d.ts +20 -24
  50. package/dist/proxmox/group.d.ts.map +1 -1
  51. package/dist/proxmox/index.js +730 -511
  52. package/dist/proxmox/index.js.map +40 -33
  53. package/dist/proxmox/lease-cache.d.ts +1 -1
  54. package/dist/proxmox/lease-cache.d.ts.map +1 -1
  55. package/dist/proxmox/lxc-lifecycle.d.ts +5 -5
  56. package/dist/proxmox/lxc-lifecycle.d.ts.map +1 -1
  57. package/dist/proxmox/mint.d.ts +50 -0
  58. package/dist/proxmox/mint.d.ts.map +1 -0
  59. package/dist/proxmox/network-apply-read.d.ts +2 -2
  60. package/dist/proxmox/network-apply-read.d.ts.map +1 -1
  61. package/dist/proxmox/pbs-notification-target-lifecycle.d.ts +2 -2
  62. package/dist/proxmox/pbs-sync-job-form.d.ts +2 -2
  63. package/dist/proxmox/provision-cli-fake.d.ts.map +1 -1
  64. package/dist/proxmox/resource.d.ts +4 -4
  65. package/dist/proxmox/role-wire.d.ts +28 -0
  66. package/dist/proxmox/role-wire.d.ts.map +1 -0
  67. package/dist/proxmox/role.d.ts +36 -52
  68. package/dist/proxmox/role.d.ts.map +1 -1
  69. package/dist/proxmox/sdn-apply-read.d.ts +1 -1
  70. package/dist/proxmox/sdn-apply-read.d.ts.map +1 -1
  71. package/dist/proxmox/unreadable-read.d.ts +50 -0
  72. package/dist/proxmox/unreadable-read.d.ts.map +1 -0
  73. package/dist/proxmox/user-wire.d.ts +47 -0
  74. package/dist/proxmox/user-wire.d.ts.map +1 -0
  75. package/dist/proxmox/user.d.ts +25 -32
  76. package/dist/proxmox/user.d.ts.map +1 -1
  77. package/dist/proxmox/zfs-pool-write.d.ts +2 -2
  78. package/dist/proxmox/zfs-pool-write.d.ts.map +1 -1
  79. package/dist/verify/fake-engine.d.ts +14 -0
  80. package/dist/verify/fake-engine.d.ts.map +1 -1
  81. package/docs/grafana.md +47 -17
  82. package/docs/upstream-conformance.md +14 -4
  83. package/package.json +2 -2
  84. package/src/grafana/index.ts +6 -5
  85. package/src/proxmox/acl-wire.ts +83 -0
  86. package/src/proxmox/acl.ts +36 -83
  87. package/src/proxmox/adopt-noop.test.ts +18 -0
  88. package/src/proxmox/api-token-adopt.test.ts +28 -12
  89. package/src/proxmox/api-token-form.ts +50 -93
  90. package/src/proxmox/api-token.ts +155 -133
  91. package/src/proxmox/client.ts +2 -7
  92. package/src/proxmox/constraints-forms.test.ts +7 -27
  93. package/src/proxmox/credential-errors.ts +33 -0
  94. package/src/proxmox/credentials.test.ts +29 -3
  95. package/src/proxmox/credentials.ts +4 -134
  96. package/src/proxmox/distilled-guard.ts +42 -0
  97. package/src/proxmox/distilled-pve.ts +15 -4
  98. package/src/proxmox/generated/constraints/index.ts +1 -1
  99. package/src/proxmox/generated/constraints/pve-access.ts +1 -4
  100. package/src/proxmox/generated/constraints/pve-cluster-backup.ts +1 -1
  101. package/src/proxmox/generated/constraints/pve-cluster-ceph.ts +1 -1
  102. package/src/proxmox/generated/constraints/pve-cluster-firewall.ts +1 -1
  103. package/src/proxmox/generated/constraints/pve-cluster-ha.ts +1 -1
  104. package/src/proxmox/generated/constraints/pve-cluster-metrics.ts +1 -1
  105. package/src/proxmox/generated/constraints/pve-cluster-notifications.ts +1 -1
  106. package/src/proxmox/generated/constraints/pve-cluster-replication.ts +1 -1
  107. package/src/proxmox/generated/constraints/pve-cluster-sdn.ts +1 -1
  108. package/src/proxmox/generated/constraints/pve-nodes-ceph.ts +1 -1
  109. package/src/proxmox/generated/constraints/pve-nodes-disks.ts +1 -1
  110. package/src/proxmox/generated/constraints/pve-nodes-lxc.ts +1 -1
  111. package/src/proxmox/generated/constraints/pve-nodes-network.ts +1 -1
  112. package/src/proxmox/generated/constraints/pve-nodes-qemu.ts +1 -1
  113. package/src/proxmox/generated/constraints/pve-pools.ts +1 -1
  114. package/src/proxmox/generated/constraints/pve-storage.ts +1 -1
  115. package/src/proxmox/group-wire.ts +117 -0
  116. package/src/proxmox/group.test.ts +110 -0
  117. package/src/proxmox/group.ts +127 -109
  118. package/src/proxmox/lease-cache.ts +2 -1
  119. package/src/proxmox/lxc-lifecycle.ts +2 -1
  120. package/src/proxmox/mint.ts +158 -0
  121. package/src/proxmox/network-apply.ts +1 -1
  122. package/src/proxmox/provision-cli-fake.ts +11 -0
  123. package/src/proxmox/provision-declare.test.ts +6 -1
  124. package/src/proxmox/read-failure-propagation.test.ts +183 -0
  125. package/src/proxmox/role-wire.ts +49 -0
  126. package/src/proxmox/role.test.ts +89 -0
  127. package/src/proxmox/role.ts +152 -104
  128. package/src/proxmox/unreadable-read.test.ts +83 -0
  129. package/src/proxmox/unreadable-read.ts +60 -0
  130. package/src/proxmox/user-wire.ts +148 -0
  131. package/src/proxmox/user.test.ts +142 -0
  132. package/src/proxmox/user.ts +136 -131
  133. package/src/verify/fake-engine.ts +23 -0
@@ -3,7 +3,7 @@ import { Resource } from "alchemy";
3
3
  import { isResolved } from "alchemy/Diff";
4
4
  import * as Provider from "alchemy/Provider";
5
5
  import * as access from "@distilled.cloud/proxmox/access";
6
- import * as Effect6 from "effect/Effect";
6
+ import * as Effect7 from "effect/Effect";
7
7
 
8
8
  // src/proxmox/constraint-guard.ts
9
9
  import * as Effect from "effect/Effect";
@@ -76,9 +76,6 @@ var PVE_ACCESS_CONSTRAINTS = {
76
76
  password: { maxLength: 64, minLength: 8, type: "string" },
77
77
  userid: { format: "pve-userid", maxLength: 64, required: true, type: "string" }
78
78
  },
79
- "pve:POST /access/users/{userid}/token/{tokenid}": {
80
- expire: { default: "same as user", minimum: 0, type: "integer" }
81
- },
82
79
  "pve:PUT /access/acl": {
83
80
  groups: { format: "pve-groupid-list", type: "string" },
84
81
  path: { required: true, type: "string" },
@@ -971,6 +968,40 @@ var guardForm = (key, form, presence) => Effect.suspend(() => {
971
968
  return found.length === 0 ? Effect.void : Effect.die(new Error(refusal(key, found)));
972
969
  });
973
970
 
971
+ // src/proxmox/acl-wire.ts
972
+ var normalize = (raw) => `/${raw.split("/").filter(Boolean).join("/")}`;
973
+ var SUBJECT_FIELD = { group: "groups", token: "tokens", user: "users" };
974
+ var ENDPOINT = "pve:PUT /access/acl";
975
+ var tuple = (props) => ({
976
+ path: normalize(props.path),
977
+ roles: props.roleid,
978
+ [SUBJECT_FIELD[props.type]]: props.ugid
979
+ });
980
+ var bind = (props) => ({
981
+ ...tuple(props),
982
+ propagate: props.propagate === false ? "0" : "1"
983
+ });
984
+ var asForm = (body) => Object.fromEntries(Object.entries(body).filter((e) => e[1] !== undefined));
985
+ var guardWrite = (props) => guardForm(ENDPOINT, asForm(bind(props)), true);
986
+ var identity = (grant) => [normalize(grant.path), grant.type, grant.ugid, grant.roleid].join(" ");
987
+ var find = (rows, props) => rows.find((row) => row.path === normalize(props.path) && row.type === props.type && row.ugid === props.ugid && row.roleid === props.roleid);
988
+ var propagates = (row) => {
989
+ const value = row.propagate;
990
+ return value === undefined || value === 1 || value === true || value === "1";
991
+ };
992
+ var attributesOf = (rows, props) => {
993
+ const row = find(rows, props);
994
+ return {
995
+ bound: row !== undefined,
996
+ path: normalize(props.path),
997
+ propagate: row !== undefined && propagates(row),
998
+ roleid: props.roleid,
999
+ type: props.type,
1000
+ ugid: props.ugid
1001
+ };
1002
+ };
1003
+ var matches = (attributes, props) => attributes.bound && attributes.propagate === (props.propagate !== false);
1004
+
974
1005
  // src/proxmox/distilled-pve.ts
975
1006
  import * as Effect5 from "effect/Effect";
976
1007
  import * as Result2 from "effect/Result";
@@ -983,13 +1014,24 @@ import * as Duration from "effect/Duration";
983
1014
  import * as Effect3 from "effect/Effect";
984
1015
  import * as Exit from "effect/Exit";
985
1016
 
986
- // src/proxmox/credentials.ts
1017
+ // src/proxmox/mint.ts
987
1018
  import * as Effect2 from "effect/Effect";
988
1019
  import * as Option from "effect/Option";
989
1020
  import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
990
1021
  import * as Headers from "effect/unstable/http/Headers";
991
1022
  import * as HttpClient from "effect/unstable/http/HttpClient";
992
1023
  import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
1024
+
1025
+ // src/proxmox/credential-errors.ts
1026
+ import * as Data from "effect/Data";
1027
+
1028
+ class PveCredentialDenied extends Data.TaggedError("PveCredentialDenied") {
1029
+ get message() {
1030
+ return `OpenBao GET /v1/${this.mount}/creds/${this.role} -> 403: ${this.detail}. This identity's ` + "AppRole has no grant for this role -- a refused read, not evidence the object is gone.";
1031
+ }
1032
+ }
1033
+
1034
+ // src/proxmox/mint.ts
993
1035
  var baoVariable = (env, name) => env[`BAO_${name}`] ?? env[`VAULT_${name}`] ?? "";
994
1036
  var mint = (target, role, env = process.env) => Effect2.gen(function* () {
995
1037
  const path = `${target.mount}/creds/${role}`;
@@ -1022,7 +1064,11 @@ var mint = (target, role, env = process.env) => Effect2.gen(function* () {
1022
1064
  }
1023
1065
  if (status < 200 || status >= 300) {
1024
1066
  const errors = parsed?.errors;
1025
- const detail = Array.isArray(errors) ? errors.map(String).join("; ") : "not an OpenBao error body";
1067
+ const isOpenBaoBody = Array.isArray(errors);
1068
+ const detail = isOpenBaoBody ? errors.map(String).join("; ") : "not an OpenBao error body";
1069
+ if (status === 403 && isOpenBaoBody) {
1070
+ return yield* Effect2.fail(new PveCredentialDenied({ detail, mount: target.mount, role }));
1071
+ }
1026
1072
  return yield* Effect2.fail(refuse(status, detail || "(no errors given)"));
1027
1073
  }
1028
1074
  const tokenId = parsed?.data?.token_id;
@@ -1172,49 +1218,22 @@ var runPve = (target, role, isWrite, op) => Effect5.gen(function* () {
1172
1218
  return yield* runPveWith(target, credential, isWrite, op);
1173
1219
  });
1174
1220
 
1221
+ // src/proxmox/unreadable-read.ts
1222
+ import * as Effect6 from "effect/Effect";
1223
+ var UNREADABLE = Symbol("pve-unreadable");
1224
+ var readOrUnreadable = (effect) => effect.pipe(Effect6.catchTag("PveCredentialDenied", () => Effect6.succeed(UNREADABLE)));
1225
+ var unreadableWarning = (type, identity2) => Effect6.logWarning(`${type} ${identity2}: the read was refused (no grant for this credential's role) -- ` + "reporting noop rather than forcing an update with nothing compared. Re-plan on a lane " + "that can read this object before trusting this row.");
1226
+
1175
1227
  // src/proxmox/acl.ts
1176
1228
  var ProxmoxAcl = Resource("Proxmox.Acl");
1177
- var normalize = (raw) => `/${raw.split("/").filter(Boolean).join("/")}`;
1178
- var SUBJECT_FIELD = { group: "groups", token: "tokens", user: "users" };
1179
- var ENDPOINT = "pve:PUT /access/acl";
1180
- var tuple = (props) => ({
1181
- path: normalize(props.path),
1182
- roles: props.roleid,
1183
- [SUBJECT_FIELD[props.type]]: props.ugid
1184
- });
1185
- var bind = (props) => ({
1186
- ...tuple(props),
1187
- propagate: props.propagate === false ? "0" : "1"
1188
- });
1189
- var asForm = (body) => Object.fromEntries(Object.entries(body).filter((e) => e[1] !== undefined));
1190
- var guardWrite = (props) => guardForm(ENDPOINT, asForm(bind(props)), true);
1191
- var identity = (grant) => [normalize(grant.path), grant.type, grant.ugid, grant.roleid].join(" ");
1192
- var find = (rows, props) => rows.find((row) => row.path === normalize(props.path) && row.type === props.type && row.ugid === props.ugid && row.roleid === props.roleid);
1193
- var propagates = (row) => {
1194
- const value = row.propagate;
1195
- return value === undefined || value === 1 || value === true || value === "1";
1196
- };
1197
- var attributesOf = (rows, props) => {
1198
- const row = find(rows, props);
1199
- return {
1200
- bound: row !== undefined,
1201
- path: normalize(props.path),
1202
- propagate: row !== undefined && propagates(row),
1203
- roleid: props.roleid,
1204
- type: props.type,
1205
- ugid: props.ugid
1206
- };
1207
- };
1208
- var matches = (attributes, props) => attributes.bound && attributes.propagate === (props.propagate !== false);
1209
- var readAttributes = (props) => runPve(props.target, "provision", false, access.listAccessAcl({})).pipe(Effect6.map((rows) => attributesOf(rows, props)), Effect6.orElseSucceed(() => {
1210
- return;
1211
- }));
1212
- var ProxmoxAclProvider = () => Provider.effect(ProxmoxAcl, Effect6.succeed(ProxmoxAcl.Provider.of({
1213
- list: () => Effect6.succeed([]),
1214
- read: Effect6.fn(function* ({ olds }) {
1215
- return yield* readAttributes(olds);
1229
+ var readAttributes = (props) => readOrUnreadable(runPve(props.target, "provision", false, access.listAccessAcl({}))).pipe(Effect7.map((rows) => rows === UNREADABLE ? UNREADABLE : attributesOf(rows, props)));
1230
+ var dropUnreadable = (live) => live === UNREADABLE ? undefined : live;
1231
+ var ProxmoxAclProvider = () => Provider.effect(ProxmoxAcl, Effect7.succeed(ProxmoxAcl.Provider.of({
1232
+ list: () => Effect7.succeed([]),
1233
+ read: Effect7.fn(function* ({ olds }) {
1234
+ return dropUnreadable(yield* readAttributes(olds));
1216
1235
  }),
1217
- diff: Effect6.fn(function* ({ news, output }) {
1236
+ diff: Effect7.fn(function* ({ news, output }) {
1218
1237
  if (output !== undefined && isResolved(news) && identity(news) !== identity(output)) {
1219
1238
  return { action: "replace" };
1220
1239
  }
@@ -1224,21 +1243,25 @@ var ProxmoxAclProvider = () => Provider.effect(ProxmoxAcl, Effect6.succeed(Proxm
1224
1243
  if (output === undefined)
1225
1244
  return;
1226
1245
  const live = yield* readAttributes(news);
1246
+ if (live === UNREADABLE) {
1247
+ yield* unreadableWarning("Proxmox.Acl", identity(news));
1248
+ return { action: "noop" };
1249
+ }
1227
1250
  return live !== undefined && matches(live, news) ? { action: "noop" } : { action: "update" };
1228
1251
  }),
1229
- reconcile: Effect6.fn(function* ({ news }) {
1230
- const before = yield* readAttributes(news);
1252
+ reconcile: Effect7.fn(function* ({ news }) {
1253
+ const before = dropUnreadable(yield* readAttributes(news));
1231
1254
  yield* guardWrite(news);
1232
1255
  if (before === undefined || !matches(before, news)) {
1233
1256
  yield* runPve(news.target, "provision", true, access.putAccessAcl(bind(news)));
1234
1257
  }
1235
- const after = yield* readAttributes(news);
1258
+ const after = dropUnreadable(yield* readAttributes(news));
1236
1259
  if (after === undefined || !after.bound) {
1237
- return yield* Effect6.die(new Error(`access/acl: the write returned no error but ${identity(news)} is still not ` + "bound. Either the PUT did nothing, or the read credential cannot see the " + "grant -- GET /access/acl is filtered to paths you may MODIFY permissions on, " + "so a read-only role is answered [] rather than 403."));
1260
+ return yield* Effect7.die(new Error(`access/acl: the write returned no error but ${identity(news)} is still not ` + "bound. Either the PUT did nothing, or the read credential cannot see the " + "grant -- GET /access/acl is filtered to paths you may MODIFY permissions on, " + "so a read-only role is answered [] rather than 403."));
1238
1261
  }
1239
1262
  return after;
1240
1263
  }),
1241
- delete: Effect6.fn(function* ({ olds }) {
1264
+ delete: Effect7.fn(function* ({ olds }) {
1242
1265
  const removal = { ...tuple(olds), delete: "1" };
1243
1266
  yield* runPve(olds.target, "provision", true, access.putAccessAcl(removal));
1244
1267
  })
@@ -1295,8 +1318,10 @@ var alertmanagerAlertBody = (options = {}) => {
1295
1318
  };
1296
1319
  // src/proxmox/api-token.ts
1297
1320
  import { Resource as Resource2 } from "alchemy";
1321
+ import { isResolved as isResolved2 } from "alchemy/Diff";
1298
1322
  import * as Provider2 from "alchemy/Provider";
1299
- import * as Effect10 from "effect/Effect";
1323
+ import * as access2 from "@distilled.cloud/proxmox/access";
1324
+ import * as Effect8 from "effect/Effect";
1300
1325
 
1301
1326
  // src/proxmox/values.ts
1302
1327
  var num = (value, fallback) => typeof value === "number" ? value : fallback;
@@ -1340,45 +1365,90 @@ var withClears = (fields, clear) => {
1340
1365
  };
1341
1366
 
1342
1367
  // src/proxmox/api-token-form.ts
1368
+ var API_TOKEN_UPDATE = "pve:PUT /access/users/{userid}/token/{tokenid}";
1343
1369
  var fullTokenid = (props) => `${props.userid}!${props.tokenid}`;
1344
1370
  var shape = (props) => ({
1345
1371
  comment: props.comment ?? "",
1346
- expire: `${props.expire}`,
1347
- privsep: props.privsep ? "1" : "0"
1372
+ expire: String(props.expire),
1373
+ privsep: props.privsep ? "1" : "0",
1374
+ tokenid: props.tokenid,
1375
+ userid: props.userid
1348
1376
  });
1349
- var apiTokenSpec = {
1350
- readRole: "provision",
1351
- attributes: (live, props) => {
1352
- const expire = live["expire"];
1353
- const privsep = live["privsep"];
1354
- if (expire === undefined && privsep === undefined)
1355
- return;
1356
- return {
1357
- comment: text(live["comment"]),
1358
- expire: int(expire, 0),
1359
- fullTokenid: fullTokenid(props),
1360
- privsep: bool(privsep, true),
1361
- tokenid: props.tokenid,
1362
- userid: props.userid
1363
- };
1364
- },
1365
- collection: (props) => `access/users/${props.userid}/token/${props.tokenid}`,
1366
- createForm: shape,
1367
- endpoint: {
1368
- create: "pve:POST /access/users/{userid}/token/{tokenid}",
1369
- update: "pve:PUT /access/users/{userid}/token/{tokenid}"
1370
- },
1371
- matches: (attributes, props) => attributes.comment === (props.comment ?? "") && attributes.expire === props.expire && attributes.privsep === props.privsep,
1372
- path: (props) => `access/users/${props.userid}/token/${props.tokenid}`,
1373
- updateForm: shape
1377
+ var attributesOf2 = (live, props) => {
1378
+ if (live.expire === undefined && live.privsep === undefined)
1379
+ return;
1380
+ return {
1381
+ comment: text(live.comment),
1382
+ expire: int(live.expire, 0),
1383
+ fullTokenid: fullTokenid(props),
1384
+ privsep: bool(live.privsep, true),
1385
+ tokenid: props.tokenid,
1386
+ userid: props.userid
1387
+ };
1374
1388
  };
1389
+ var matches2 = (attributes, props) => attributes.comment === (props.comment ?? "") && attributes.expire === props.expire && attributes.privsep === props.privsep;
1390
+
1391
+ // src/proxmox/distilled-guard.ts
1392
+ var asForm2 = (body) => Object.fromEntries(Object.entries(body).filter((e) => typeof e[1] === "string"));
1393
+ var guardWrite2 = (endpoint, body, presence) => guardForm(endpoint, asForm2(body), presence);
1394
+
1395
+ // src/proxmox/api-token.ts
1396
+ var ProxmoxApiToken = Resource2("Proxmox.ApiToken", {
1397
+ defaultRemovalPolicy: "retain"
1398
+ });
1399
+ var readToken = (props) => {
1400
+ const label = { tokenid: props.tokenid, userid: props.userid };
1401
+ return readOrUnreadable(runPve(props.target, "provision", false, access2.getAccessUserToken(label))).pipe(Effect8.map((live) => live === UNREADABLE ? UNREADABLE : attributesOf2(live, props)));
1402
+ };
1403
+ var dropUnreadable2 = (live) => live === UNREADABLE ? undefined : live;
1404
+ var ProxmoxApiTokenProvider = () => Provider2.effect(ProxmoxApiToken, Effect8.succeed(ProxmoxApiToken.Provider.of({
1405
+ list: () => Effect8.succeed([]),
1406
+ read: Effect8.fn(function* ({ olds }) {
1407
+ return dropUnreadable2(yield* readToken(olds));
1408
+ }),
1409
+ diff: Effect8.fn(function* ({ news, output }) {
1410
+ if (!isResolved2(news))
1411
+ return;
1412
+ yield* guardWrite2(API_TOKEN_UPDATE, shape(news), false);
1413
+ if (output === undefined)
1414
+ return;
1415
+ const live = yield* readToken(news);
1416
+ if (live === UNREADABLE) {
1417
+ yield* unreadableWarning("Proxmox.ApiToken", `${news.userid}!${news.tokenid}`);
1418
+ return { action: "noop" };
1419
+ }
1420
+ return live !== undefined && matches2(live, news) ? { action: "noop" } : { action: "update" };
1421
+ }),
1422
+ reconcile: Effect8.fn(function* ({ news }) {
1423
+ const before = dropUnreadable2(yield* readToken(news));
1424
+ if (before === undefined) {
1425
+ return yield* Effect8.die(new Error(`${news.userid}!${news.tokenid}: this resource does not create API tokens. PVE ` + "returns the secret only in the create response and it cannot be stored, so a " + "token made here would be a live credential nobody holds. Create it with " + "`pveum user token add` and capture the value, or mint a short-lived one from " + "the OpenBao proxmox mount (`bao read proxmox-c1/creds/<role>`) -- then declare " + "it here to manage it."));
1426
+ }
1427
+ yield* guardWrite2(API_TOKEN_UPDATE, shape(news), false);
1428
+ if (!matches2(before, news)) {
1429
+ yield* runPve(news.target, "provision", true, access2.putAccessUserToken(shape(news)));
1430
+ }
1431
+ const after = dropUnreadable2(yield* readToken(news));
1432
+ if (after === undefined) {
1433
+ return yield* Effect8.die(new Error(`${news.userid}!${news.tokenid}: the write returned no error but the token is ` + 'still absent. PVE wraps every answer in {"data":...} and can report success ' + "on a call that did nothing -- read back rather than trusting the status code."));
1434
+ }
1435
+ return after;
1436
+ }),
1437
+ delete: Effect8.fn(function* ({ olds }) {
1438
+ yield* runPve(olds.target, "provision", true, access2.deleteAccessUserToken({ tokenid: olds.tokenid, userid: olds.userid }));
1439
+ })
1440
+ })));
1441
+ // src/proxmox/backup-job.ts
1442
+ import { Resource as Resource3 } from "alchemy";
1443
+ import * as Provider3 from "alchemy/Provider";
1444
+ import * as Effect12 from "effect/Effect";
1375
1445
 
1376
1446
  // src/proxmox/resource.ts
1377
- import { isResolved as isResolved2 } from "alchemy/Diff";
1378
- import * as Effect9 from "effect/Effect";
1447
+ import { isResolved as isResolved3 } from "alchemy/Diff";
1448
+ import * as Effect11 from "effect/Effect";
1379
1449
 
1380
1450
  // src/proxmox/client.ts
1381
- import * as Effect7 from "effect/Effect";
1451
+ import * as Effect9 from "effect/Effect";
1382
1452
  import * as HttpClient5 from "effect/unstable/http/HttpClient";
1383
1453
  import * as HttpClientRequest2 from "effect/unstable/http/HttpClientRequest";
1384
1454
  class PveError extends Error {
@@ -1409,69 +1479,69 @@ var buildRequest = (method, apiBase, path, credential, target, form) => {
1409
1479
  const url = `${apiBase}/${path}`;
1410
1480
  return HttpClientRequest2.make(method)(url).pipe(HttpClientRequest2.setHeaders({ Authorization: authorization(credential, target) }), form === undefined ? (self) => self : HttpClientRequest2.bodyText(encode(form), "application/x-www-form-urlencoded"));
1411
1481
  };
1412
- var executeOne = (target, credential, method, path, form, apiBase) => Effect7.gen(function* () {
1482
+ var executeOne = (target, credential, method, path, form, apiBase) => Effect9.gen(function* () {
1413
1483
  const client = yield* HttpClient5.HttpClient;
1414
1484
  const request = buildRequest(method, apiBase, path, credential, target, form);
1415
- return yield* client.execute(request).pipe(Effect7.mapError((cause) => new PveError(0, method, path, String(cause))));
1485
+ return yield* client.execute(request).pipe(Effect9.mapError((cause) => new PveError(0, method, path, String(cause))));
1416
1486
  });
1417
- var readResponse = (response, method, path) => Effect7.gen(function* () {
1487
+ var readResponse = (response, method, path) => Effect9.gen(function* () {
1418
1488
  if (response.status < 200 || response.status >= 300) {
1419
- const detail = yield* response.text.pipe(Effect7.orElseSucceed(() => ""));
1420
- return yield* Effect7.fail(new PveError(response.status, method, path, detail.slice(0, 300)));
1489
+ const detail = yield* response.text.pipe(Effect9.orElseSucceed(() => ""));
1490
+ return yield* Effect9.fail(new PveError(response.status, method, path, detail.slice(0, 300)));
1421
1491
  }
1422
- const body = yield* response.json.pipe(Effect7.mapError((cause) => new PveError(0, method, path, String(cause))));
1492
+ const body = yield* response.json.pipe(Effect9.mapError((cause) => new PveError(0, method, path, String(cause))));
1423
1493
  return body.data;
1424
1494
  });
1425
- var pveWith = (target, credential, method, path, form) => Effect7.gen(function* () {
1426
- const response = target.scheme === "pve" ? yield* executeOnCluster(target, method, path, (apiBase) => buildRequest(method, apiBase, path, credential, target, form)).pipe(Effect7.mapError((failure) => failure.tag === "cluster" ? clusterExhausted(failure.method, failure.path, failure.attempts) : new PveError(0, method, path, String(failure)))) : yield* executeOne(target, credential, method, path, form, target.api);
1495
+ var pveWith = (target, credential, method, path, form) => Effect9.gen(function* () {
1496
+ const response = target.scheme === "pve" ? yield* executeOnCluster(target, method, path, (apiBase) => buildRequest(method, apiBase, path, credential, target, form)).pipe(Effect9.mapError((failure) => failure.tag === "cluster" ? clusterExhausted(failure.method, failure.path, failure.attempts) : new PveError(0, method, path, String(failure)))) : yield* executeOne(target, credential, method, path, form, target.api);
1427
1497
  return yield* readResponse(response, method, path);
1428
1498
  });
1429
- var pveEnvelopeWith = (target, credential, method, path) => Effect7.gen(function* () {
1430
- const response = yield* executeOnCluster(target, method, path, (apiBase) => buildRequest(method, apiBase, path, credential, target)).pipe(Effect7.mapError((failure) => failure.tag === "cluster" ? clusterExhausted(failure.method, failure.path, failure.attempts) : new PveError(0, method, path, String(failure))));
1499
+ var pveEnvelopeWith = (target, credential, method, path) => Effect9.gen(function* () {
1500
+ const response = yield* executeOnCluster(target, method, path, (apiBase) => buildRequest(method, apiBase, path, credential, target)).pipe(Effect9.mapError((failure) => failure.tag === "cluster" ? clusterExhausted(failure.method, failure.path, failure.attempts) : new PveError(0, method, path, String(failure))));
1431
1501
  if (response.status < 200 || response.status >= 300) {
1432
- const detail = yield* response.text.pipe(Effect7.orElseSucceed(() => ""));
1433
- return yield* Effect7.fail(new PveError(response.status, method, path, detail.slice(0, 300)));
1502
+ const detail = yield* response.text.pipe(Effect9.orElseSucceed(() => ""));
1503
+ return yield* Effect9.fail(new PveError(response.status, method, path, detail.slice(0, 300)));
1434
1504
  }
1435
- return yield* response.json.pipe(Effect7.mapError((cause) => new PveError(0, method, path, String(cause))));
1505
+ return yield* response.json.pipe(Effect9.mapError((cause) => new PveError(0, method, path, String(cause))));
1436
1506
  });
1437
- var pve = (target, role, method, path, form) => Effect7.gen(function* () {
1507
+ var pve = (target, role, method, path, form) => Effect9.gen(function* () {
1438
1508
  const credential = yield* leased(target, role);
1439
1509
  return yield* pveWith(target, credential, method, path, form);
1440
1510
  });
1441
1511
 
1442
1512
  // src/proxmox/resource-guard.ts
1443
- import * as Effect8 from "effect/Effect";
1513
+ import * as Effect10 from "effect/Effect";
1444
1514
  var specGuards = (spec) => {
1445
1515
  const endpointsOf = (props) => typeof spec.endpoint === "function" ? spec.endpoint(props) : spec.endpoint;
1446
- const guardCreate = (props, presence) => Effect8.suspend(() => {
1516
+ const guardCreate = (props, presence) => Effect10.suspend(() => {
1447
1517
  const create = endpointsOf(props)?.create;
1448
- return create === undefined ? Effect8.void : guardForm(create, spec.createForm(props), presence);
1518
+ return create === undefined ? Effect10.void : guardForm(create, spec.createForm(props), presence);
1449
1519
  });
1450
- const guardUpdate = (props) => Effect8.suspend(() => {
1520
+ const guardUpdate = (props) => Effect10.suspend(() => {
1451
1521
  const update = endpointsOf(props)?.update;
1452
1522
  if (update === undefined)
1453
- return Effect8.void;
1523
+ return Effect10.void;
1454
1524
  const form = spec.updateForm?.(props);
1455
- return form === undefined ? Effect8.void : guardForm(update, form, false);
1525
+ return form === undefined ? Effect10.void : guardForm(update, form, false);
1456
1526
  });
1457
1527
  return { guardCreate, guardUpdate };
1458
1528
  };
1459
1529
 
1460
1530
  // src/proxmox/update-guard.ts
1461
- var formToSend = (matches2, live, props, form) => matches2(live, props) || Object.keys(form).length === 0 ? undefined : form;
1531
+ var formToSend = (matches3, live, props, form) => matches3(live, props) || Object.keys(form).length === 0 ? undefined : form;
1462
1532
 
1463
1533
  // src/proxmox/resource.ts
1464
1534
  var pveOperations = (spec) => {
1465
1535
  const { guardCreate, guardUpdate } = specGuards(spec);
1466
- const read = (props) => pve(props.target, spec.readRole ?? "read", "GET", spec.path(props)).pipe(Effect9.map((data) => data == null ? undefined : spec.attributes(data, props)), Effect9.orElseSucceed(() => {
1536
+ const read = (props) => pve(props.target, spec.readRole ?? "read", "GET", spec.path(props)).pipe(Effect11.map((data) => data == null ? undefined : spec.attributes(data, props)), Effect11.orElseSucceed(() => {
1467
1537
  return;
1468
1538
  }));
1469
1539
  return {
1470
1540
  guardCreate,
1471
1541
  guardUpdate,
1472
1542
  read,
1473
- diff: (news, output) => Effect9.gen(function* () {
1474
- if (!isResolved2(news))
1543
+ diff: (news, output) => Effect11.gen(function* () {
1544
+ if (!isResolved3(news))
1475
1545
  return;
1476
1546
  yield* guardCreate(news, output === undefined);
1477
1547
  yield* guardUpdate(news);
@@ -1486,7 +1556,7 @@ var pveOperations = (spec) => {
1486
1556
  return { action: "noop" };
1487
1557
  return spec.updateForm === undefined ? { action: "replace" } : { action: "update" };
1488
1558
  }),
1489
- reconcile: (news) => Effect9.gen(function* () {
1559
+ reconcile: (news) => Effect11.gen(function* () {
1490
1560
  const live = yield* read(news);
1491
1561
  yield* guardCreate(news, live === undefined);
1492
1562
  yield* guardUpdate(news);
@@ -1500,7 +1570,7 @@ var pveOperations = (spec) => {
1500
1570
  }
1501
1571
  const after = yield* read(news);
1502
1572
  if (after === undefined) {
1503
- return yield* Effect9.die(new Error(`${spec.path(news)}: the write returned no error but the object is still absent. ` + 'PVE wraps every answer in {"data":...} and can report success on a call that did ' + "nothing -- read back rather than trusting the status code."));
1573
+ return yield* Effect11.die(new Error(`${spec.path(news)}: the write returned no error but the object is still absent. ` + 'PVE wraps every answer in {"data":...} and can report success on a call that did ' + "nothing -- read back rather than trusting the status code."));
1504
1574
  }
1505
1575
  return after;
1506
1576
  }),
@@ -1510,7 +1580,7 @@ var pveOperations = (spec) => {
1510
1580
  var pveHandlers = (spec) => {
1511
1581
  const ops = pveOperations(spec);
1512
1582
  return {
1513
- list: () => Effect9.succeed([]),
1583
+ list: () => Effect11.succeed([]),
1514
1584
  read: ({ olds }) => ops.read(olds),
1515
1585
  diff: ({ news, output }) => ops.diff(news, output),
1516
1586
  reconcile: ({ news }) => ops.reconcile(news),
@@ -1518,26 +1588,7 @@ var pveHandlers = (spec) => {
1518
1588
  };
1519
1589
  };
1520
1590
 
1521
- // src/proxmox/api-token.ts
1522
- var ProxmoxApiToken = Resource2("Proxmox.ApiToken", {
1523
- defaultRemovalPolicy: "retain"
1524
- });
1525
- var ops = pveOperations(apiTokenSpec);
1526
- var handlers = {
1527
- ...pveHandlers(apiTokenSpec),
1528
- reconcile: Effect10.fn(function* ({ news }) {
1529
- const live = yield* ops.read(news);
1530
- if (live === undefined) {
1531
- return yield* Effect10.die(new Error(`${news.userid}!${news.tokenid}: this resource does not create API tokens. PVE returns ` + "the secret only in the create response and it cannot be stored, so a token made here " + "would be a live credential nobody holds. Create it with `pveum user token add` and " + "capture the value, or mint a short-lived one from the OpenBao proxmox mount " + "(`bao read proxmox-c1/creds/<role>`) -- then declare it here to manage it."));
1532
- }
1533
- return yield* ops.reconcile(news);
1534
- })
1535
- };
1536
- var ProxmoxApiTokenProvider = () => Provider2.effect(ProxmoxApiToken, Effect10.succeed(ProxmoxApiToken.Provider.of(handlers)));
1537
1591
  // src/proxmox/backup-job.ts
1538
- import { Resource as Resource3 } from "alchemy";
1539
- import * as Provider3 from "alchemy/Provider";
1540
- import * as Effect11 from "effect/Effect";
1541
1592
  var ProxmoxBackupJob = Resource3("Proxmox.BackupJob");
1542
1593
  var shape2 = (props) => ({
1543
1594
  all: props.all === true ? "1" : "0",
@@ -1555,7 +1606,7 @@ var shape2 = (props) => ({
1555
1606
  ...props.storage === undefined ? {} : { storage: props.storage },
1556
1607
  ...props.vmid === undefined ? {} : { vmid: guestList(props.vmid) }
1557
1608
  });
1558
- var handlers2 = pveHandlers({
1609
+ var handlers = pveHandlers({
1559
1610
  attributes: (live, props) => ({
1560
1611
  all: bool(live["all"], false),
1561
1612
  comment: text(live["comment"], ""),
@@ -1581,11 +1632,11 @@ var handlers2 = pveHandlers({
1581
1632
  path: (props) => `cluster/backup/${props.id}`,
1582
1633
  updateForm: shape2
1583
1634
  });
1584
- var ProxmoxBackupJobProvider = () => Provider3.effect(ProxmoxBackupJob, Effect11.succeed(ProxmoxBackupJob.Provider.of(handlers2)));
1635
+ var ProxmoxBackupJobProvider = () => Provider3.effect(ProxmoxBackupJob, Effect12.succeed(ProxmoxBackupJob.Provider.of(handlers)));
1585
1636
  // src/proxmox/ceph-daemon.ts
1586
1637
  import { Resource as Resource4 } from "alchemy";
1587
1638
  import * as Provider4 from "alchemy/Provider";
1588
- import * as Effect12 from "effect/Effect";
1639
+ import * as Effect13 from "effect/Effect";
1589
1640
 
1590
1641
  // src/proxmox/ceph-daemon-form.ts
1591
1642
  var DAEMON_ENDPOINTS = {
@@ -1628,7 +1679,7 @@ var daemonAttributes = (live, props) => {
1628
1679
  var ProxmoxCephDaemon = Resource4("Proxmox.CephDaemon", {
1629
1680
  defaultRemovalPolicy: "retain"
1630
1681
  });
1631
- var handlers3 = pveHandlers({
1682
+ var handlers2 = pveHandlers({
1632
1683
  attributes: daemonAttributes,
1633
1684
  collection: daemonPath,
1634
1685
  createForm,
@@ -1636,16 +1687,16 @@ var handlers3 = pveHandlers({
1636
1687
  matches: () => true,
1637
1688
  path: collectionPath
1638
1689
  });
1639
- var ProxmoxCephDaemonProvider = () => Provider4.effect(ProxmoxCephDaemon, Effect12.succeed(ProxmoxCephDaemon.Provider.of({
1640
- ...handlers3,
1690
+ var ProxmoxCephDaemonProvider = () => Provider4.effect(ProxmoxCephDaemon, Effect13.succeed(ProxmoxCephDaemon.Provider.of({
1691
+ ...handlers2,
1641
1692
  delete: ({ olds }) => pve(olds.target, "provision", "DELETE", daemonPath(olds))
1642
1693
  })));
1643
1694
  // src/proxmox/ceph-flag.ts
1644
1695
  import { Resource as Resource5 } from "alchemy";
1645
1696
  import * as Provider5 from "alchemy/Provider";
1646
- import * as Effect13 from "effect/Effect";
1697
+ import * as Effect14 from "effect/Effect";
1647
1698
  var ProxmoxCephFlag = Resource5("Proxmox.CephFlag");
1648
- var handlers4 = pveHandlers({
1699
+ var handlers3 = pveHandlers({
1649
1700
  attributes: (live, props) => ({ flag: props.flag, value: bool(live) }),
1650
1701
  collection: () => "cluster/ceph/flags",
1651
1702
  createForm: (props) => ({ [props.flag]: flag(props.value) ?? "0" }),
@@ -1654,25 +1705,25 @@ var handlers4 = pveHandlers({
1654
1705
  path: (props) => `cluster/ceph/flags/${props.flag}`,
1655
1706
  updateForm: (props) => ({ value: flag(props.value) ?? "0" })
1656
1707
  });
1657
- var ProxmoxCephFlagProvider = () => Provider5.effect(ProxmoxCephFlag, Effect13.succeed(ProxmoxCephFlag.Provider.of({
1658
- ...handlers4,
1659
- delete: () => Effect13.void,
1660
- reconcile: Effect13.fn(function* ({ news }) {
1661
- const after = yield* handlers4.reconcile({ news });
1708
+ var ProxmoxCephFlagProvider = () => Provider5.effect(ProxmoxCephFlag, Effect14.succeed(ProxmoxCephFlag.Provider.of({
1709
+ ...handlers3,
1710
+ delete: () => Effect14.void,
1711
+ reconcile: Effect14.fn(function* ({ news }) {
1712
+ const after = yield* handlers3.reconcile({ news });
1662
1713
  if (after.value !== news.value) {
1663
- return yield* Effect13.die(new Error(`cluster/ceph/flags/${news.flag}: the PUT returned no error but ceph still ` + `reports the flag ${after.value ? "set" : "clear"} rather than ` + `${news.value ? "set" : "clear"}. This endpoint's success is documented as a ` + "null body, so the status code proves nothing -- check `ceph osd dump | " + "head -1` and the cluster log. `pause` in particular is two ceph flags read " + "as one; see the header."));
1714
+ return yield* Effect14.die(new Error(`cluster/ceph/flags/${news.flag}: the PUT returned no error but ceph still ` + `reports the flag ${after.value ? "set" : "clear"} rather than ` + `${news.value ? "set" : "clear"}. This endpoint's success is documented as a ` + "null body, so the status code proves nothing -- check `ceph osd dump | " + "head -1` and the cluster log. `pause` in particular is two ceph flags read " + "as one; see the header."));
1664
1715
  }
1665
1716
  return after;
1666
1717
  })
1667
1718
  })));
1668
1719
  // src/proxmox/ceph-fs.ts
1669
1720
  import { Resource as Resource6 } from "alchemy";
1670
- import { isResolved as isResolved3 } from "alchemy/Diff";
1721
+ import { isResolved as isResolved4 } from "alchemy/Diff";
1671
1722
  import * as Provider6 from "alchemy/Provider";
1672
- import * as Effect15 from "effect/Effect";
1723
+ import * as Effect16 from "effect/Effect";
1673
1724
 
1674
1725
  // src/proxmox/ceph-fs-wire.ts
1675
- import * as Effect14 from "effect/Effect";
1726
+ import * as Effect15 from "effect/Effect";
1676
1727
  var readRow = (live, props) => {
1677
1728
  const row = (Array.isArray(live) ? live : []).filter((entry) => typeof entry === "object" && entry !== null).find((entry) => entry["name"] === props.name);
1678
1729
  if (row === undefined)
@@ -1700,29 +1751,29 @@ var destroyPath = (props) => {
1700
1751
  ].filter((part) => part !== undefined);
1701
1752
  return query.length === 0 ? objectPath(props) : `${objectPath(props)}?${query.join("&")}`;
1702
1753
  };
1703
- var settle = (target, node, upid, what) => Effect14.gen(function* () {
1754
+ var settle = (target, node, upid, what) => Effect15.gen(function* () {
1704
1755
  for (let attempt = 0;attempt < POLL_ATTEMPTS; attempt += 1) {
1705
- const status = yield* pve(target, "read", "GET", `nodes/${node}/tasks/${encodeURIComponent(upid)}/status`).pipe(Effect14.orElseSucceed(() => {
1756
+ const status = yield* pve(target, "read", "GET", `nodes/${node}/tasks/${encodeURIComponent(upid)}/status`).pipe(Effect15.orElseSucceed(() => {
1706
1757
  return;
1707
1758
  }));
1708
1759
  if (status?.status === "stopped") {
1709
1760
  const exit = text(status.exitstatus);
1710
1761
  if (exit === "OK")
1711
1762
  return;
1712
- return yield* Effect14.die(new Error(`${what}: PVE task ${upid} finished with "${exit === "" ? "no exit status" : exit}". ` + "The write returned HTTP 200 because it only forked the worker -- the real error is " + `in \`pvesh get /nodes/${node}/tasks/${upid}/log\`.`));
1763
+ return yield* Effect15.die(new Error(`${what}: PVE task ${upid} finished with "${exit === "" ? "no exit status" : exit}". ` + "The write returned HTTP 200 because it only forked the worker -- the real error is " + `in \`pvesh get /nodes/${node}/tasks/${upid}/log\`.`));
1713
1764
  }
1714
- yield* Effect14.sleep(POLL_SECONDS * 1000);
1765
+ yield* Effect15.sleep(POLL_SECONDS * 1000);
1715
1766
  }
1716
- return yield* Effect14.die(new Error(`${what}: PVE task ${upid} was still running after ` + `${String(POLL_ATTEMPTS * POLL_SECONDS)}s, or its ` + "status could not be read. Check the task log, and check that the `read` role holds " + `Sys.Audit on /nodes/${node} -- each call mints a new token, so the poller is never the ` + "task owner and is answered 403 rather than told to wait."));
1767
+ return yield* Effect15.die(new Error(`${what}: PVE task ${upid} was still running after ` + `${String(POLL_ATTEMPTS * POLL_SECONDS)}s, or its ` + "status could not be read. Check the task log, and check that the `read` role holds " + `Sys.Audit on /nodes/${node} -- each call mints a new token, so the poller is never the ` + "task owner and is answered 403 rather than told to wait."));
1717
1768
  });
1718
- var createFs = (props) => Effect14.gen(function* () {
1769
+ var createFs = (props) => Effect15.gen(function* () {
1719
1770
  const upid = text(yield* pve(props.target, "provision", "POST", objectPath(props), createForm2(props)));
1720
1771
  if (upid === "") {
1721
- return yield* Effect14.die(new Error(`${objectPath(props)}: POST returned no task id. PVE wraps every answer in ` + '{"data":...} and can report success on a call that did nothing.'));
1772
+ return yield* Effect15.die(new Error(`${objectPath(props)}: POST returned no task id. PVE wraps every answer in ` + '{"data":...} and can report success on a call that did nothing.'));
1722
1773
  }
1723
1774
  yield* settle(props.target, props.node, upid, objectPath(props));
1724
1775
  });
1725
- var destroyFs = (props) => Effect14.gen(function* () {
1776
+ var destroyFs = (props) => Effect15.gen(function* () {
1726
1777
  const upid = text(yield* pve(props.target, "provision", "DELETE", destroyPath(props)));
1727
1778
  if (upid !== "")
1728
1779
  yield* settle(props.target, props.node, upid, destroyPath(props));
@@ -1734,7 +1785,7 @@ var notDestroyed = (props) => new Error(`${objectPath(props)}: the destroy task
1734
1785
  var ProxmoxCephFs = Resource6("Proxmox.CephFs", {
1735
1786
  defaultRemovalPolicy: "retain"
1736
1787
  });
1737
- var ops2 = pveOperations({
1788
+ var ops = pveOperations({
1738
1789
  attributes: readRow,
1739
1790
  collection: objectPath,
1740
1791
  createForm: createForm2,
@@ -1742,44 +1793,44 @@ var ops2 = pveOperations({
1742
1793
  matches: () => true,
1743
1794
  path: (props) => `nodes/${props.node}/ceph/fs`
1744
1795
  });
1745
- var ProxmoxCephFsProvider = () => Provider6.effect(ProxmoxCephFs, Effect15.succeed(ProxmoxCephFs.Provider.of({
1746
- list: () => Effect15.succeed([]),
1747
- read: Effect15.fn(function* ({ olds }) {
1748
- return yield* ops2.read(olds);
1796
+ var ProxmoxCephFsProvider = () => Provider6.effect(ProxmoxCephFs, Effect16.succeed(ProxmoxCephFs.Provider.of({
1797
+ list: () => Effect16.succeed([]),
1798
+ read: Effect16.fn(function* ({ olds }) {
1799
+ return yield* ops.read(olds);
1749
1800
  }),
1750
- diff: Effect15.fn(function* ({ news, output }) {
1751
- if (output !== undefined && isResolved3(news) && news.name !== output.name) {
1801
+ diff: Effect16.fn(function* ({ news, output }) {
1802
+ if (output !== undefined && isResolved4(news) && news.name !== output.name) {
1752
1803
  return { action: "replace" };
1753
1804
  }
1754
- return yield* ops2.diff(news, output);
1805
+ return yield* ops.diff(news, output);
1755
1806
  }),
1756
- reconcile: Effect15.fn(function* ({ news }) {
1757
- const live = yield* ops2.read(news);
1807
+ reconcile: Effect16.fn(function* ({ news }) {
1808
+ const live = yield* ops.read(news);
1758
1809
  if (live !== undefined)
1759
1810
  return live;
1760
1811
  yield* createFs(news);
1761
- const after = yield* ops2.read(news);
1812
+ const after = yield* ops.read(news);
1762
1813
  if (after === undefined)
1763
- return yield* Effect15.die(notCreated(news));
1814
+ return yield* Effect16.die(notCreated(news));
1764
1815
  return after;
1765
1816
  }),
1766
- delete: Effect15.fn(function* ({ olds }) {
1767
- if ((yield* ops2.read(olds)) === undefined)
1817
+ delete: Effect16.fn(function* ({ olds }) {
1818
+ if ((yield* ops.read(olds)) === undefined)
1768
1819
  return;
1769
1820
  yield* destroyFs(olds);
1770
- const after = yield* ops2.read(olds);
1821
+ const after = yield* ops.read(olds);
1771
1822
  if (after !== undefined)
1772
- return yield* Effect15.die(notDestroyed(olds));
1823
+ return yield* Effect16.die(notDestroyed(olds));
1773
1824
  })
1774
1825
  })));
1775
1826
  // src/proxmox/ceph-osd.ts
1776
1827
  import { Resource as Resource7 } from "alchemy";
1777
- import { isResolved as isResolved4 } from "alchemy/Diff";
1828
+ import { isResolved as isResolved5 } from "alchemy/Diff";
1778
1829
  import * as Provider7 from "alchemy/Provider";
1779
- import * as Effect18 from "effect/Effect";
1830
+ import * as Effect19 from "effect/Effect";
1780
1831
 
1781
1832
  // src/proxmox/ceph-osd-tree.ts
1782
- import * as Effect16 from "effect/Effect";
1833
+ import * as Effect17 from "effect/Effect";
1783
1834
  var branches = (node) => (Array.isArray(node["children"]) ? node["children"] : []).filter((child) => typeof child === "object" && child !== null);
1784
1835
  var osdLeaves = (tree) => {
1785
1836
  const root = tree["root"];
@@ -1805,29 +1856,29 @@ var osdAttributes = (leaf, props) => ({
1805
1856
  status: text(leaf["status"]),
1806
1857
  total_space: num(leaf["total_space"], 0)
1807
1858
  });
1808
- var readOsd = (props) => pve(props.target, "read", "GET", `nodes/${props.node}/ceph/osd`).pipe(Effect16.map((t) => {
1859
+ var readOsd = (props) => pve(props.target, "read", "GET", `nodes/${props.node}/ceph/osd`).pipe(Effect17.map((t) => {
1809
1860
  const leaf = t === undefined ? undefined : findOsd(t, props.osdid);
1810
1861
  return leaf === undefined ? undefined : osdAttributes(leaf, props);
1811
1862
  }));
1812
1863
 
1813
1864
  // src/proxmox/ceph-osd-write.ts
1814
- import * as Effect17 from "effect/Effect";
1865
+ import * as Effect18 from "effect/Effect";
1815
1866
  var OSD_CREATE_ENDPOINT = "pve:POST /nodes/{node}/ceph/osd";
1816
1867
  var describe = (props) => `osd.${String(props.osdid)} on ${props.node}`;
1817
1868
  var createOsdForm = (props, dev) => ({
1818
1869
  dev,
1819
1870
  ...props.device_class === undefined ? {} : { "crush-device-class": props.device_class }
1820
1871
  });
1821
- var createOsd = Effect17.fn(function* (news) {
1872
+ var createOsd = Effect18.fn(function* (news) {
1822
1873
  if (news.dev === undefined) {
1823
- return yield* Effect17.die(new Error(`${describe(news)}: no such OSD in the CRUSH tree, and no \`dev\` was declared. ` + "Without a device this resource is an ASSERTION over an OSD that must already " + "exist. Declare `dev` to let it create one -- which WIPES that device -- or fix " + "the declaration; if the disk was there yesterday, a replica is missing and Ceph " + "is degraded."));
1874
+ return yield* Effect18.die(new Error(`${describe(news)}: no such OSD in the CRUSH tree, and no \`dev\` was declared. ` + "Without a device this resource is an ASSERTION over an OSD that must already " + "exist. Declare `dev` to let it create one -- which WIPES that device -- or fix " + "the declaration; if the disk was there yesterday, a replica is missing and Ceph " + "is degraded."));
1824
1875
  }
1825
1876
  const form = createOsdForm(news, news.dev);
1826
1877
  yield* guardForm(OSD_CREATE_ENDPOINT, form, true);
1827
1878
  yield* pve(news.target, "provision", "POST", `nodes/${news.node}/ceph/osd`, form);
1828
1879
  const created = yield* readOsd(news);
1829
1880
  if (created === undefined) {
1830
- return yield* Effect17.die(new Error(`${describe(news)}: the create returned no error but osd.${String(news.osdid)} is ` + "still absent. Ceph allocates OSD ids and cannot be told which to use, so it most " + "likely assigned a different number -- check `ceph osd tree`, and declare the id it " + "actually gave the device rather than deploying again."));
1881
+ return yield* Effect18.die(new Error(`${describe(news)}: the create returned no error but osd.${String(news.osdid)} is ` + "still absent. Ceph allocates OSD ids and cannot be told which to use, so it most " + "likely assigned a different number -- check `ceph osd tree`, and declare the id it " + "actually gave the device rather than deploying again."));
1831
1882
  }
1832
1883
  return created;
1833
1884
  });
@@ -1839,16 +1890,16 @@ var ProxmoxCephOsd = Resource7("Proxmox.CephOsd", {
1839
1890
  });
1840
1891
  var settled = (live, props) => (props.host === undefined || props.host === live.host) && (props.device_class === undefined || props.device_class === live.device_class);
1841
1892
  var describe2 = (props) => `osd.${String(props.osdid)} (read via nodes/${props.node}/ceph/osd)`;
1842
- var handlers5 = {
1843
- list: () => Effect18.succeed([]),
1844
- read: Effect18.fn(function* ({ olds }) {
1893
+ var handlers4 = {
1894
+ list: () => Effect19.succeed([]),
1895
+ read: Effect19.fn(function* ({ olds }) {
1845
1896
  return yield* readOsd(olds);
1846
1897
  }),
1847
- diff: Effect18.fn(function* ({
1898
+ diff: Effect19.fn(function* ({
1848
1899
  news,
1849
1900
  output
1850
1901
  }) {
1851
- if (output === undefined || !isResolved4(news))
1902
+ if (output === undefined || !isResolved5(news))
1852
1903
  return;
1853
1904
  const live = yield* readOsd(news);
1854
1905
  if (live === undefined)
@@ -1857,25 +1908,25 @@ var handlers5 = {
1857
1908
  return { action: "update" };
1858
1909
  return settled(live, news) ? { action: "noop" } : { action: "update" };
1859
1910
  }),
1860
- reconcile: Effect18.fn(function* ({ news }) {
1911
+ reconcile: Effect19.fn(function* ({ news }) {
1861
1912
  const live = yield* readOsd(news);
1862
1913
  if (live === undefined) {
1863
1914
  return yield* createOsd(news);
1864
1915
  }
1865
1916
  if (!settled(live, news)) {
1866
- return yield* Effect18.die(new Error(`${describe2(news)}: exists but not as declared -- live host=${live.host} ` + `device_class=${live.device_class}. Nothing here moves an OSD between hosts or ` + "reclassifies it; fix the declaration, or move it deliberately with `ceph osd crush " + "move` / `ceph osd crush set-device-class` and deploy again."));
1917
+ return yield* Effect19.die(new Error(`${describe2(news)}: exists but not as declared -- live host=${live.host} ` + `device_class=${live.device_class}. Nothing here moves an OSD between hosts or ` + "reclassifies it; fix the declaration, or move it deliberately with `ceph osd crush " + "move` / `ceph osd crush set-device-class` and deploy again."));
1867
1918
  }
1868
1919
  return live;
1869
1920
  }),
1870
- delete: Effect18.fn(function* ({ olds }) {
1921
+ delete: Effect19.fn(function* ({ olds }) {
1871
1922
  yield* destroyOsd(olds);
1872
1923
  })
1873
1924
  };
1874
- var ProxmoxCephOsdProvider = () => Provider7.effect(ProxmoxCephOsd, Effect18.succeed(ProxmoxCephOsd.Provider.of(handlers5)));
1925
+ var ProxmoxCephOsdProvider = () => Provider7.effect(ProxmoxCephOsd, Effect19.succeed(ProxmoxCephOsd.Provider.of(handlers4)));
1875
1926
  // src/proxmox/ceph-pool.ts
1876
1927
  import { Resource as Resource8 } from "alchemy";
1877
1928
  import * as Provider8 from "alchemy/Provider";
1878
- import * as Effect20 from "effect/Effect";
1929
+ import * as Effect21 from "effect/Effect";
1879
1930
 
1880
1931
  // src/proxmox/ceph-pool-form.ts
1881
1932
  var object = (props) => `nodes/${props.node}/ceph/pool/${props.name}`;
@@ -1904,14 +1955,14 @@ var createBody = (props) => ({
1904
1955
  });
1905
1956
 
1906
1957
  // src/proxmox/ceph-pool-settle.ts
1907
- import * as Effect19 from "effect/Effect";
1908
- var confirmAbsent = (props, collection2, describe3) => pve(props.target, "read", "GET", collection2).pipe(Effect19.flatMap((rows) => (rows ?? []).some((row) => row.pool_name === props.name) ? Effect19.die(new Error(`${describe3}: the cluster lists this pool but its status could not be read, ` + "so this deploy will NOT create over it. Check that the node is up and that the " + "read role holds Sys.Audit or Datastore.Audit on / -- a create here would apply " + "pg_num to a live pool and start a PG merge.")) : Effect19.void));
1909
- var settle2 = (props, read, done) => Effect19.gen(function* () {
1958
+ import * as Effect20 from "effect/Effect";
1959
+ var confirmAbsent = (props, collection2, describe3) => pve(props.target, "read", "GET", collection2).pipe(Effect20.flatMap((rows) => (rows ?? []).some((row) => row.pool_name === props.name) ? Effect20.die(new Error(`${describe3}: the cluster lists this pool but its status could not be read, ` + "so this deploy will NOT create over it. Check that the node is up and that the " + "read role holds Sys.Audit or Datastore.Audit on / -- a create here would apply " + "pg_num to a live pool and start a PG merge.")) : Effect20.void));
1960
+ var settle2 = (props, read, done) => Effect20.gen(function* () {
1910
1961
  for (let attempt = 0;attempt < 30; attempt += 1) {
1911
1962
  const live = yield* read(props);
1912
1963
  if (done(live))
1913
1964
  return live;
1914
- yield* Effect19.sleep("2 seconds");
1965
+ yield* Effect20.sleep("2 seconds");
1915
1966
  }
1916
1967
  return yield* read(props);
1917
1968
  });
@@ -1952,44 +2003,44 @@ var spec = {
1952
2003
  path: (props) => `${object(props)}/status?verbose=1`,
1953
2004
  updateForm: updateBody
1954
2005
  };
1955
- var ops3 = pveOperations(spec);
1956
- var ProxmoxCephPoolProvider = () => Provider8.effect(ProxmoxCephPool, Effect20.succeed(ProxmoxCephPool.Provider.of({
1957
- list: () => Effect20.succeed([]),
1958
- read: ({ olds }) => ops3.read(olds),
1959
- diff: ({ news, output }) => ops3.diff(news, output),
1960
- reconcile: Effect20.fn(function* ({ news }) {
1961
- const live = yield* ops3.read(news);
2006
+ var ops2 = pveOperations(spec);
2007
+ var ProxmoxCephPoolProvider = () => Provider8.effect(ProxmoxCephPool, Effect21.succeed(ProxmoxCephPool.Provider.of({
2008
+ list: () => Effect21.succeed([]),
2009
+ read: ({ olds }) => ops2.read(olds),
2010
+ diff: ({ news, output }) => ops2.diff(news, output),
2011
+ reconcile: Effect21.fn(function* ({ news }) {
2012
+ const live = yield* ops2.read(news);
1962
2013
  let upid;
1963
2014
  if (live === undefined) {
1964
- yield* ops3.guardCreate(news, true);
2015
+ yield* ops2.guardCreate(news, true);
1965
2016
  yield* confirmAbsent(news, spec.collection(news), object(news));
1966
2017
  upid = yield* pve(news.target, "provision", "POST", collection(news), createBody(news));
1967
2018
  } else {
1968
2019
  const form = formToSend(spec.matches, live, news, updateBody(news));
1969
2020
  if (form !== undefined) {
1970
- yield* ops3.guardUpdate(news);
2021
+ yield* ops2.guardUpdate(news);
1971
2022
  upid = yield* pve(news.target, "provision", "PUT", object(news), form);
1972
2023
  }
1973
2024
  }
1974
- const after = yield* settle2(news, ops3.read, (row) => row !== undefined && spec.matches(row, news));
2025
+ const after = yield* settle2(news, ops2.read, (row) => row !== undefined && spec.matches(row, news));
1975
2026
  if (after === undefined || !spec.matches(after, news)) {
1976
- return yield* Effect20.die(new Error(`${object(news)}: the write returned no error but the pool still does not match ` + `the declaration after 60s. PVE answered with task ${upid ?? "(none)"} -- read ` + "its log with `pvesh get /nodes/<node>/tasks/<upid>/log` to see what Ceph " + "refused."));
2027
+ return yield* Effect21.die(new Error(`${object(news)}: the write returned no error but the pool still does not match ` + `the declaration after 60s. PVE answered with task ${upid ?? "(none)"} -- read ` + "its log with `pvesh get /nodes/<node>/tasks/<upid>/log` to see what Ceph " + "refused."));
1977
2028
  }
1978
2029
  return after;
1979
2030
  }),
1980
- delete: Effect20.fn(function* ({ olds }) {
2031
+ delete: Effect21.fn(function* ({ olds }) {
1981
2032
  yield* pve(olds.target, "provision", "DELETE", object(olds));
1982
- const left = yield* settle2(olds, ops3.read, (row) => row === undefined);
2033
+ const left = yield* settle2(olds, ops2.read, (row) => row === undefined);
1983
2034
  if (left !== undefined) {
1984
- return yield* Effect20.die(new Error(`${object(olds)}: the DELETE returned no error but the pool is still there after ` + "60s. Either a storage on it still holds RBD images, or the mons are running " + "with mon_allow_pool_delete false."));
2035
+ return yield* Effect21.die(new Error(`${object(olds)}: the DELETE returned no error but the pool is still there after ` + "60s. Either a storage on it still holds RBD images, or the mons are running " + "with mon_allow_pool_delete false."));
1985
2036
  }
1986
2037
  })
1987
2038
  })));
1988
2039
  // src/proxmox/firewall-alias.ts
1989
2040
  import { Resource as Resource9 } from "alchemy";
1990
- import { isResolved as isResolved5 } from "alchemy/Diff";
2041
+ import { isResolved as isResolved6 } from "alchemy/Diff";
1991
2042
  import * as Provider9 from "alchemy/Provider";
1992
- import * as Effect21 from "effect/Effect";
2043
+ import * as Effect22 from "effect/Effect";
1993
2044
 
1994
2045
  // src/proxmox/firewall-alias-form.ts
1995
2046
  var fold = (name) => name.trim().toLowerCase();
@@ -2009,7 +2060,7 @@ var body = (props) => {
2009
2060
 
2010
2061
  // src/proxmox/firewall-alias.ts
2011
2062
  var ProxmoxFirewallAlias = Resource9("Proxmox.FirewallAlias");
2012
- var handlers6 = pveHandlers({
2063
+ var handlers5 = pveHandlers({
2013
2064
  attributes: (live, props) => {
2014
2065
  const address = cidr(live["cidr"]);
2015
2066
  return address === "" ? undefined : {
@@ -2029,48 +2080,105 @@ var handlers6 = pveHandlers({
2029
2080
  path: (props) => `cluster/firewall/aliases/${props.name}`,
2030
2081
  updateForm: body
2031
2082
  });
2032
- var ProxmoxFirewallAliasProvider = () => Provider9.effect(ProxmoxFirewallAlias, Effect21.succeed(ProxmoxFirewallAlias.Provider.of({
2033
- ...handlers6,
2034
- diff: Effect21.fn(function* ({ news, output }) {
2035
- if (output !== undefined && isResolved5(news) && fold(news.name) !== fold(output.name)) {
2083
+ var ProxmoxFirewallAliasProvider = () => Provider9.effect(ProxmoxFirewallAlias, Effect22.succeed(ProxmoxFirewallAlias.Provider.of({
2084
+ ...handlers5,
2085
+ diff: Effect22.fn(function* ({ news, output }) {
2086
+ if (output !== undefined && isResolved6(news) && fold(news.name) !== fold(output.name)) {
2036
2087
  return { action: "replace" };
2037
2088
  }
2038
- return yield* handlers6.diff({ news, output });
2089
+ return yield* handlers5.diff({ news, output });
2039
2090
  })
2040
2091
  })));
2041
2092
  // src/proxmox/group.ts
2042
2093
  import { Resource as Resource10 } from "alchemy";
2094
+ import { isResolved as isResolved7 } from "alchemy/Diff";
2043
2095
  import * as Provider10 from "alchemy/Provider";
2044
- import * as Effect22 from "effect/Effect";
2045
- var ProxmoxGroup = Resource10("Proxmox.Group", {
2046
- defaultRemovalPolicy: "retain"
2047
- });
2096
+ import * as access4 from "@distilled.cloud/proxmox/access";
2097
+ import * as Effect24 from "effect/Effect";
2098
+
2099
+ // src/proxmox/group-wire.ts
2100
+ import * as access3 from "@distilled.cloud/proxmox/access";
2101
+ import * as Effect23 from "effect/Effect";
2102
+ var GROUP_CREATE = "pve:POST /access/groups";
2103
+ var GROUP_UPDATE = "pve:PUT /access/groups/{groupid}";
2048
2104
  var storedComment = (comment2) => {
2049
2105
  const value = comment2 ?? "";
2050
2106
  return value === "0" ? "" : value;
2051
2107
  };
2052
2108
  var memberList = (live) => {
2053
- const joined = csv((Array.isArray(live) ? live : []).map((member) => text(member)));
2109
+ const joined = csv(live.map((member) => text(member)));
2054
2110
  return joined === "" ? [] : joined.split(",");
2055
2111
  };
2056
- var handlers7 = pveHandlers({
2057
- attributes: (live, props) => ({
2058
- comment: text(live["comment"]),
2059
- groupid: props.groupid,
2060
- members: memberList(live["members"])
2112
+ var createForm3 = (props) => ({
2113
+ comment: storedComment(props.comment),
2114
+ groupid: props.groupid
2115
+ });
2116
+ var updateForm = (props) => ({
2117
+ comment: storedComment(props.comment),
2118
+ groupid: props.groupid
2119
+ });
2120
+ var attributesOf3 = (live, props) => ({
2121
+ comment: text(live.comment),
2122
+ groupid: props.groupid,
2123
+ members: memberList(live.members)
2124
+ });
2125
+ var matches3 = (attributes, props) => attributes.comment === storedComment(props.comment);
2126
+ var readGroupOrFail = (props) => readOrUnreadable(runPve(props.target, "read", false, access3.getAccessGroup({ groupid: props.groupid }))).pipe(Effect23.map((live) => live === UNREADABLE ? UNREADABLE : live.members === undefined ? undefined : attributesOf3(live, props)));
2127
+ var readGroup = (props) => readGroupOrFail(props).pipe(Effect23.orElseSucceed(() => {
2128
+ return;
2129
+ }));
2130
+ var dropUnreadable3 = (live) => live === UNREADABLE ? undefined : live;
2131
+
2132
+ // src/proxmox/group.ts
2133
+ var ProxmoxGroup = Resource10("Proxmox.Group", {
2134
+ defaultRemovalPolicy: "retain"
2135
+ });
2136
+ var ProxmoxGroupProvider = () => Provider10.effect(ProxmoxGroup, Effect24.succeed(ProxmoxGroup.Provider.of({
2137
+ list: () => Effect24.succeed([]),
2138
+ read: Effect24.fn(function* ({ olds, output }) {
2139
+ return dropUnreadable3(yield* output === undefined ? readGroup(olds) : readGroupOrFail(olds));
2061
2140
  }),
2062
- collection: () => "access/groups",
2063
- createForm: (props) => ({ comment: storedComment(props.comment), groupid: props.groupid }),
2064
- endpoint: { create: "pve:POST /access/groups", update: "pve:PUT /access/groups/{groupid}" },
2065
- matches: (attributes, props) => attributes.comment === storedComment(props.comment),
2066
- path: (props) => `access/groups/${props.groupid}`,
2067
- updateForm: (props) => ({ comment: storedComment(props.comment) })
2068
- });
2069
- var ProxmoxGroupProvider = () => Provider10.effect(ProxmoxGroup, Effect22.succeed(ProxmoxGroup.Provider.of(handlers7)));
2141
+ diff: Effect24.fn(function* ({ news, output }) {
2142
+ if (!isResolved7(news))
2143
+ return;
2144
+ yield* guardWrite2(GROUP_CREATE, createForm3(news), output === undefined);
2145
+ yield* guardWrite2(GROUP_UPDATE, updateForm(news), false);
2146
+ if (output === undefined)
2147
+ return;
2148
+ const live = yield* readGroupOrFail(news);
2149
+ if (live === UNREADABLE) {
2150
+ yield* unreadableWarning("Proxmox.Group", news.groupid);
2151
+ return { action: "noop" };
2152
+ }
2153
+ if (live === undefined) {
2154
+ yield* guardWrite2(GROUP_CREATE, createForm3(news), true);
2155
+ return { action: "update" };
2156
+ }
2157
+ return matches3(live, news) ? { action: "noop" } : { action: "update" };
2158
+ }),
2159
+ reconcile: Effect24.fn(function* ({ news }) {
2160
+ const before = dropUnreadable3(yield* readGroup(news));
2161
+ yield* guardWrite2(GROUP_CREATE, createForm3(news), before === undefined);
2162
+ yield* guardWrite2(GROUP_UPDATE, updateForm(news), false);
2163
+ if (before === undefined) {
2164
+ yield* runPve(news.target, "provision", true, access4.createAccessGroup(createForm3(news)));
2165
+ } else if (!matches3(before, news)) {
2166
+ yield* runPve(news.target, "provision", true, access4.putAccessGroup(updateForm(news)));
2167
+ }
2168
+ const after = dropUnreadable3(yield* readGroup(news));
2169
+ if (after === undefined) {
2170
+ return yield* Effect24.die(new Error(`access/groups/${news.groupid}: the write returned no error but the group is ` + 'still absent. PVE wraps every answer in {"data":...} and can report success ' + "on a call that did nothing -- read back rather than trusting the status code."));
2171
+ }
2172
+ return after;
2173
+ }),
2174
+ delete: Effect24.fn(function* ({ olds }) {
2175
+ yield* runPve(olds.target, "provision", true, access4.deleteAccessGroup({ groupid: olds.groupid }));
2176
+ })
2177
+ })));
2070
2178
  // src/proxmox/ha-resource.ts
2071
2179
  import { Resource as Resource11 } from "alchemy";
2072
2180
  import * as Provider11 from "alchemy/Provider";
2073
- import * as Effect23 from "effect/Effect";
2181
+ import * as Effect25 from "effect/Effect";
2074
2182
  var ProxmoxHaResource = Resource11("Proxmox.HaResource");
2075
2183
  var kind = (sid) => sid.split(":")[0] ?? "";
2076
2184
  var shape3 = (props) => ({
@@ -2079,7 +2187,7 @@ var shape3 = (props) => ({
2079
2187
  max_restart: String(props.max_restart ?? 1),
2080
2188
  state: props.state ?? "started"
2081
2189
  });
2082
- var handlers8 = pveHandlers({
2190
+ var handlers6 = pveHandlers({
2083
2191
  attributes: (live, props) => ({
2084
2192
  comment: typeof live["comment"] === "string" ? live["comment"] : "",
2085
2193
  max_relocate: num(live["max_relocate"], 1),
@@ -2098,11 +2206,11 @@ var handlers8 = pveHandlers({
2098
2206
  path: (props) => `cluster/ha/resources/${props.sid}`,
2099
2207
  updateForm: shape3
2100
2208
  });
2101
- var ProxmoxHaResourceProvider = () => Provider11.effect(ProxmoxHaResource, Effect23.succeed(ProxmoxHaResource.Provider.of(handlers8)));
2209
+ var ProxmoxHaResourceProvider = () => Provider11.effect(ProxmoxHaResource, Effect25.succeed(ProxmoxHaResource.Provider.of(handlers6)));
2102
2210
  // src/proxmox/ha-rule.ts
2103
2211
  import { Resource as Resource12 } from "alchemy";
2104
2212
  import * as Provider12 from "alchemy/Provider";
2105
- import * as Effect24 from "effect/Effect";
2213
+ import * as Effect26 from "effect/Effect";
2106
2214
 
2107
2215
  // src/proxmox/ha-rule-form.ts
2108
2216
  var nodeList = (value) => {
@@ -2150,7 +2258,7 @@ var clearList = (props) => Object.entries(optional(props)).filter(([, value]) =>
2150
2258
 
2151
2259
  // src/proxmox/ha-rule.ts
2152
2260
  var ProxmoxHaRule = Resource12("Proxmox.HaRule");
2153
- var handlers9 = pveHandlers({
2261
+ var handlers7 = pveHandlers({
2154
2262
  attributes: (live, props) => {
2155
2263
  const liveType = text(live["type"]);
2156
2264
  if (liveType !== "" && liveType !== props.type)
@@ -2179,43 +2287,43 @@ var handlers9 = pveHandlers({
2179
2287
  return withClears(fields, clear);
2180
2288
  }
2181
2289
  });
2182
- var ProxmoxHaRuleProvider = () => Provider12.effect(ProxmoxHaRule, Effect24.succeed(ProxmoxHaRule.Provider.of(handlers9)));
2290
+ var ProxmoxHaRuleProvider = () => Provider12.effect(ProxmoxHaRule, Effect26.succeed(ProxmoxHaRule.Provider.of(handlers7)));
2183
2291
  // src/proxmox/lxc.ts
2184
2292
  import { Resource as Resource13 } from "alchemy";
2185
- import { isResolved as isResolved6 } from "alchemy/Diff";
2293
+ import { isResolved as isResolved8 } from "alchemy/Diff";
2186
2294
  import * as Provider13 from "alchemy/Provider";
2187
- import * as Effect34 from "effect/Effect";
2295
+ import * as Effect36 from "effect/Effect";
2188
2296
 
2189
2297
  // src/ownership/adopt.ts
2190
2298
  import { AdoptPolicy } from "alchemy/AdoptPolicy";
2191
2299
  import { AlchemyContext } from "alchemy/AlchemyContext";
2192
2300
  import { Stack as Stack3 } from "alchemy/Stack";
2193
- import * as Effect28 from "effect/Effect";
2301
+ import * as Effect30 from "effect/Effect";
2194
2302
  import * as Option5 from "effect/Option";
2195
2303
 
2196
2304
  // src/ownership/resume.ts
2197
2305
  import { Unowned } from "alchemy/AdoptPolicy";
2198
2306
  import { Artifacts } from "alchemy/Artifacts";
2199
- import * as Effect25 from "effect/Effect";
2307
+ import * as Effect27 from "effect/Effect";
2200
2308
  import * as Option2 from "effect/Option";
2201
2309
  var PROVEN = "homeflare/ownership/resumes";
2202
2310
  var UNFINISHED = "homeflare/ownership/unfinished";
2203
- var note = (key, instanceId) => Effect25.gen(function* () {
2204
- const bag = yield* Effect25.serviceOption(Artifacts);
2311
+ var note = (key, instanceId) => Effect27.gen(function* () {
2312
+ const bag = yield* Effect27.serviceOption(Artifacts);
2205
2313
  if (Option2.isSome(bag))
2206
2314
  yield* bag.value.set(key, instanceId);
2207
2315
  });
2208
- var noted = (key, instanceId) => Effect25.gen(function* () {
2209
- const bag = yield* Effect25.serviceOption(Artifacts);
2316
+ var noted = (key, instanceId) => Effect27.gen(function* () {
2317
+ const bag = yield* Effect27.serviceOption(Artifacts);
2210
2318
  if (Option2.isNone(bag))
2211
2319
  return false;
2212
2320
  return (yield* bag.value.get(key)) === instanceId;
2213
2321
  });
2214
- var noteUnfinished = (instanceId) => Effect25.as(note(UNFINISHED, instanceId), undefined);
2215
- var noteResume = (instanceId, recovered) => Effect25.gen(function* () {
2322
+ var noteUnfinished = (instanceId) => Effect27.as(note(UNFINISHED, instanceId), undefined);
2323
+ var noteResume = (instanceId, recovered) => Effect27.gen(function* () {
2216
2324
  yield* noteUnfinished(instanceId);
2217
- const unproven = () => Effect25.succeed(undefined);
2218
- const found = yield* recovered.pipe(Effect25.catch(unproven), Effect25.catchDefect(unproven));
2325
+ const unproven = () => Effect27.succeed(undefined);
2326
+ const found = yield* recovered.pipe(Effect27.catch(unproven), Effect27.catchDefect(unproven));
2219
2327
  if (found === undefined || found === null || Unowned.is(found))
2220
2328
  return;
2221
2329
  yield* note(PROVEN, instanceId);
@@ -2226,7 +2334,7 @@ var provingResumes = (service) => {
2226
2334
  const { diff, read } = service;
2227
2335
  if (diff === undefined || read === undefined)
2228
2336
  return service;
2229
- const proving = (input) => Effect25.gen(function* () {
2337
+ const proving = (input) => Effect27.gen(function* () {
2230
2338
  if (input.output === undefined) {
2231
2339
  yield* noteResume(input.instanceId, read({ ...input, output: undefined }));
2232
2340
  }
@@ -2238,12 +2346,12 @@ var provingResumes = (service) => {
2238
2346
  // src/ownership/rows.ts
2239
2347
  import { Stack as Stack2 } from "alchemy/Stack";
2240
2348
  import { State, isActionState } from "alchemy/State/State";
2241
- import * as Effect27 from "effect/Effect";
2349
+ import * as Effect29 from "effect/Effect";
2242
2350
  import * as Option4 from "effect/Option";
2243
2351
 
2244
2352
  // src/ownership/whole.ts
2245
2353
  import { Stack } from "alchemy/Stack";
2246
- import * as Effect26 from "effect/Effect";
2354
+ import * as Effect28 from "effect/Effect";
2247
2355
  import * as Option3 from "effect/Option";
2248
2356
  var absent = (value) => value === undefined || value === null;
2249
2357
  var isPlainObject = (value) => {
@@ -2269,22 +2377,22 @@ var carries = (declared, recorded) => {
2269
2377
  }
2270
2378
  return true;
2271
2379
  };
2272
- var wholeDeclaration = (fqn, props) => Effect26.map(Effect26.serviceOption(Stack), (stack) => {
2380
+ var wholeDeclaration = (fqn, props) => Effect28.map(Effect28.serviceOption(Stack), (stack) => {
2273
2381
  const declared = Option3.isSome(stack) ? stack.value.resources[fqn] : undefined;
2274
2382
  return declared !== undefined && carries(declared.Props, props);
2275
2383
  });
2276
2384
 
2277
2385
  // src/ownership/rows.ts
2278
- var storeOf = Effect27.gen(function* () {
2279
- const stack = yield* Effect27.serviceOption(Stack2);
2280
- const state = yield* Effect27.serviceOption(State);
2386
+ var storeOf = Effect29.gen(function* () {
2387
+ const stack = yield* Effect29.serviceOption(Stack2);
2388
+ const state = yield* Effect29.serviceOption(State);
2281
2389
  if (Option4.isNone(stack) || Option4.isNone(state))
2282
2390
  return;
2283
2391
  const store = yield* state.value;
2284
2392
  const where = { stack: stack.value.name, stage: stack.value.stage };
2285
2393
  return { resources: stack.value.resources, store, where };
2286
2394
  });
2287
- var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect27.orElseSucceed(() => {
2395
+ var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect29.orElseSucceed(() => {
2288
2396
  return;
2289
2397
  }));
2290
2398
  var generationOf = (row, instanceId) => {
@@ -2297,7 +2405,7 @@ var generationOf = (row, instanceId) => {
2297
2405
  }
2298
2406
  return;
2299
2407
  };
2300
- var recordedGeneration = (fqn, instanceId) => Effect27.gen(function* () {
2408
+ var recordedGeneration = (fqn, instanceId) => Effect29.gen(function* () {
2301
2409
  const found = yield* storeOf;
2302
2410
  if (found === undefined)
2303
2411
  return;
@@ -2308,14 +2416,14 @@ var recordedGeneration = (fqn, instanceId) => Effect27.gen(function* () {
2308
2416
  }
2309
2417
  return;
2310
2418
  });
2311
- var recordedInstance = (fqn, instanceId) => Effect27.gen(function* () {
2419
+ var recordedInstance = (fqn, instanceId) => Effect29.gen(function* () {
2312
2420
  const generation = yield* recordedGeneration(fqn, instanceId);
2313
2421
  if (generation === undefined)
2314
2422
  return "absent";
2315
2423
  return (yield* wholeDeclaration(fqn, generation.props)) ? "whole" : "partial";
2316
2424
  });
2317
- var isCreate = (fqn, instanceId) => Effect27.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
2318
- var forgetRefusedCreate = (fqn, instanceId) => Effect27.gen(function* () {
2425
+ var isCreate = (fqn, instanceId) => Effect29.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
2426
+ var forgetRefusedCreate = (fqn, instanceId) => Effect29.gen(function* () {
2319
2427
  const found = yield* storeOf;
2320
2428
  if (found === undefined)
2321
2429
  return;
@@ -2324,32 +2432,32 @@ var forgetRefusedCreate = (fqn, instanceId) => Effect27.gen(function* () {
2324
2432
  if (mine?.status !== "creating" || mine.instanceId !== instanceId || mine.attr !== undefined) {
2325
2433
  return;
2326
2434
  }
2327
- yield* found.store.delete({ ...found.where, fqn }).pipe(Effect27.ignore);
2435
+ yield* found.store.delete({ ...found.where, fqn }).pipe(Effect29.ignore);
2328
2436
  });
2329
2437
 
2330
2438
  // src/ownership/adopt.ts
2331
- var adoptEnabled = (fqn) => Effect28.gen(function* () {
2332
- const stack = yield* Effect28.serviceOption(Stack3);
2439
+ var adoptEnabled = (fqn) => Effect30.gen(function* () {
2440
+ const stack = yield* Effect30.serviceOption(Stack3);
2333
2441
  const scoped = Option5.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;
2334
2442
  if (scoped !== undefined)
2335
2443
  return scoped;
2336
- const policy = yield* Effect28.serviceOption(AdoptPolicy);
2444
+ const policy = yield* Effect30.serviceOption(AdoptPolicy);
2337
2445
  if (Option5.isSome(policy))
2338
2446
  return policy.value;
2339
- const context = yield* Effect28.serviceOption(AlchemyContext);
2447
+ const context = yield* Effect30.serviceOption(AlchemyContext);
2340
2448
  return Option5.isSome(context) ? context.value.adopt : false;
2341
2449
  });
2342
- var adoptable = (owner) => Effect28.gen(function* () {
2450
+ var adoptable = (owner) => Effect30.gen(function* () {
2343
2451
  if (yield* isCreate(owner.fqn, owner.instanceId))
2344
2452
  return true;
2345
2453
  return yield* unfinished(owner.instanceId);
2346
2454
  });
2347
- var adoptsAtApply = (owner) => Effect28.gen(function* () {
2455
+ var adoptsAtApply = (owner) => Effect30.gen(function* () {
2348
2456
  if (!(yield* adoptEnabled(owner.fqn)))
2349
2457
  return false;
2350
2458
  return yield* adoptable(owner);
2351
2459
  });
2352
- var refuseTakeover = (owner, what) => Effect28.gen(function* () {
2460
+ var refuseTakeover = (owner, what) => Effect30.gen(function* () {
2353
2461
  if (owner.output !== undefined)
2354
2462
  return;
2355
2463
  if (yield* resumes(owner.instanceId))
@@ -2359,25 +2467,25 @@ var refuseTakeover = (owner, what) => Effect28.gen(function* () {
2359
2467
  return;
2360
2468
  yield* forgetRefusedCreate(owner.fqn, owner.instanceId);
2361
2469
  const remedy = may ? "Deploy with --adopt (or wrap the resource in adopt(true)) to take it over, or remove it first." : "It is the new identity of a replace, which --adopt does not cover: remove it first.";
2362
- return yield* Effect28.die(new Error(`${what}: already exists, and this stack holds no state for it — the plan could not ask ` + "first (a prop was still an Output, a replace moved onto it, or it appeared after the " + `plan). Nothing was written. ${remedy}`));
2470
+ return yield* Effect30.die(new Error(`${what}: already exists, and this stack holds no state for it — the plan could not ask ` + "first (a prop was still an Output, a replace moved onto it, or it appeared after the " + `plan). Nothing was written. ${remedy}`));
2363
2471
  });
2364
2472
  var claimFor = (owner) => (what) => refuseTakeover(owner, what);
2365
2473
 
2366
2474
  // src/ownership/probe.ts
2367
2475
  import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
2368
- import * as Effect29 from "effect/Effect";
2369
- var provenOurs = (ask, settled2) => Effect29.gen(function* () {
2476
+ import * as Effect31 from "effect/Effect";
2477
+ var provenOurs = (ask, settled2) => Effect31.gen(function* () {
2370
2478
  const recorded = yield* recordedInstance(ask.fqn, ask.instanceId);
2371
2479
  if (recorded === "absent")
2372
2480
  return false;
2373
2481
  if (recorded === "partial") {
2374
- yield* Effect29.logWarning(`${ask.fqn}: the interrupted create's state row lacks part of the declaration (a prop ` + "was still an Output when it was written), so the live object is not proven ours");
2482
+ yield* Effect31.logWarning(`${ask.fqn}: the interrupted create's state row lacks part of the declaration (a prop ` + "was still an Output when it was written), so the live object is not proven ours");
2375
2483
  return false;
2376
2484
  }
2377
- const unproven = (reason) => Effect29.as(Effect29.logWarning(`${ask.fqn}: the interrupted create's object could not be compared with its props ` + `(${reason instanceof Error ? reason.message : String(reason)}), so it is not treated as ours`), false);
2378
- return yield* settled2.pipe(Effect29.catch(unproven), Effect29.catchDefect(unproven));
2485
+ const unproven = (reason) => Effect31.as(Effect31.logWarning(`${ask.fqn}: the interrupted create's object could not be compared with its props ` + `(${reason instanceof Error ? reason.message : String(reason)}), so it is not treated as ours`), false);
2486
+ return yield* settled2.pipe(Effect31.catch(unproven), Effect31.catchDefect(unproven));
2379
2487
  });
2380
- var ownedRead = (ask, found, settled2) => Effect29.gen(function* () {
2488
+ var ownedRead = (ask, found, settled2) => Effect31.gen(function* () {
2381
2489
  if (found === undefined || ask.output !== undefined)
2382
2490
  return found;
2383
2491
  return (yield* provenOurs(ask, settled2)) ? found : Unowned2(found);
@@ -2435,8 +2543,8 @@ var optionMap = (value, family, defaultKey) => {
2435
2543
  return out;
2436
2544
  };
2437
2545
  var sameMap = (left, right) => left.size === right.size && [...left].every(([key, value]) => right.get(key) === value);
2438
- var sortedForm = (map7) => [...map7].map(([key, value]) => `${key}=${value}`).sort().join(",");
2439
- var printMap = (map7, lead = []) => [...lead.filter((key) => map7.has(key)), ...[...map7.keys()].filter((k) => !lead.includes(k))].map((key) => `${key}=${map7.get(key) ?? ""}`).join(",");
2546
+ var sortedForm = (map9) => [...map9].map(([key, value]) => `${key}=${value}`).sort().join(",");
2547
+ var printMap = (map9, lead = []) => [...lead.filter((key) => map9.has(key)), ...[...map9.keys()].filter((k) => !lead.includes(k))].map((key) => `${key}=${map9.get(key) ?? ""}`).join(",");
2440
2548
  var netMaps = (declared, live) => {
2441
2549
  const want = optionMap(declared, "net");
2442
2550
  const have = optionMap(live, "net");
@@ -2797,7 +2905,7 @@ var createRefusals = (props) => {
2797
2905
  }
2798
2906
  return refuse;
2799
2907
  };
2800
- var createForm3 = (props) => {
2908
+ var createForm4 = (props) => {
2801
2909
  const form = { vmid: String(props.vmid) };
2802
2910
  if (props.ostemplate !== undefined)
2803
2911
  form["ostemplate"] = props.ostemplate;
@@ -2832,11 +2940,11 @@ var identityRefusals = (news, olds, output) => {
2832
2940
  };
2833
2941
 
2834
2942
  // src/proxmox/lxc-adoption.ts
2835
- import * as Effect33 from "effect/Effect";
2943
+ import * as Effect35 from "effect/Effect";
2836
2944
 
2837
2945
  // src/ownership/adopting.ts
2838
- import * as Effect30 from "effect/Effect";
2839
- var adopting = (owner, settled2) => Effect30.gen(function* () {
2946
+ import * as Effect32 from "effect/Effect";
2947
+ var adopting = (owner, settled2) => Effect32.gen(function* () {
2840
2948
  if (owner.output === undefined)
2841
2949
  return false;
2842
2950
  const generation = yield* recordedGeneration(owner.fqn, owner.instanceId);
@@ -2848,11 +2956,11 @@ var adopting = (owner, settled2) => Effect30.gen(function* () {
2848
2956
  });
2849
2957
 
2850
2958
  // src/proxmox/lxc-lifecycle.ts
2851
- import * as Effect32 from "effect/Effect";
2959
+ import * as Effect34 from "effect/Effect";
2852
2960
 
2853
2961
  // src/proxmox/network-apply-read.ts
2854
- import * as Effect31 from "effect/Effect";
2855
- var stagedDiff = (target, node) => Effect31.gen(function* () {
2962
+ import * as Effect33 from "effect/Effect";
2963
+ var stagedDiff = (target, node) => Effect33.gen(function* () {
2856
2964
  const path = `nodes/${node}/network`;
2857
2965
  const credential = yield* leased(target, "read");
2858
2966
  const envelope = yield* pveEnvelopeWith(target, credential, "GET", path);
@@ -2865,8 +2973,8 @@ var pendingLines = (diff) => {
2865
2973
  `).filter((line) => (line.startsWith("+") || line.startsWith("-")) && !line.startsWith("+++") && !line.startsWith("---")).length;
2866
2974
  return Math.max(changed, 1);
2867
2975
  };
2868
- var pendingCount = (target, node) => stagedDiff(target, node).pipe(Effect31.map(pendingLines));
2869
- var clusterHealth = (target) => pve(target, "read", "GET", "cluster/status").pipe(Effect31.map((rows) => {
2976
+ var pendingCount = (target, node) => stagedDiff(target, node).pipe(Effect33.map(pendingLines));
2977
+ var clusterHealth = (target) => pve(target, "read", "GET", "cluster/status").pipe(Effect33.map((rows) => {
2870
2978
  const all = rows ?? [];
2871
2979
  const cluster = all.find((row) => row.type === "cluster");
2872
2980
  return {
@@ -2874,21 +2982,21 @@ var clusterHealth = (target) => pve(target, "read", "GET", "cluster/status").pip
2874
2982
  quorate: cluster === undefined ? true : bool(cluster.quorate)
2875
2983
  };
2876
2984
  }));
2877
- var degradedReason = (target) => clusterHealth(target).pipe(Effect31.map((health) => health.quorate && health.offline.length === 0 ? undefined : `quorate=${String(health.quorate)} offline=[${health.offline.join(", ")}]`));
2985
+ var degradedReason = (target) => clusterHealth(target).pipe(Effect33.map((health) => health.quorate && health.offline.length === 0 ? undefined : `quorate=${String(health.quorate)} offline=[${health.offline.join(", ")}]`));
2878
2986
  var UNREACHABLE = "the task could not be read -- the node stopped answering, or this lease cannot see it";
2879
2987
  var STILL_RUNNING = "still running when this provider stopped waiting";
2880
2988
  var ATTEMPTS = 30;
2881
- var awaitTask = (target, credential, node, upid, attempts = ATTEMPTS) => Effect31.gen(function* () {
2989
+ var awaitTask = (target, credential, node, upid, attempts = ATTEMPTS) => Effect33.gen(function* () {
2882
2990
  const path = `nodes/${node}/tasks/${upid}/status`;
2883
2991
  for (let attempt = 0;attempt < attempts; attempt += 1) {
2884
- const status = yield* pveWith(target, credential, "GET", path).pipe(Effect31.orElseSucceed(() => {
2992
+ const status = yield* pveWith(target, credential, "GET", path).pipe(Effect33.orElseSucceed(() => {
2885
2993
  return;
2886
2994
  }));
2887
2995
  if (status === undefined)
2888
2996
  return UNREACHABLE;
2889
2997
  if (status.status === "stopped")
2890
2998
  return text(status.exitstatus, "stopped with no exitstatus");
2891
- yield* Effect31.sleep("1 second");
2999
+ yield* Effect33.sleep("1 second");
2892
3000
  }
2893
3001
  return STILL_RUNNING;
2894
3002
  });
@@ -2906,43 +3014,43 @@ var LXC_ENDPOINTS = {
2906
3014
  var CREATE_POLLS = 180;
2907
3015
  var TASK_POLLS = 60;
2908
3016
  var isServerError = (error) => error instanceof PveError && error.status === 500;
2909
- var readLive = (where) => Effect32.gen(function* () {
2910
- const answer = yield* pve(where.target, "read", "GET", `${guestPath(where)}/config`).pipe(Effect32.map((data) => ({ data, error: undefined })), Effect32.catchIf(isServerError, (error) => Effect32.succeed({ data: undefined, error })));
3017
+ var readLive = (where) => Effect34.gen(function* () {
3018
+ const answer = yield* pve(where.target, "read", "GET", `${guestPath(where)}/config`).pipe(Effect34.map((data) => ({ data, error: undefined })), Effect34.catchIf(isServerError, (error) => Effect34.succeed({ data: undefined, error })));
2911
3019
  if (answer.data !== undefined && answer.data !== null)
2912
3020
  return answer.data;
2913
3021
  const rows = yield* pve(where.target, "read", "GET", "cluster/resources?type=vm");
2914
3022
  const found = (rows ?? []).find((row) => Number(row.vmid) === where.vmid);
2915
3023
  if (found !== undefined && found.node === where.node && found.type === "lxc") {
2916
- return yield* Effect32.fail(answer.error ?? new LxcRefusedError(`CT ${String(where.vmid)} is listed but its config read empty.`));
3024
+ return yield* Effect34.fail(answer.error ?? new LxcRefusedError(`CT ${String(where.vmid)} is listed but its config read empty.`));
2917
3025
  }
2918
3026
  if (found !== undefined) {
2919
- return yield* Effect32.fail(new LxcRefusedError(`vmid ${String(where.vmid)} is a ${text(found.type, "guest")} on ` + `${text(found.node, "?")}, not a container on ${where.node}. A migration is not an ` + "update and vmids are cluster-wide: declare the node it is on, or pick a free vmid."));
3027
+ return yield* Effect34.fail(new LxcRefusedError(`vmid ${String(where.vmid)} is a ${text(found.type, "guest")} on ` + `${text(found.node, "?")}, not a container on ${where.node}. A migration is not an ` + "update and vmids are cluster-wide: declare the node it is on, or pick a free vmid."));
2920
3028
  }
2921
3029
  return;
2922
3030
  });
2923
- var attributesOf2 = (where, live) => ({
3031
+ var attributesOf4 = (where, live) => ({
2924
3032
  config: storedConfig(live),
2925
3033
  node: where.node,
2926
3034
  rawKeys: rawKeysOf(live),
2927
3035
  vmid: where.vmid
2928
3036
  });
2929
- var task = (where, method, path, what, polls, form) => Effect32.gen(function* () {
3037
+ var task = (where, method, path, what, polls, form) => Effect34.gen(function* () {
2930
3038
  const credential = yield* mint(where.target, "provision");
2931
3039
  const upid = text(yield* pveWith(where.target, credential, method, path, form));
2932
3040
  if (upid === "") {
2933
- return yield* Effect32.fail(new LxcRefusedError(`${what}: PVE returned no task id, so the write cannot be confirmed and is not claimed.`));
3041
+ return yield* Effect34.fail(new LxcRefusedError(`${what}: PVE returned no task id, so the write cannot be confirmed and is not claimed.`));
2934
3042
  }
2935
3043
  const exit = yield* awaitTask(where.target, credential, where.node, upid, polls);
2936
3044
  if (exit !== "OK") {
2937
- return yield* Effect32.fail(new LxcRefusedError(`${what}: task ${upid} ended "${exit}". Read \`pvesh get /nodes/${where.node}/tasks/` + `${upid}/log\` before running the deploy again.`));
3045
+ return yield* Effect34.fail(new LxcRefusedError(`${what}: task ${upid} ended "${exit}". Read \`pvesh get /nodes/${where.node}/tasks/` + `${upid}/log\` before running the deploy again.`));
2938
3046
  }
2939
3047
  });
2940
- var createGuest = (props) => Effect32.gen(function* () {
2941
- const form = createForm3(props);
3048
+ var createGuest = (props) => Effect34.gen(function* () {
3049
+ const form = createForm4(props);
2942
3050
  yield* guardForm(LXC_ENDPOINTS.create, form, true);
2943
3051
  return yield* task(props, "POST", `nodes/${props.node}/lxc`, `create CT ${String(props.vmid)}`, CREATE_POLLS, form);
2944
3052
  });
2945
- var updateGuest = (props, change, digest) => Effect32.gen(function* () {
3053
+ var updateGuest = (props, change, digest) => Effect34.gen(function* () {
2946
3054
  if (Object.keys(change.put).length > 0 || change.clear.length > 0) {
2947
3055
  const form = withClears({ ...change.put, ...digest === "" ? {} : { digest } }, change.clear);
2948
3056
  yield* guardForm(LXC_ENDPOINTS.update, form, false);
@@ -2954,47 +3062,47 @@ var updateGuest = (props, change, digest) => Effect32.gen(function* () {
2954
3062
  yield* task(props, "PUT", `${guestPath(props)}/resize`, `grow ${grow.disk} of CT ${String(props.vmid)} to ${grow.size}`, TASK_POLLS, grown);
2955
3063
  }
2956
3064
  });
2957
- var destroyGuest = (where, declared) => Effect32.gen(function* () {
3065
+ var destroyGuest = (where, declared) => Effect34.gen(function* () {
2958
3066
  const live = yield* readLive(where);
2959
3067
  if (live === undefined)
2960
3068
  return;
2961
3069
  const drift = judge(declared, live).drift;
2962
3070
  if (drift.length > 0) {
2963
- return yield* Effect32.fail(new LxcRefusedError(`destroy CT ${String(where.vmid)}: the guest on ${where.node} no longer matches its last ` + `declaration (${drift.join(", ")}), so it may not be the guest this stack made. ` + "Nothing was deleted. Delete it by hand if it should go, or declare it again."));
3071
+ return yield* Effect34.fail(new LxcRefusedError(`destroy CT ${String(where.vmid)}: the guest on ${where.node} no longer matches its last ` + `declaration (${drift.join(", ")}), so it may not be the guest this stack made. ` + "Nothing was deleted. Delete it by hand if it should go, or declare it again."));
2964
3072
  }
2965
3073
  yield* task(where, "DELETE", guestPath(where), `destroy CT ${String(where.vmid)}`, TASK_POLLS);
2966
3074
  });
2967
3075
 
2968
3076
  // src/proxmox/lxc-adoption.ts
2969
3077
  var adoptionRefusal = (props, drift) => `CT ${String(props.vmid)} on ${props.node}: adopting it would change ${drift.join(", ")}. An ` + "adoption never changes a guest, so nothing is written. Declare what is live for these keys " + "(the plan names keys, never values) and plan again; change them after the adoption, as an " + "update.";
2970
- var refuseAdoptedDrift = (owner, props, live, change) => Effect33.gen(function* () {
3078
+ var refuseAdoptedDrift = (owner, props, live, change) => Effect35.gen(function* () {
2971
3079
  if (change.drift.length === 0)
2972
3080
  return;
2973
- const settled2 = (recorded) => Effect33.sync(() => judge(recorded, live).drift.length === 0);
3081
+ const settled2 = (recorded) => Effect35.sync(() => judge(recorded, live).drift.length === 0);
2974
3082
  if (!(yield* adopting(owner, settled2)))
2975
3083
  return;
2976
- return yield* Effect33.fail(new LxcRefusedError(adoptionRefusal(props, change.drift)));
3084
+ return yield* Effect35.fail(new LxcRefusedError(adoptionRefusal(props, change.drift)));
2977
3085
  });
2978
3086
 
2979
3087
  // src/proxmox/lxc.ts
2980
3088
  var ProxmoxLxc = Resource13("Proxmox.Lxc", { defaultRemovalPolicy: "retain" });
2981
- var refused = (reasons) => Effect34.fail(new LxcRefusedError(reasons.join(`
3089
+ var refused = (reasons) => Effect36.fail(new LxcRefusedError(reasons.join(`
2982
3090
  `)));
2983
- var diff = (owner, news, olds) => Effect34.gen(function* () {
3091
+ var diff = (owner, news, olds) => Effect36.gen(function* () {
2984
3092
  const { output } = owner;
2985
3093
  const identity2 = identityRefusals(news, olds, output);
2986
3094
  if (identity2.length > 0)
2987
3095
  return yield* refused(identity2);
2988
3096
  if (output === undefined)
2989
3097
  return;
2990
- if (!isResolved6(news))
3098
+ if (!isResolved8(news))
2991
3099
  return;
2992
3100
  const live = yield* readLive(news);
2993
3101
  if (live === undefined) {
2994
3102
  const refusals = createRefusals(news);
2995
3103
  if (refusals.length > 0)
2996
3104
  return yield* refused(refusals);
2997
- yield* Effect34.logWarning(`Proxmox.Lxc ${news.node}/${String(news.vmid)}: the cluster lists no guest with this ` + "vmid -- a deploy CREATES it again from ostemplate, with new, empty volumes.");
3105
+ yield* Effect36.logWarning(`Proxmox.Lxc ${news.node}/${String(news.vmid)}: the cluster lists no guest with this ` + "vmid -- a deploy CREATES it again from ostemplate, with new, empty volumes.");
2998
3106
  return { action: "update" };
2999
3107
  }
3000
3108
  const change = judge(news, live);
@@ -3005,10 +3113,10 @@ var diff = (owner, news, olds) => Effect34.gen(function* () {
3005
3113
  if (change.drift.length === 0 && !moved)
3006
3114
  return { action: "noop" };
3007
3115
  const what = change.drift.length === 0 ? `the guest is on ${news.node} now, not ${output.node} -- a deploy re-records it, ` + "writing nothing." : `live config differs from the declaration in ${change.drift.join(", ")} -- a ` + "deploy writes these.";
3008
- yield* Effect34.logWarning(`Proxmox.Lxc ${news.node}/${String(news.vmid)}: ${what}`);
3116
+ yield* Effect36.logWarning(`Proxmox.Lxc ${news.node}/${String(news.vmid)}: ${what}`);
3009
3117
  return { action: "update" };
3010
3118
  });
3011
- var reconcile = (owner, news) => Effect34.gen(function* () {
3119
+ var reconcile = (owner, news) => Effect36.gen(function* () {
3012
3120
  const { output } = owner;
3013
3121
  const identity2 = identityRefusals(news, news, output);
3014
3122
  if (identity2.length > 0)
@@ -3047,22 +3155,22 @@ var reconcile = (owner, news) => Effect34.gen(function* () {
3047
3155
  `CT ${String(news.vmid)}: after the write, ${left.join(", ")} still differ from the ` + "declaration. PVE may have normalised the value (tags are lower-cased by default) -- " + "declare what it stores."
3048
3156
  ]);
3049
3157
  }
3050
- return attributesOf2(news, after);
3158
+ return attributesOf4(news, after);
3051
3159
  });
3052
3160
  var whereOf = (olds, output) => ({
3053
3161
  node: output?.node ?? olds.node,
3054
3162
  target: olds.target,
3055
3163
  vmid: output?.vmid ?? olds.vmid
3056
3164
  });
3057
- var read = (owner, olds) => Effect34.gen(function* () {
3165
+ var read = (owner, olds) => Effect36.gen(function* () {
3058
3166
  const where = whereOf(olds, owner.output);
3059
3167
  const live = yield* readLive(where);
3060
3168
  if (live === undefined)
3061
3169
  return;
3062
- const settled2 = Effect34.sync(() => judge(olds, live).drift.length === 0);
3063
- return yield* ownedRead(owner, attributesOf2(where, live), settled2);
3170
+ const settled2 = Effect36.sync(() => judge(olds, live).drift.length === 0);
3171
+ return yield* ownedRead(owner, attributesOf4(where, live), settled2);
3064
3172
  });
3065
- var handlers10 = {
3173
+ var handlers8 = {
3066
3174
  delete: ({ olds, output }) => destroyGuest(whereOf(olds, output), olds),
3067
3175
  diff: ({
3068
3176
  fqn,
@@ -3071,7 +3179,7 @@ var handlers10 = {
3071
3179
  olds,
3072
3180
  output
3073
3181
  }) => diff({ fqn, instanceId, output }, news, olds),
3074
- list: () => Effect34.succeed([]),
3182
+ list: () => Effect36.succeed([]),
3075
3183
  read: ({
3076
3184
  fqn,
3077
3185
  instanceId,
@@ -3085,11 +3193,11 @@ var handlers10 = {
3085
3193
  output
3086
3194
  }) => reconcile({ fqn, instanceId, output }, news)
3087
3195
  };
3088
- var ProxmoxLxcProvider = () => Provider13.effect(ProxmoxLxc, Effect34.succeed(ProxmoxLxc.Provider.of(handlers10)).pipe(Effect34.map(provingResumes)));
3196
+ var ProxmoxLxcProvider = () => Provider13.effect(ProxmoxLxc, Effect36.succeed(ProxmoxLxc.Provider.of(handlers8)).pipe(Effect36.map(provingResumes)));
3089
3197
  // src/proxmox/metric-server.ts
3090
3198
  import { Resource as Resource14 } from "alchemy";
3091
3199
  import * as Provider14 from "alchemy/Provider";
3092
- import * as Effect35 from "effect/Effect";
3200
+ import * as Effect37 from "effect/Effect";
3093
3201
 
3094
3202
  // src/proxmox/metric-server-form.ts
3095
3203
  var UNSET2 = -1;
@@ -3169,7 +3277,7 @@ var matchesType = (attributes, props) => {
3169
3277
  return socket && attributes.path === (props.path ?? "");
3170
3278
  return socket && attributes.influxdbproto === (props.influxdbproto ?? "") && attributes.organization === (props.organization ?? "") && attributes.bucket === (props.bucket ?? "") && attributes["api-path-prefix"] === (props["api-path-prefix"] ?? "") && attributes["max-body-size"] === (props["max-body-size"] ?? UNSET2) && attributes["verify-certificate"] === (props["verify-certificate"] !== false);
3171
3279
  };
3172
- var handlers11 = pveHandlers({
3280
+ var handlers9 = pveHandlers({
3173
3281
  attributes: (live, props) => {
3174
3282
  const liveType = text(live["type"]);
3175
3283
  if (liveType !== "" && liveType !== props.type)
@@ -3202,12 +3310,12 @@ var handlers11 = pveHandlers({
3202
3310
  path: (props) => `cluster/metrics/server/${props.id}`,
3203
3311
  updateForm: update
3204
3312
  });
3205
- var ProxmoxMetricServerProvider = () => Provider14.effect(ProxmoxMetricServer, Effect35.succeed(ProxmoxMetricServer.Provider.of(handlers11)));
3313
+ var ProxmoxMetricServerProvider = () => Provider14.effect(ProxmoxMetricServer, Effect37.succeed(ProxmoxMetricServer.Provider.of(handlers9)));
3206
3314
  // src/proxmox/network-apply.ts
3207
3315
  import { Resource as Resource15 } from "alchemy";
3208
- import { isResolved as isResolved7 } from "alchemy/Diff";
3316
+ import { isResolved as isResolved9 } from "alchemy/Diff";
3209
3317
  import * as Provider15 from "alchemy/Provider";
3210
- import * as Effect36 from "effect/Effect";
3318
+ import * as Effect38 from "effect/Effect";
3211
3319
 
3212
3320
  // src/proxmox/apply-endpoints.ts
3213
3321
  var NETWORK_APPLY_ENDPOINT = "pve:PUT /nodes/{node}/network";
@@ -3217,15 +3325,15 @@ var guardSdnApply = guardForm(SDN_APPLY_ENDPOINT, {}, true);
3217
3325
 
3218
3326
  // src/proxmox/network-apply.ts
3219
3327
  var ProxmoxNetworkApply = Resource15("Proxmox.NetworkApply");
3220
- var read2 = (props) => pendingCount(props.target, props.node).pipe(Effect36.map((pending) => ({ node: props.node, pending })));
3221
- var diff2 = (news) => Effect36.gen(function* () {
3222
- if (!isResolved7(news))
3328
+ var read2 = (props) => pendingCount(props.target, props.node).pipe(Effect38.map((pending) => ({ node: props.node, pending })));
3329
+ var diff2 = (news) => Effect38.gen(function* () {
3330
+ if (!isResolved9(news))
3223
3331
  return;
3224
3332
  const { pending } = yield* read2(news);
3225
3333
  return pending === 0 ? { action: "noop" } : { action: "update" };
3226
3334
  });
3227
- var refuse = (node, why) => Effect36.die(new Error(`Proxmox.NetworkApply ${node}: ${why}`));
3228
- var apply = (props) => Effect36.gen(function* () {
3335
+ var refuse = (node, why) => Effect38.die(new Error(`Proxmox.NetworkApply ${node}: ${why}`));
3336
+ var apply = (props) => Effect38.gen(function* () {
3229
3337
  const node = props.node;
3230
3338
  const staged = yield* pendingCount(props.target, node);
3231
3339
  if (staged === 0)
@@ -3254,18 +3362,18 @@ var apply = (props) => Effect36.gen(function* () {
3254
3362
  }
3255
3363
  return { node, pending: left };
3256
3364
  });
3257
- var handlers12 = {
3258
- delete: () => Effect36.void,
3365
+ var handlers10 = {
3366
+ delete: () => Effect38.void,
3259
3367
  diff: ({ news }) => diff2(news),
3260
- list: () => Effect36.succeed([]),
3368
+ list: () => Effect38.succeed([]),
3261
3369
  read: ({ olds }) => read2(olds),
3262
3370
  reconcile: ({ news }) => apply(news)
3263
3371
  };
3264
- var ProxmoxNetworkApplyProvider = () => Provider15.effect(ProxmoxNetworkApply, Effect36.succeed(ProxmoxNetworkApply.Provider.of(handlers12)));
3372
+ var ProxmoxNetworkApplyProvider = () => Provider15.effect(ProxmoxNetworkApply, Effect38.succeed(ProxmoxNetworkApply.Provider.of(handlers10)));
3265
3373
  // src/proxmox/node-network.ts
3266
3374
  import { Resource as Resource16 } from "alchemy";
3267
3375
  import * as Provider16 from "alchemy/Provider";
3268
- import * as Effect37 from "effect/Effect";
3376
+ import * as Effect39 from "effect/Effect";
3269
3377
 
3270
3378
  // src/proxmox/node-network-form.ts
3271
3379
  var UNSET3 = -1;
@@ -3341,7 +3449,7 @@ var readAttributes2 = (live, props) => {
3341
3449
  var ProxmoxNodeNetwork = Resource16("Proxmox.NodeNetwork", {
3342
3450
  defaultRemovalPolicy: "retain"
3343
3451
  });
3344
- var handlers13 = pveHandlers({
3452
+ var handlers11 = pveHandlers({
3345
3453
  attributes: readAttributes2,
3346
3454
  collection: (props) => `nodes/${props.node}/network`,
3347
3455
  createForm: createBody3,
@@ -3353,11 +3461,11 @@ var handlers13 = pveHandlers({
3353
3461
  path: (props) => `nodes/${props.node}/network/${props.iface}`,
3354
3462
  updateForm: updateBody2
3355
3463
  });
3356
- var ProxmoxNodeNetworkProvider = () => Provider16.effect(ProxmoxNodeNetwork, Effect37.succeed(ProxmoxNodeNetwork.Provider.of(handlers13)));
3464
+ var ProxmoxNodeNetworkProvider = () => Provider16.effect(ProxmoxNodeNetwork, Effect39.succeed(ProxmoxNodeNetwork.Provider.of(handlers11)));
3357
3465
  // src/proxmox/notification-matcher.ts
3358
3466
  import { Resource as Resource17 } from "alchemy";
3359
3467
  import * as Provider17 from "alchemy/Provider";
3360
- import * as Effect38 from "effect/Effect";
3468
+ import * as Effect40 from "effect/Effect";
3361
3469
 
3362
3470
  // src/proxmox/notification-matcher-form.ts
3363
3471
  var list = (value) => Array.isArray(value) ? value.map((item) => String(item)) : [];
@@ -3435,7 +3543,7 @@ var matcherUpdateForm = (fields) => {
3435
3543
 
3436
3544
  // src/proxmox/notification-matcher.ts
3437
3545
  var ProxmoxNotificationMatcher = Resource17("Proxmox.NotificationMatcher");
3438
- var handlers14 = pveHandlers({
3546
+ var handlers12 = pveHandlers({
3439
3547
  attributes: (live, props) => matcherAttributes(live, props.name),
3440
3548
  collection: () => "cluster/notifications/matchers",
3441
3549
  createForm: matcherCreateForm,
@@ -3447,11 +3555,11 @@ var handlers14 = pveHandlers({
3447
3555
  path: (props) => `cluster/notifications/matchers/${props.name}`,
3448
3556
  updateForm: matcherUpdateForm
3449
3557
  });
3450
- var ProxmoxNotificationMatcherProvider = () => Provider17.effect(ProxmoxNotificationMatcher, Effect38.succeed(ProxmoxNotificationMatcher.Provider.of(handlers14)));
3558
+ var ProxmoxNotificationMatcherProvider = () => Provider17.effect(ProxmoxNotificationMatcher, Effect40.succeed(ProxmoxNotificationMatcher.Provider.of(handlers12)));
3451
3559
  // src/proxmox/notification-target.ts
3452
3560
  import { Resource as Resource18 } from "alchemy";
3453
3561
  import * as Provider18 from "alchemy/Provider";
3454
- import * as Effect39 from "effect/Effect";
3562
+ import * as Effect41 from "effect/Effect";
3455
3563
 
3456
3564
  // src/proxmox/notification-target-endpoint.ts
3457
3565
  var ENDPOINTS = {
@@ -3507,7 +3615,7 @@ var createShape = (props) => ({
3507
3615
 
3508
3616
  // src/proxmox/notification-target.ts
3509
3617
  var ProxmoxNotificationTarget = Resource18("Proxmox.NotificationTarget");
3510
- var handlers15 = pveHandlers({
3618
+ var handlers13 = pveHandlers({
3511
3619
  attributes: (live, props) => ({
3512
3620
  author: text(live["author"]),
3513
3621
  body: text(live["body"]),
@@ -3533,15 +3641,15 @@ var handlers15 = pveHandlers({
3533
3641
  path: (props) => `cluster/notifications/endpoints/${props.type}/${props.name}`,
3534
3642
  updateForm: shape4
3535
3643
  });
3536
- var ProxmoxNotificationTargetProvider = () => Provider18.effect(ProxmoxNotificationTarget, Effect39.succeed(ProxmoxNotificationTarget.Provider.of(handlers15)));
3644
+ var ProxmoxNotificationTargetProvider = () => Provider18.effect(ProxmoxNotificationTarget, Effect41.succeed(ProxmoxNotificationTarget.Provider.of(handlers13)));
3537
3645
  // src/proxmox/pbs-datastore.ts
3538
3646
  import { Resource as Resource19 } from "alchemy";
3539
- import { isResolved as isResolved8 } from "alchemy/Diff";
3647
+ import { isResolved as isResolved10 } from "alchemy/Diff";
3540
3648
  import * as Provider19 from "alchemy/Provider";
3541
- import * as Effect43 from "effect/Effect";
3649
+ import * as Effect45 from "effect/Effect";
3542
3650
 
3543
3651
  // src/proxmox/pbs-datastore-form.ts
3544
- import * as Effect40 from "effect/Effect";
3652
+ import * as Effect42 from "effect/Effect";
3545
3653
  var attributes = (live, props) => ({
3546
3654
  backend: propertyString(live["backend"]),
3547
3655
  comment: text(live["comment"]),
@@ -3564,7 +3672,7 @@ var attributes = (live, props) => ({
3564
3672
  "verify-new": bool(live["verify-new"])
3565
3673
  });
3566
3674
  var same4 = (declared, live) => declared === undefined || declared === live;
3567
- var matches2 = (live, props) => same4(props.comment, live.comment) && same4(props["gc-schedule"]?.trim(), live["gc-schedule"]) && same4(props["prune-schedule"]?.trim(), live["prune-schedule"]) && same4(props["keep-last"], live["keep-last"]) && same4(props["keep-hourly"], live["keep-hourly"]) && same4(props["keep-daily"], live["keep-daily"]) && same4(props["keep-weekly"], live["keep-weekly"]) && same4(props["keep-monthly"], live["keep-monthly"]) && same4(props["keep-yearly"], live["keep-yearly"]) && same4(props["notify-user"], live["notify-user"]) && same4(props["counter-reset-schedule"]?.trim(), live["counter-reset-schedule"]) && (props["notification-thresholds"] === undefined || propertyString(props["notification-thresholds"]) === live["notification-thresholds"]) && same4(props["verify-new"], live["verify-new"]) && (props.notify === undefined || propertyString(props.notify) === live.notify) && (props.tuning === undefined || propertyString(props.tuning) === live.tuning) && (props["maintenance-mode"] === undefined || propertyString(props["maintenance-mode"], "type") === live["maintenance-mode"]);
3675
+ var matches4 = (live, props) => same4(props.comment, live.comment) && same4(props["gc-schedule"]?.trim(), live["gc-schedule"]) && same4(props["prune-schedule"]?.trim(), live["prune-schedule"]) && same4(props["keep-last"], live["keep-last"]) && same4(props["keep-hourly"], live["keep-hourly"]) && same4(props["keep-daily"], live["keep-daily"]) && same4(props["keep-weekly"], live["keep-weekly"]) && same4(props["keep-monthly"], live["keep-monthly"]) && same4(props["keep-yearly"], live["keep-yearly"]) && same4(props["notify-user"], live["notify-user"]) && same4(props["counter-reset-schedule"]?.trim(), live["counter-reset-schedule"]) && (props["notification-thresholds"] === undefined || propertyString(props["notification-thresholds"]) === live["notification-thresholds"]) && same4(props["verify-new"], live["verify-new"]) && (props.notify === undefined || propertyString(props.notify) === live.notify) && (props.tuning === undefined || propertyString(props.tuning) === live.tuning) && (props["maintenance-mode"] === undefined || propertyString(props["maintenance-mode"], "type") === live["maintenance-mode"]);
3568
3676
  var set2 = (key, value) => value === undefined ? {} : { [key]: value };
3569
3677
  var count = (value) => value === undefined ? undefined : String(value);
3570
3678
  var mutable = (props) => ({
@@ -3585,56 +3693,56 @@ var mutable = (props) => ({
3585
3693
  ...set2("tuning", props.tuning),
3586
3694
  ...set2("verify-new", flag(props["verify-new"]))
3587
3695
  });
3588
- var createForm4 = (props) => ({
3696
+ var createForm5 = (props) => ({
3589
3697
  ...mutable(props),
3590
3698
  name: props.name,
3591
3699
  path: props.path,
3592
3700
  ...props.backend === undefined ? {} : { backend: props.backend }
3593
3701
  });
3594
- var updateForm = (props) => mutable(props);
3702
+ var updateForm2 = (props) => mutable(props);
3595
3703
  var object2 = (props) => `config/datastore/${props.name}`;
3596
- var readOne = (props) => pve(props.target, "read", "GET", object2(props)).pipe(Effect40.map((live) => live === undefined || typeof live["name"] !== "string" ? undefined : attributes(live, props)), Effect40.orElseSucceed(() => {
3704
+ var readOne = (props) => pve(props.target, "read", "GET", object2(props)).pipe(Effect42.map((live) => live === undefined || typeof live["name"] !== "string" ? undefined : attributes(live, props)), Effect42.orElseSucceed(() => {
3597
3705
  return;
3598
3706
  }));
3599
3707
 
3600
3708
  // src/proxmox/pbs-datastore-guard.ts
3601
- import * as Effect41 from "effect/Effect";
3709
+ import * as Effect43 from "effect/Effect";
3602
3710
  var samePath = (left, right) => {
3603
3711
  const trim = (value) => value.length > 1 ? value.replace(/\/+$/, "") : value;
3604
3712
  return trim(left) === trim(right);
3605
3713
  };
3606
- var guardPath = (live, props) => live.path === "" || samePath(live.path, props.path) ? Effect41.void : Effect41.die(new Error(`${object2(props)}: declared path ${props.path} but PBS holds ${live.path}. \`path\` ` + "is create-only, so this cannot be reconciled and will NOT be planned as a silent " + "noop: backups would keep landing in the old directory behind a green plan. Correct " + "the declaration, or remove the datastore and declare a new one -- the contents live, " + "because `destroy-data` is never sent."));
3607
- var guardBackend = (live, props) => props.backend === undefined || propertyString(props.backend) === live.backend ? Effect41.void : Effect41.die(new Error(`${object2(props)}: declared backend ${props.backend} but PBS holds ` + `${live.backend === "" ? "(none — a local datastore)" : live.backend}. \`backend\` ` + "is create-only: PBS has no update for it, so this cannot be reconciled and will NOT " + "be planned as a silent noop. Correct the declaration to match what is really there, " + "or remove the datastore and declare a new one -- the contents live, because " + "`destroy-data` is never sent."));
3608
- var settle3 = (read3, done) => Effect41.gen(function* () {
3714
+ var guardPath = (live, props) => live.path === "" || samePath(live.path, props.path) ? Effect43.void : Effect43.die(new Error(`${object2(props)}: declared path ${props.path} but PBS holds ${live.path}. \`path\` ` + "is create-only, so this cannot be reconciled and will NOT be planned as a silent " + "noop: backups would keep landing in the old directory behind a green plan. Correct " + "the declaration, or remove the datastore and declare a new one -- the contents live, " + "because `destroy-data` is never sent."));
3715
+ var guardBackend = (live, props) => props.backend === undefined || propertyString(props.backend) === live.backend ? Effect43.void : Effect43.die(new Error(`${object2(props)}: declared backend ${props.backend} but PBS holds ` + `${live.backend === "" ? "(none — a local datastore)" : live.backend}. \`backend\` ` + "is create-only: PBS has no update for it, so this cannot be reconciled and will NOT " + "be planned as a silent noop. Correct the declaration to match what is really there, " + "or remove the datastore and declare a new one -- the contents live, because " + "`destroy-data` is never sent."));
3716
+ var settle3 = (read3, done) => Effect43.gen(function* () {
3609
3717
  for (let attempt = 0;attempt < 60; attempt += 1) {
3610
3718
  const live = yield* read3;
3611
3719
  if (done(live))
3612
3720
  return live;
3613
- yield* Effect41.sleep("2 seconds");
3721
+ yield* Effect43.sleep("2 seconds");
3614
3722
  }
3615
3723
  return yield* read3;
3616
3724
  });
3617
3725
 
3618
3726
  // src/proxmox/pbs-datastore-endpoint.ts
3619
- import * as Effect42 from "effect/Effect";
3727
+ import * as Effect44 from "effect/Effect";
3620
3728
  var DATASTORE_ENDPOINT = {
3621
3729
  create: "pbs:POST /config/datastore",
3622
3730
  update: "pbs:PUT /config/datastore/{name}"
3623
3731
  };
3624
- var guardDatastoreForms = (props) => Effect42.gen(function* () {
3625
- yield* guardForm(DATASTORE_ENDPOINT.create, createForm4(props), true);
3626
- yield* guardForm(DATASTORE_ENDPOINT.update, updateForm(props), false);
3732
+ var guardDatastoreForms = (props) => Effect44.gen(function* () {
3733
+ yield* guardForm(DATASTORE_ENDPOINT.create, createForm5(props), true);
3734
+ yield* guardForm(DATASTORE_ENDPOINT.update, updateForm2(props), false);
3627
3735
  });
3628
3736
 
3629
3737
  // src/proxmox/pbs-datastore.ts
3630
3738
  var PbsDatastore = Resource19("Pbs.Datastore", {
3631
3739
  defaultRemovalPolicy: "retain"
3632
3740
  });
3633
- var PbsDatastoreProvider = () => Provider19.effect(PbsDatastore, Effect43.succeed(PbsDatastore.Provider.of({
3634
- list: () => Effect43.succeed([]),
3741
+ var PbsDatastoreProvider = () => Provider19.effect(PbsDatastore, Effect45.succeed(PbsDatastore.Provider.of({
3742
+ list: () => Effect45.succeed([]),
3635
3743
  read: ({ olds }) => readOne(olds),
3636
- diff: ({ news, output }) => Effect43.gen(function* () {
3637
- if (!isResolved8(news))
3744
+ diff: ({ news, output }) => Effect45.gen(function* () {
3745
+ if (!isResolved10(news))
3638
3746
  return;
3639
3747
  yield* guardDatastoreForms(news);
3640
3748
  if (output === undefined)
@@ -3644,42 +3752,42 @@ var PbsDatastoreProvider = () => Provider19.effect(PbsDatastore, Effect43.succee
3644
3752
  return { action: "update" };
3645
3753
  yield* guardPath(live, news);
3646
3754
  yield* guardBackend(live, news);
3647
- return matches2(live, news) ? { action: "noop" } : { action: "update" };
3755
+ return matches4(live, news) ? { action: "noop" } : { action: "update" };
3648
3756
  }),
3649
- reconcile: Effect43.fn(function* ({ news }) {
3757
+ reconcile: Effect45.fn(function* ({ news }) {
3650
3758
  yield* guardDatastoreForms(news);
3651
3759
  const live = yield* readOne(news);
3652
3760
  let upid;
3653
3761
  if (live === undefined) {
3654
- upid = yield* pve(news.target, "provision", "POST", "config/datastore", createForm4(news));
3762
+ upid = yield* pve(news.target, "provision", "POST", "config/datastore", createForm5(news));
3655
3763
  } else {
3656
3764
  yield* guardPath(live, news);
3657
3765
  yield* guardBackend(live, news);
3658
- const form = formToSend(matches2, live, news, updateForm(news));
3766
+ const form = formToSend(matches4, live, news, updateForm2(news));
3659
3767
  if (form !== undefined) {
3660
3768
  yield* pve(news.target, "provision", "PUT", object2(news), form);
3661
3769
  }
3662
3770
  }
3663
- const after = yield* settle3(readOne(news), (row) => row !== undefined && matches2(row, news));
3664
- if (after === undefined || !matches2(after, news)) {
3665
- return yield* Effect43.die(new Error(`${object2(news)}: the write returned no error but the datastore still does not ` + `match the declaration after 120s. PBS answered with task ${upid ?? "(none)"} ` + "-- read it with `proxmox-backup-manager task log <upid>`. A slow disk is a " + "plausible cause (see `settle`); a create over an existing chunk store is not, " + "because PBS refuses that outright rather than timing out."));
3771
+ const after = yield* settle3(readOne(news), (row) => row !== undefined && matches4(row, news));
3772
+ if (after === undefined || !matches4(after, news)) {
3773
+ return yield* Effect45.die(new Error(`${object2(news)}: the write returned no error but the datastore still does not ` + `match the declaration after 120s. PBS answered with task ${upid ?? "(none)"} ` + "-- read it with `proxmox-backup-manager task log <upid>`. A slow disk is a " + "plausible cause (see `settle`); a create over an existing chunk store is not, " + "because PBS refuses that outright rather than timing out."));
3666
3774
  }
3667
3775
  return after;
3668
3776
  }),
3669
- delete: Effect43.fn(function* ({ olds }) {
3777
+ delete: Effect45.fn(function* ({ olds }) {
3670
3778
  yield* pve(olds.target, "provision", "DELETE", object2(olds));
3671
3779
  const left = yield* settle3(readOne(olds), (row) => row === undefined);
3672
3780
  if (left !== undefined) {
3673
- return yield* Effect43.die(new Error(`${object2(olds)}: the DELETE returned no error but the datastore is still in ` + "datastore.cfg after 120s. Either a backup, verify or GC task still holds it, " + "or the provision role lacks Datastore.Allocate on /datastore/" + `${olds.name}. Read the task log with \`proxmox-backup-manager task log <upid>\`.`));
3781
+ return yield* Effect45.die(new Error(`${object2(olds)}: the DELETE returned no error but the datastore is still in ` + "datastore.cfg after 120s. Either a backup, verify or GC task still holds it, " + "or the provision role lacks Datastore.Allocate on /datastore/" + `${olds.name}. Read the task log with \`proxmox-backup-manager task log <upid>\`.`));
3674
3782
  }
3675
3783
  })
3676
3784
  })));
3677
3785
  // src/proxmox/pbs-notification-matcher.ts
3678
3786
  import { Resource as Resource20 } from "alchemy";
3679
3787
  import * as Provider20 from "alchemy/Provider";
3680
- import * as Effect44 from "effect/Effect";
3788
+ import * as Effect46 from "effect/Effect";
3681
3789
  var PbsNotificationMatcher = Resource20("Pbs.NotificationMatcher");
3682
- var handlers16 = pveHandlers({
3790
+ var handlers14 = pveHandlers({
3683
3791
  attributes: (live, props) => matcherAttributes(live, props.name),
3684
3792
  collection: () => "config/notifications/matchers",
3685
3793
  createForm: matcherCreateForm,
@@ -3691,15 +3799,15 @@ var handlers16 = pveHandlers({
3691
3799
  path: (props) => `config/notifications/matchers/${props.name}`,
3692
3800
  updateForm: matcherUpdateForm
3693
3801
  });
3694
- var PbsNotificationMatcherProvider = () => Provider20.effect(PbsNotificationMatcher, Effect44.succeed(PbsNotificationMatcher.Provider.of(handlers16)));
3802
+ var PbsNotificationMatcherProvider = () => Provider20.effect(PbsNotificationMatcher, Effect46.succeed(PbsNotificationMatcher.Provider.of(handlers14)));
3695
3803
  // src/proxmox/pbs-notification-target.ts
3696
3804
  import { Resource as Resource21 } from "alchemy";
3697
3805
  import * as Provider21 from "alchemy/Provider";
3698
- import * as Effect46 from "effect/Effect";
3806
+ import * as Effect48 from "effect/Effect";
3699
3807
 
3700
3808
  // src/proxmox/pbs-notification-target-lifecycle.ts
3701
- import { isResolved as isResolved9 } from "alchemy/Diff";
3702
- import * as Effect45 from "effect/Effect";
3809
+ import { isResolved as isResolved11 } from "alchemy/Diff";
3810
+ import * as Effect47 from "effect/Effect";
3703
3811
 
3704
3812
  // src/proxmox/pbs-notification-target-endpoint.ts
3705
3813
  var ENDPOINTS2 = {
@@ -3976,12 +4084,12 @@ var targetForm = (props, groups, carry, mode) => {
3976
4084
 
3977
4085
  // src/proxmox/pbs-notification-target-lifecycle.ts
3978
4086
  var path = (props) => `config/notifications/endpoints/${props.type}/${props.name}`;
3979
- var readLive2 = (props) => pve(props.target, "read", "GET", path(props)).pipe(Effect45.map((data) => data == null ? undefined : targetAttributes(data, props)), Effect45.orElseSucceed(() => {
4087
+ var readLive2 = (props) => pve(props.target, "read", "GET", path(props)).pipe(Effect47.map((data) => data == null ? undefined : targetAttributes(data, props)), Effect47.orElseSucceed(() => {
3980
4088
  return;
3981
4089
  }));
3982
4090
  var refuse2 = (props) => {
3983
4091
  const reasons = refusals(props);
3984
- return reasons.length === 0 ? Effect45.void : Effect45.die(new Error(`${path(props)}: ${reasons.join("; ")}.`));
4092
+ return reasons.length === 0 ? Effect47.void : Effect47.die(new Error(`${path(props)}: ${reasons.join("; ")}.`));
3985
4093
  };
3986
4094
  var toCarry = (live, props, groups, sealed) => {
3987
4095
  const header = headerState(live, props, groups) === "stale";
@@ -3993,17 +4101,17 @@ var requireValues = (props, groups, carry) => {
3993
4101
  ...carry.header ? groups.header.missing : [],
3994
4102
  ...carry.sealed ? groups.sealed.missing : []
3995
4103
  ];
3996
- return missing.length === 0 ? Effect45.void : Effect45.die(new Error(`${path(props)}: this write must send write-only values, and ${missing.join(", ")} ` + "is unset or empty in the deploying environment. Export it and deploy again."));
4104
+ return missing.length === 0 ? Effect47.void : Effect47.die(new Error(`${path(props)}: this write must send write-only values, and ${missing.join(", ")} ` + "is unset or empty in the deploying environment. Export it and deploy again."));
3997
4105
  };
3998
- var handlers17 = {
3999
- list: () => Effect45.succeed([]),
4000
- read: ({ olds, output }) => (output === undefined ? refuse2(olds) : Effect45.void).pipe(Effect45.andThen(readLive2(olds)), Effect45.map((live) => live === undefined ? undefined : { ...live, sealed: output?.sealed ?? "" })),
4106
+ var handlers15 = {
4107
+ list: () => Effect47.succeed([]),
4108
+ read: ({ olds, output }) => (output === undefined ? refuse2(olds) : Effect47.void).pipe(Effect47.andThen(readLive2(olds)), Effect47.map((live) => live === undefined ? undefined : { ...live, sealed: output?.sealed ?? "" })),
4001
4109
  diff: ({
4002
4110
  news,
4003
4111
  olds,
4004
4112
  output
4005
- }) => Effect45.gen(function* () {
4006
- if (output === undefined || !isResolved9(news))
4113
+ }) => Effect47.gen(function* () {
4114
+ if (output === undefined || !isResolved11(news))
4007
4115
  return;
4008
4116
  if (olds !== undefined && (olds.type !== news.type || olds.name !== news.name)) {
4009
4117
  return { action: "replace", deleteFirst: olds.name === news.name };
@@ -4014,7 +4122,7 @@ var handlers17 = {
4014
4122
  return { action: "update" };
4015
4123
  return toCarry(live, news, resolveGroups(news), output.sealed) === undefined ? { action: "noop" } : { action: "update" };
4016
4124
  }),
4017
- reconcile: ({ news, output }) => Effect45.gen(function* () {
4125
+ reconcile: ({ news, output }) => Effect47.gen(function* () {
4018
4126
  yield* refuse2(news);
4019
4127
  const groups = resolveGroups(news);
4020
4128
  const live = yield* readLive2(news);
@@ -4040,7 +4148,7 @@ var handlers17 = {
4040
4148
  }
4041
4149
  const after = yield* readLive2(news);
4042
4150
  if (after === undefined) {
4043
- return yield* Effect45.die(new Error(`${path(news)}: the write returned no error but the target is still absent.`));
4151
+ return yield* Effect47.die(new Error(`${path(news)}: the write returned no error but the target is still absent.`));
4044
4152
  }
4045
4153
  return { ...after, sealed };
4046
4154
  }),
@@ -4049,11 +4157,11 @@ var handlers17 = {
4049
4157
 
4050
4158
  // src/proxmox/pbs-notification-target.ts
4051
4159
  var PbsNotificationTarget = Resource21("Pbs.NotificationTarget");
4052
- var PbsNotificationTargetProvider = () => Provider21.effect(PbsNotificationTarget, Effect46.succeed(PbsNotificationTarget.Provider.of(handlers17)));
4160
+ var PbsNotificationTargetProvider = () => Provider21.effect(PbsNotificationTarget, Effect48.succeed(PbsNotificationTarget.Provider.of(handlers15)));
4053
4161
  // src/proxmox/pbs-prune-job.ts
4054
4162
  import { Resource as Resource22 } from "alchemy";
4055
4163
  import * as Provider22 from "alchemy/Provider";
4056
- import * as Effect47 from "effect/Effect";
4164
+ import * as Effect49 from "effect/Effect";
4057
4165
 
4058
4166
  // src/proxmox/pbs-prune-job-form.ts
4059
4167
  var KEEP_KEYS = [
@@ -4111,7 +4219,7 @@ var updateBody3 = (props) => {
4111
4219
  // src/proxmox/pbs-prune-job.ts
4112
4220
  var PbsPruneJob = Resource22("Pbs.PruneJob");
4113
4221
  var same6 = (declared2, live) => declared2 === undefined || declared2 === live;
4114
- var handlers18 = pveHandlers({
4222
+ var handlers16 = pveHandlers({
4115
4223
  attributes: (live, props) => ({
4116
4224
  comment: text(live["comment"], ""),
4117
4225
  disable: bool(live["disable"], false),
@@ -4129,11 +4237,11 @@ var handlers18 = pveHandlers({
4129
4237
  path: (props) => `config/prune/${props.id}`,
4130
4238
  updateForm: updateBody3
4131
4239
  });
4132
- var PbsPruneJobProvider = () => Provider22.effect(PbsPruneJob, Effect47.succeed(PbsPruneJob.Provider.of(handlers18)));
4240
+ var PbsPruneJobProvider = () => Provider22.effect(PbsPruneJob, Effect49.succeed(PbsPruneJob.Provider.of(handlers16)));
4133
4241
  // src/proxmox/pbs-sync-job.ts
4134
4242
  import { Resource as Resource23 } from "alchemy";
4135
4243
  import * as Provider23 from "alchemy/Provider";
4136
- import * as Effect48 from "effect/Effect";
4244
+ import * as Effect50 from "effect/Effect";
4137
4245
 
4138
4246
  // src/proxmox/pbs-sync-job-form.ts
4139
4247
  var UNSET4 = -1;
@@ -4181,7 +4289,7 @@ var createBody5 = (props) => ({
4181
4289
  ...props["sync-direction"] === undefined ? {} : { "sync-direction": props["sync-direction"] }
4182
4290
  });
4183
4291
  var updateBody4 = (props) => shape6(props);
4184
- var handlers19 = pveHandlers({
4292
+ var handlers17 = pveHandlers({
4185
4293
  attributes: (live, props) => ({
4186
4294
  comment: text(live["comment"], ""),
4187
4295
  "group-filter": filters(live["group-filter"]),
@@ -4210,11 +4318,11 @@ var handlers19 = pveHandlers({
4210
4318
 
4211
4319
  // src/proxmox/pbs-sync-job.ts
4212
4320
  var PbsSyncJob = Resource23("Pbs.SyncJob");
4213
- var PbsSyncJobProvider = () => Provider23.effect(PbsSyncJob, Effect48.succeed(PbsSyncJob.Provider.of(handlers19)));
4321
+ var PbsSyncJobProvider = () => Provider23.effect(PbsSyncJob, Effect50.succeed(PbsSyncJob.Provider.of(handlers17)));
4214
4322
  // src/proxmox/pbs-verify-job.ts
4215
4323
  import { Resource as Resource24 } from "alchemy";
4216
4324
  import * as Provider24 from "alchemy/Provider";
4217
- import * as Effect49 from "effect/Effect";
4325
+ import * as Effect51 from "effect/Effect";
4218
4326
 
4219
4327
  // src/proxmox/pbs-verify-job-form.ts
4220
4328
  var rechecks = (ignoreVerified, outdatedAfter) => {
@@ -4236,7 +4344,7 @@ var shape7 = (props) => ({
4236
4344
 
4237
4345
  // src/proxmox/pbs-verify-job.ts
4238
4346
  var PbsVerifyJob = Resource24("Pbs.VerifyJob");
4239
- var handlers20 = pveHandlers({
4347
+ var handlers18 = pveHandlers({
4240
4348
  attributes: (live, props) => {
4241
4349
  const ignoreVerified = bool(live["ignore-verified"], true);
4242
4350
  const outdatedAfter = int(live["outdated-after"], -1);
@@ -4259,11 +4367,11 @@ var handlers20 = pveHandlers({
4259
4367
  path: (props) => `config/verify/${props.id}`,
4260
4368
  updateForm: shape7
4261
4369
  });
4262
- var PbsVerifyJobProvider = () => Provider24.effect(PbsVerifyJob, Effect49.succeed(PbsVerifyJob.Provider.of(handlers20)));
4370
+ var PbsVerifyJobProvider = () => Provider24.effect(PbsVerifyJob, Effect51.succeed(PbsVerifyJob.Provider.of(handlers18)));
4263
4371
  // src/proxmox/pool.ts
4264
4372
  import { Resource as Resource25 } from "alchemy";
4265
4373
  import * as Provider25 from "alchemy/Provider";
4266
- import * as Effect50 from "effect/Effect";
4374
+ import * as Effect52 from "effect/Effect";
4267
4375
  var ProxmoxPool = Resource25("Proxmox.Pool");
4268
4376
  var poolCreateForm = (props) => ({
4269
4377
  comment: props.comment ?? "",
@@ -4272,7 +4380,7 @@ var poolCreateForm = (props) => ({
4272
4380
  var poolUpdateForm = (props) => ({
4273
4381
  comment: props.comment ?? ""
4274
4382
  });
4275
- var handlers21 = pveHandlers({
4383
+ var handlers19 = pveHandlers({
4276
4384
  attributes: (live, props) => {
4277
4385
  const row = live;
4278
4386
  return {
@@ -4288,7 +4396,7 @@ var handlers21 = pveHandlers({
4288
4396
  path: (props) => `pools/${props.poolid}`,
4289
4397
  updateForm: poolUpdateForm
4290
4398
  });
4291
- var ProxmoxPoolProvider = () => Provider25.effect(ProxmoxPool, Effect50.succeed(ProxmoxPool.Provider.of(handlers21)));
4399
+ var ProxmoxPoolProvider = () => Provider25.effect(ProxmoxPool, Effect52.succeed(ProxmoxPool.Provider.of(handlers19)));
4292
4400
  // src/proxmox/provision-baseline.ts
4293
4401
  var PROVISION_PRIVILEGES = Object.freeze([
4294
4402
  "Datastore.Allocate",
@@ -4481,44 +4589,105 @@ echo 'provision baseline: done. Next: point the OpenBao proxmox mount at these u
4481
4589
  // src/proxmox/provision-declare.ts
4482
4590
  import { adopt } from "alchemy/AdoptPolicy";
4483
4591
  import * as RemovalPolicy from "alchemy/RemovalPolicy";
4484
- import * as Effect53 from "effect/Effect";
4592
+ import * as Effect56 from "effect/Effect";
4485
4593
 
4486
4594
  // src/proxmox/role.ts
4487
4595
  import { Resource as Resource26 } from "alchemy";
4596
+ import { isResolved as isResolved12 } from "alchemy/Diff";
4488
4597
  import * as Provider26 from "alchemy/Provider";
4489
- import * as Effect51 from "effect/Effect";
4490
- var ProxmoxRole = Resource26("Proxmox.Role");
4598
+ import * as access5 from "@distilled.cloud/proxmox/access";
4599
+ import * as Effect53 from "effect/Effect";
4600
+
4601
+ // src/proxmox/role-wire.ts
4602
+ var ROLE_CREATE = "pve:POST /access/roles";
4603
+ var ROLE_UPDATE = "pve:PUT /access/roles/{roleid}";
4491
4604
  var canonical2 = (privs) => [...new Set(privs.map((priv) => priv.trim()).filter((priv) => priv.length > 0))].sort();
4492
- var livePrivs = (live) => {
4493
- const listed = live["privs"];
4494
- if (typeof listed === "string")
4495
- return canonical2(listed.split(","));
4496
- return canonical2(Object.keys(live).filter((key) => key.includes(".")));
4497
- };
4498
- var handlers22 = pveHandlers({
4499
- attributes: (live, props) => ({ privs: livePrivs(live), roleid: props.roleid }),
4500
- collection: () => "access/roles",
4501
- createForm: (props) => ({ privs: canonical2(props.privs).join(","), roleid: props.roleid }),
4502
- endpoint: { create: "pve:POST /access/roles", update: "pve:PUT /access/roles/{roleid}" },
4503
- matches: (attributes2, props) => canonical2(attributes2.privs).join(",") === canonical2(props.privs).join(","),
4504
- path: (props) => `access/roles/${props.roleid}`,
4505
- updateForm: (props) => ({ privs: canonical2(props.privs).join(",") })
4605
+ var createForm6 = (props) => ({
4606
+ privs: canonical2(props.privs).join(","),
4607
+ roleid: props.roleid
4608
+ });
4609
+ var updateForm3 = (props) => ({
4610
+ privs: canonical2(props.privs).join(","),
4611
+ roleid: props.roleid
4506
4612
  });
4507
- var ProxmoxRoleProvider = () => Provider26.effect(ProxmoxRole, Effect51.succeed(ProxmoxRole.Provider.of(handlers22)));
4613
+ var find2 = (rows, roleid) => rows.find((row) => row.roleid === roleid);
4614
+ var attributesOf5 = (row, props) => ({
4615
+ privs: canonical2(row.privs === undefined ? [] : row.privs.split(",")),
4616
+ roleid: props.roleid
4617
+ });
4618
+ var matches5 = (attributes2, props) => canonical2(attributes2.privs).join(",") === canonical2(props.privs).join(",");
4619
+
4620
+ // src/proxmox/role.ts
4621
+ var ProxmoxRole = Resource26("Proxmox.Role");
4622
+ var readRole = (props) => readOrUnreadable(runPve(props.target, "read", false, access5.listAccessRoles({}))).pipe(Effect53.map((rows) => {
4623
+ if (rows === UNREADABLE)
4624
+ return UNREADABLE;
4625
+ const row = find2(rows, props.roleid);
4626
+ return row === undefined ? undefined : attributesOf5(row, props);
4627
+ }));
4628
+ var dropUnreadable4 = (live) => live === UNREADABLE ? undefined : live;
4629
+ var ProxmoxRoleProvider = () => Provider26.effect(ProxmoxRole, Effect53.succeed(ProxmoxRole.Provider.of({
4630
+ list: () => Effect53.succeed([]),
4631
+ read: Effect53.fn(function* ({ olds }) {
4632
+ return dropUnreadable4(yield* readRole(olds));
4633
+ }),
4634
+ diff: Effect53.fn(function* ({ news, output }) {
4635
+ if (!isResolved12(news))
4636
+ return;
4637
+ yield* guardWrite2(ROLE_CREATE, createForm6(news), output === undefined);
4638
+ yield* guardWrite2(ROLE_UPDATE, updateForm3(news), false);
4639
+ if (output === undefined)
4640
+ return;
4641
+ const live = yield* readRole(news);
4642
+ if (live === UNREADABLE) {
4643
+ yield* unreadableWarning("Proxmox.Role", news.roleid);
4644
+ return { action: "noop" };
4645
+ }
4646
+ if (live === undefined) {
4647
+ yield* guardWrite2(ROLE_CREATE, createForm6(news), true);
4648
+ return { action: "update" };
4649
+ }
4650
+ return matches5(live, news) ? { action: "noop" } : { action: "update" };
4651
+ }),
4652
+ reconcile: Effect53.fn(function* ({ news }) {
4653
+ const before = dropUnreadable4(yield* readRole(news));
4654
+ yield* guardWrite2(ROLE_CREATE, createForm6(news), before === undefined);
4655
+ yield* guardWrite2(ROLE_UPDATE, updateForm3(news), false);
4656
+ if (before === undefined) {
4657
+ yield* runPve(news.target, "provision", true, access5.createAccessRole(createForm6(news)));
4658
+ } else if (!matches5(before, news)) {
4659
+ yield* runPve(news.target, "provision", true, access5.putAccessRole(updateForm3(news)));
4660
+ }
4661
+ const after = dropUnreadable4(yield* readRole(news));
4662
+ if (after === undefined) {
4663
+ return yield* Effect53.die(new Error(`access/roles/${news.roleid}: the write returned no error but the role is ` + 'still absent. PVE wraps every answer in {"data":...} and can report success ' + "on a call that did nothing -- read back rather than trusting the status code."));
4664
+ }
4665
+ return after;
4666
+ }),
4667
+ delete: Effect53.fn(function* ({ olds }) {
4668
+ yield* runPve(olds.target, "provision", true, access5.deleteAccessRole({ roleid: olds.roleid }));
4669
+ })
4670
+ })));
4508
4671
 
4509
4672
  // src/proxmox/user.ts
4510
4673
  import { Resource as Resource27 } from "alchemy";
4674
+ import { isResolved as isResolved13 } from "alchemy/Diff";
4511
4675
  import * as Provider27 from "alchemy/Provider";
4512
- import * as Effect52 from "effect/Effect";
4513
- var ProxmoxUser = Resource27("Proxmox.User");
4514
- var str2 = (value) => typeof value === "string" ? value : "";
4676
+ import * as access7 from "@distilled.cloud/proxmox/access";
4677
+ import * as Effect55 from "effect/Effect";
4678
+
4679
+ // src/proxmox/user-wire.ts
4680
+ import * as access6 from "@distilled.cloud/proxmox/access";
4681
+ import * as Effect54 from "effect/Effect";
4682
+ var USER_CREATE = "pve:POST /access/users";
4683
+ var USER_UPDATE = "pve:PUT /access/users/{userid}";
4515
4684
  var int2 = (value) => {
4516
4685
  const parsed = typeof value === "number" ? value : Number(value ?? 0);
4517
4686
  return Number.isFinite(parsed) ? parsed : 0;
4518
4687
  };
4519
4688
  var groupSet = (value) => {
4520
4689
  const raw = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
4521
- const names2 = raw.map((group) => str2(group).trim()).filter((group) => group !== "");
4690
+ const names2 = raw.map((group) => text(group).trim()).filter((group) => group !== "");
4522
4691
  return [...new Set(names2)].sort();
4523
4692
  };
4524
4693
  var tokenNames = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? Object.keys(value).sort() : [];
@@ -4531,30 +4700,80 @@ var shape8 = (props) => ({
4531
4700
  groups: groupSet(props.groups).join(","),
4532
4701
  lastname: props.lastname ?? ""
4533
4702
  });
4534
- var handlers23 = pveHandlers({
4535
- attributes: (live, props) => ({
4536
- comment: str2(live["comment"]),
4537
- email: str2(live["email"]),
4538
- enable: bool(live["enable"], true),
4539
- expire: int2(live["expire"]),
4540
- firstname: str2(live["firstname"]),
4541
- groups: groupSet(live["groups"]),
4542
- lastname: str2(live["lastname"]),
4543
- tokens: tokenNames(live["tokens"]),
4544
- userid: props.userid
4703
+ var createForm7 = (props) => ({
4704
+ ...shape8(props),
4705
+ userid: props.userid
4706
+ });
4707
+ var updateForm4 = (props) => ({
4708
+ ...shape8(props),
4709
+ userid: props.userid
4710
+ });
4711
+ var attributesOf6 = (live, props) => ({
4712
+ comment: text(live.comment),
4713
+ email: text(live.email),
4714
+ enable: bool(live.enable, true),
4715
+ expire: int2(live.expire),
4716
+ firstname: text(live.firstname),
4717
+ groups: groupSet(live.groups),
4718
+ lastname: text(live.lastname),
4719
+ tokens: tokenNames(live.tokens),
4720
+ userid: props.userid
4721
+ });
4722
+ var matches6 = (attributes2, props) => attributes2.comment === (props.comment ?? "") && attributes2.email === (props.email ?? "") && attributes2.enable === (props.enable !== false) && attributes2.expire === (props.expire ?? 0) && attributes2.firstname === (props.firstname ?? "") && attributes2.lastname === (props.lastname ?? "") && attributes2.groups.join(",") === groupSet(props.groups).join(",");
4723
+ var readUserOrFail = (props) => readOrUnreadable(runPve(props.target, "read", false, access6.getAccessUser({ userid: props.userid }))).pipe(Effect54.map((live) => live === UNREADABLE ? UNREADABLE : attributesOf6(live, props)));
4724
+ var readUser = (props) => readUserOrFail(props).pipe(Effect54.orElseSucceed(() => {
4725
+ return;
4726
+ }));
4727
+ var dropUnreadable5 = (live) => live === UNREADABLE ? undefined : live;
4728
+
4729
+ // src/proxmox/user.ts
4730
+ var ProxmoxUser = Resource27("Proxmox.User");
4731
+ var ProxmoxUserProvider = () => Provider27.effect(ProxmoxUser, Effect55.succeed(ProxmoxUser.Provider.of({
4732
+ list: () => Effect55.succeed([]),
4733
+ read: Effect55.fn(function* ({ olds, output }) {
4734
+ return dropUnreadable5(yield* output === undefined ? readUser(olds) : readUserOrFail(olds));
4545
4735
  }),
4546
- collection: () => "access/users",
4547
- createForm: (props) => ({ ...shape8(props), userid: props.userid }),
4548
- endpoint: { create: "pve:POST /access/users", update: "pve:PUT /access/users/{userid}" },
4549
- matches: (attributes2, props) => attributes2.comment === (props.comment ?? "") && attributes2.email === (props.email ?? "") && attributes2.enable === (props.enable !== false) && attributes2.expire === (props.expire ?? 0) && attributes2.firstname === (props.firstname ?? "") && attributes2.lastname === (props.lastname ?? "") && attributes2.groups.join(",") === groupSet(props.groups).join(","),
4550
- path: (props) => `access/users/${props.userid}`,
4551
- updateForm: shape8
4552
- });
4553
- var ProxmoxUserProvider = () => Provider27.effect(ProxmoxUser, Effect52.succeed(ProxmoxUser.Provider.of(handlers23)));
4736
+ diff: Effect55.fn(function* ({ news, output }) {
4737
+ if (!isResolved13(news))
4738
+ return;
4739
+ yield* guardWrite2(USER_CREATE, createForm7(news), output === undefined);
4740
+ yield* guardWrite2(USER_UPDATE, updateForm4(news), false);
4741
+ if (output === undefined)
4742
+ return;
4743
+ const live = yield* readUserOrFail(news);
4744
+ if (live === UNREADABLE) {
4745
+ yield* unreadableWarning("Proxmox.User", news.userid);
4746
+ return { action: "noop" };
4747
+ }
4748
+ if (live === undefined) {
4749
+ yield* guardWrite2(USER_CREATE, createForm7(news), true);
4750
+ return { action: "update" };
4751
+ }
4752
+ return matches6(live, news) ? { action: "noop" } : { action: "update" };
4753
+ }),
4754
+ reconcile: Effect55.fn(function* ({ news }) {
4755
+ const before = dropUnreadable5(yield* readUser(news));
4756
+ yield* guardWrite2(USER_CREATE, createForm7(news), before === undefined);
4757
+ yield* guardWrite2(USER_UPDATE, updateForm4(news), false);
4758
+ if (before === undefined) {
4759
+ yield* runPve(news.target, "provision", true, access7.createAccessUser(createForm7(news)));
4760
+ } else if (!matches6(before, news)) {
4761
+ yield* runPve(news.target, "provision", true, access7.putAccessUser(updateForm4(news)));
4762
+ }
4763
+ const after = dropUnreadable5(yield* readUser(news));
4764
+ if (after === undefined) {
4765
+ return yield* Effect55.die(new Error(`access/users/${news.userid}: the write returned no error but the account is ` + 'still absent. PVE wraps every answer in {"data":...} and can report success ' + "on a call that did nothing -- read back rather than trusting the status code."));
4766
+ }
4767
+ return after;
4768
+ }),
4769
+ delete: Effect55.fn(function* ({ olds }) {
4770
+ yield* runPve(olds.target, "provision", true, access7.deleteAccessUser({ userid: olds.userid }));
4771
+ })
4772
+ })));
4554
4773
 
4555
4774
  // src/proxmox/provision-declare.ts
4556
- var declareProvisionBaseline = (id, target, names2 = {}, options = {}) => Effect53.gen(function* () {
4557
- const baseline = yield* Effect53.try({
4775
+ var declareProvisionBaseline = (id, target, names2 = {}, options = {}) => Effect56.gen(function* () {
4776
+ const baseline = yield* Effect56.try({
4558
4777
  catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)),
4559
4778
  try: () => provisionBaseline(names2)
4560
4779
  });
@@ -4568,13 +4787,13 @@ var declareProvisionBaseline = (id, target, names2 = {}, options = {}) => Effect
4568
4787
  target
4569
4788
  }));
4570
4789
  const group = yield* owned(ProxmoxGroup(`${id}-mint-group`, { ...baseline.group, target }));
4571
- const users = yield* Effect53.all(baseline.users.map((user) => owned(ProxmoxUser(`${id}-${user.lane}-user`, {
4790
+ const users = yield* Effect56.all(baseline.users.map((user) => owned(ProxmoxUser(`${id}-${user.lane}-user`, {
4572
4791
  comment: user.comment,
4573
4792
  groups: [...user.groups],
4574
4793
  target,
4575
4794
  userid: user.userid
4576
4795
  }))));
4577
- const grants = yield* Effect53.all(baseline.grants.map((grant) => owned(ProxmoxAcl(`${id}-${grant.lane}-grant`, {
4796
+ const grants = yield* Effect56.all(baseline.grants.map((grant) => owned(ProxmoxAcl(`${id}-${grant.lane}-grant`, {
4578
4797
  path: "/",
4579
4798
  propagate: true,
4580
4799
  roleid: grant.roleid,
@@ -4587,9 +4806,9 @@ var declareProvisionBaseline = (id, target, names2 = {}, options = {}) => Effect
4587
4806
  // src/proxmox/qemu.ts
4588
4807
  import { Resource as Resource28 } from "alchemy";
4589
4808
  import * as Provider28 from "alchemy/Provider";
4590
- import * as Effect54 from "effect/Effect";
4809
+ import * as Effect57 from "effect/Effect";
4591
4810
  var ProxmoxVm = Resource28("Proxmox.Vm");
4592
- var createForm5 = (props) => ({ ...shape9(props), vmid: String(props.vmid) });
4811
+ var createForm8 = (props) => ({ ...shape9(props), vmid: String(props.vmid) });
4593
4812
  var shape9 = (props) => ({
4594
4813
  cores: String(props.cores ?? 1),
4595
4814
  memory: String(props.memory ?? 512),
@@ -4598,7 +4817,7 @@ var shape9 = (props) => ({
4598
4817
  sockets: String(props.sockets ?? 1),
4599
4818
  ...props.net0 === undefined ? {} : { net0: props.net0 }
4600
4819
  });
4601
- var handlers24 = pveHandlers({
4820
+ var handlers20 = pveHandlers({
4602
4821
  attributes: (live, props) => ({
4603
4822
  cores: num(live["cores"], 1),
4604
4823
  memory: num(live["memory"], 512),
@@ -4609,7 +4828,7 @@ var handlers24 = pveHandlers({
4609
4828
  vmid: props.vmid
4610
4829
  }),
4611
4830
  collection: (props) => `nodes/${props.node}/qemu`,
4612
- createForm: createForm5,
4831
+ createForm: createForm8,
4613
4832
  endpoint: {
4614
4833
  create: "pve:POST /nodes/{node}/qemu",
4615
4834
  update: "pve:PUT /nodes/{node}/qemu/{vmid}/config"
@@ -4618,11 +4837,11 @@ var handlers24 = pveHandlers({
4618
4837
  path: (props) => `nodes/${props.node}/qemu/${String(props.vmid)}/config`,
4619
4838
  updateForm: shape9
4620
4839
  });
4621
- var ProxmoxVmProvider = () => Provider28.effect(ProxmoxVm, Effect54.succeed(ProxmoxVm.Provider.of(handlers24)));
4840
+ var ProxmoxVmProvider = () => Provider28.effect(ProxmoxVm, Effect57.succeed(ProxmoxVm.Provider.of(handlers20)));
4622
4841
  // src/proxmox/replication-job.ts
4623
4842
  import { Resource as Resource29 } from "alchemy";
4624
4843
  import * as Provider29 from "alchemy/Provider";
4625
- import * as Effect55 from "effect/Effect";
4844
+ import * as Effect58 from "effect/Effect";
4626
4845
 
4627
4846
  // src/proxmox/replication-job-form.ts
4628
4847
  var DEFAULT_SCHEDULE = "*/15";
@@ -4652,7 +4871,7 @@ var rateOf = (value) => {
4652
4871
  const parsed = Number.parseFloat(text(value));
4653
4872
  return Number.isFinite(parsed) ? parsed : 0;
4654
4873
  };
4655
- var handlers25 = pveHandlers({
4874
+ var handlers21 = pveHandlers({
4656
4875
  attributes: (live, props) => ({
4657
4876
  comment: text(live["comment"], ""),
4658
4877
  disable: bool(live["disable"], false),
@@ -4676,15 +4895,15 @@ var handlers25 = pveHandlers({
4676
4895
  path: (props) => `cluster/replication/${jobId(props)}`,
4677
4896
  updateForm: updateBody5
4678
4897
  });
4679
- var ProxmoxReplicationJobProvider = () => Provider29.effect(ProxmoxReplicationJob, Effect55.succeed(ProxmoxReplicationJob.Provider.of(handlers25)));
4898
+ var ProxmoxReplicationJobProvider = () => Provider29.effect(ProxmoxReplicationJob, Effect58.succeed(ProxmoxReplicationJob.Provider.of(handlers21)));
4680
4899
  // src/proxmox/sdn-apply.ts
4681
4900
  import { Resource as Resource30 } from "alchemy";
4682
- import { isResolved as isResolved10 } from "alchemy/Diff";
4901
+ import { isResolved as isResolved14 } from "alchemy/Diff";
4683
4902
  import * as Provider30 from "alchemy/Provider";
4684
- import * as Effect57 from "effect/Effect";
4903
+ import * as Effect60 from "effect/Effect";
4685
4904
 
4686
4905
  // src/proxmox/sdn-apply-read.ts
4687
- import * as Effect56 from "effect/Effect";
4906
+ import * as Effect59 from "effect/Effect";
4688
4907
  var MARKED = [
4689
4908
  "cluster/sdn/zones",
4690
4909
  "cluster/sdn/vnets",
@@ -4693,7 +4912,7 @@ var MARKED = [
4693
4912
  "cluster/sdn/route-maps"
4694
4913
  ];
4695
4914
  var subsystemAbsent = (error) => error instanceof PveError && error.status === 501;
4696
- var markedPending = (target) => Effect56.all(MARKED.map((collection2) => pve(target, "read", "GET", `${collection2}?pending=1`).pipe(Effect56.map((rows) => (rows ?? []).filter((row) => row.state !== undefined).length), Effect56.catchIf(subsystemAbsent, () => Effect56.succeed(0)))), { concurrency: "unbounded" }).pipe(Effect56.map((counts) => counts.reduce((total, count2) => total + count2, 0)));
4915
+ var markedPending = (target) => Effect59.all(MARKED.map((collection2) => pve(target, "read", "GET", `${collection2}?pending=1`).pipe(Effect59.map((rows) => (rows ?? []).filter((row) => row.state !== undefined).length), Effect59.catchIf(subsystemAbsent, () => Effect59.succeed(0)))), { concurrency: "unbounded" }).pipe(Effect59.map((counts) => counts.reduce((total, count2) => total + count2, 0)));
4697
4916
  var fabricStaged = (staged, running) => {
4698
4917
  if (staged === undefined && running === undefined)
4699
4918
  return 0;
@@ -4701,9 +4920,9 @@ var fabricStaged = (staged, running) => {
4701
4920
  return "inconsistent";
4702
4921
  return staged === running ? 0 : 1;
4703
4922
  };
4704
- var fabricPending = (target) => Effect56.all(["pending", "running"].map((view) => pve(target, "read", "GET", `cluster/sdn/fabrics/all?${view}=1`).pipe(Effect56.map(canonical3), Effect56.catchIf(subsystemAbsent, () => Effect56.succeed(undefined)))), { concurrency: "unbounded" }).pipe(Effect56.flatMap(([staged, running]) => {
4923
+ var fabricPending = (target) => Effect59.all(["pending", "running"].map((view) => pve(target, "read", "GET", `cluster/sdn/fabrics/all?${view}=1`).pipe(Effect59.map(canonical3), Effect59.catchIf(subsystemAbsent, () => Effect59.succeed(undefined)))), { concurrency: "unbounded" }).pipe(Effect59.flatMap(([staged, running]) => {
4705
4924
  const verdict = fabricStaged(staged, running);
4706
- return verdict === "inconsistent" ? Effect56.die(new Error("cluster/sdn/fabrics/all answered 501 for one of ?pending=1 and ?running=1 and not " + "the other. Refusing to read that as a staged fabric change.")) : Effect56.succeed(verdict);
4925
+ return verdict === "inconsistent" ? Effect59.die(new Error("cluster/sdn/fabrics/all answered 501 for one of ?pending=1 and ?running=1 and not " + "the other. Refusing to read that as a staged fabric change.")) : Effect59.succeed(verdict);
4707
4926
  }));
4708
4927
  var canonical3 = (value) => JSON.stringify(sortDeep(value));
4709
4928
  var sortDeep = (value) => {
@@ -4716,23 +4935,23 @@ var sortDeep = (value) => {
4716
4935
  }
4717
4936
  return value;
4718
4937
  };
4719
- var sdnPendingCount = (target) => Effect56.all([markedPending(target), fabricPending(target)], { concurrency: "unbounded" }).pipe(Effect56.map(([marked, fabric]) => marked + fabric));
4938
+ var sdnPendingCount = (target) => Effect59.all([markedPending(target), fabricPending(target)], { concurrency: "unbounded" }).pipe(Effect59.map(([marked, fabric]) => marked + fabric));
4720
4939
 
4721
4940
  // src/proxmox/sdn-apply.ts
4722
4941
  var ProxmoxSdnApply = Resource30("Proxmox.SdnApply");
4723
- var read3 = (props) => sdnPendingCount(props.target).pipe(Effect57.map((pending) => ({ pending })));
4724
- var ProxmoxSdnApplyProvider = () => Provider30.effect(ProxmoxSdnApply, Effect57.succeed(ProxmoxSdnApply.Provider.of({
4725
- list: () => Effect57.succeed([]),
4726
- read: Effect57.fn(function* ({ olds }) {
4942
+ var read3 = (props) => sdnPendingCount(props.target).pipe(Effect60.map((pending) => ({ pending })));
4943
+ var ProxmoxSdnApplyProvider = () => Provider30.effect(ProxmoxSdnApply, Effect60.succeed(ProxmoxSdnApply.Provider.of({
4944
+ list: () => Effect60.succeed([]),
4945
+ read: Effect60.fn(function* ({ olds }) {
4727
4946
  return yield* read3(olds);
4728
4947
  }),
4729
- diff: Effect57.fn(function* ({ news }) {
4730
- if (!isResolved10(news))
4948
+ diff: Effect60.fn(function* ({ news }) {
4949
+ if (!isResolved14(news))
4731
4950
  return;
4732
4951
  const { pending } = yield* read3(news);
4733
4952
  return pending === 0 ? { action: "noop" } : { action: "update" };
4734
4953
  }),
4735
- reconcile: Effect57.fn(function* ({ news }) {
4954
+ reconcile: Effect60.fn(function* ({ news }) {
4736
4955
  const staged = yield* read3(news);
4737
4956
  if (staged.pending === 0)
4738
4957
  return staged;
@@ -4740,16 +4959,16 @@ var ProxmoxSdnApplyProvider = () => Provider30.effect(ProxmoxSdnApply, Effect57.
4740
4959
  yield* pve(news.target, "provision", "PUT", "cluster/sdn");
4741
4960
  const after = yield* read3(news);
4742
4961
  if (after.pending !== 0) {
4743
- return yield* Effect57.die(new Error(`PUT /cluster/sdn returned no error but ${String(after.pending)} SDN object(s) ` + "are still staged. The running config does not match the declared one -- check " + "`pvesh get /cluster/sdn/zones --pending 1` for the objects PVE refused."));
4962
+ return yield* Effect60.die(new Error(`PUT /cluster/sdn returned no error but ${String(after.pending)} SDN object(s) ` + "are still staged. The running config does not match the declared one -- check " + "`pvesh get /cluster/sdn/zones --pending 1` for the objects PVE refused."));
4744
4963
  }
4745
4964
  return after;
4746
4965
  }),
4747
- delete: () => Effect57.void
4966
+ delete: () => Effect60.void
4748
4967
  })));
4749
4968
  // src/proxmox/sdn-subnet.ts
4750
4969
  import { Resource as Resource31 } from "alchemy";
4751
4970
  import * as Provider31 from "alchemy/Provider";
4752
- import * as Effect58 from "effect/Effect";
4971
+ import * as Effect61 from "effect/Effect";
4753
4972
 
4754
4973
  // src/proxmox/sdn-subnet-form.ts
4755
4974
  var subnetId = (props) => `${props.zone}-${props.cidr.replace("/", "-")}`;
@@ -4768,19 +4987,19 @@ var body5 = (props) => ({
4768
4987
  vnet: props.vnet,
4769
4988
  ...Object.fromEntries(Object.entries(optional3(props)).filter((entry) => entry[1] !== undefined))
4770
4989
  });
4771
- var createForm6 = (props) => ({
4990
+ var createForm9 = (props) => ({
4772
4991
  ...body5(props),
4773
4992
  subnet: props.cidr,
4774
4993
  type: "subnet"
4775
4994
  });
4776
- var updateForm2 = (props) => {
4995
+ var updateForm5 = (props) => {
4777
4996
  const clear = Object.entries(optional3(props)).filter(([, value]) => value === undefined).map(([key]) => key);
4778
4997
  return withClears(body5(props), clear);
4779
4998
  };
4780
4999
 
4781
5000
  // src/proxmox/sdn-subnet.ts
4782
5001
  var ProxmoxSdnSubnet = Resource31("Proxmox.SdnSubnet");
4783
- var handlers26 = pveHandlers({
5002
+ var handlers22 = pveHandlers({
4784
5003
  attributes: (live, props) => {
4785
5004
  const liveVnet = text(live["vnet"]);
4786
5005
  if (liveVnet !== "" && liveVnet !== props.vnet)
@@ -4800,20 +5019,20 @@ var handlers26 = pveHandlers({
4800
5019
  };
4801
5020
  },
4802
5021
  collection: (props) => `cluster/sdn/vnets/${props.vnet}/subnets`,
4803
- createForm: createForm6,
5022
+ createForm: createForm9,
4804
5023
  endpoint: {
4805
5024
  create: "pve:POST /cluster/sdn/vnets/{vnet}/subnets",
4806
5025
  update: "pve:PUT /cluster/sdn/vnets/{vnet}/subnets/{subnet}"
4807
5026
  },
4808
5027
  matches: (attributes2, props) => attributes2.gateway === (props.gateway ?? "") && attributes2.snat === (props.snat === true) && attributes2.dnszoneprefix === (props.dnszoneprefix ?? "") && attributes2.dhcpDnsServer === (props.dhcpDnsServer ?? "") && attributes2.dhcpRange === declaredRanges(props.dhcpRange),
4809
5028
  path: (props) => `cluster/sdn/vnets/${props.vnet}/subnets/${subnetId(props)}`,
4810
- updateForm: updateForm2
5029
+ updateForm: updateForm5
4811
5030
  });
4812
- var ProxmoxSdnSubnetProvider = () => Provider31.effect(ProxmoxSdnSubnet, Effect58.succeed(ProxmoxSdnSubnet.Provider.of(handlers26)));
5031
+ var ProxmoxSdnSubnetProvider = () => Provider31.effect(ProxmoxSdnSubnet, Effect61.succeed(ProxmoxSdnSubnet.Provider.of(handlers22)));
4813
5032
  // src/proxmox/sdn-vnet.ts
4814
5033
  import { Resource as Resource32 } from "alchemy";
4815
5034
  import * as Provider32 from "alchemy/Provider";
4816
- import * as Effect59 from "effect/Effect";
5035
+ import * as Effect62 from "effect/Effect";
4817
5036
  var ProxmoxSdnVnet = Resource32("Proxmox.SdnVnet");
4818
5037
  var tagOf = (value) => typeof value === "number" ? value : undefined;
4819
5038
  var shape11 = (props) => {
@@ -4826,14 +5045,14 @@ var shape11 = (props) => {
4826
5045
  ...props.tag === undefined ? {} : { tag: String(props.tag) }
4827
5046
  };
4828
5047
  };
4829
- var updateForm3 = (props) => {
5048
+ var updateForm6 = (props) => {
4830
5049
  const unset2 = [
4831
5050
  ...(props.alias ?? "") === "" ? ["alias"] : [],
4832
5051
  ...props.tag === undefined ? ["tag"] : []
4833
5052
  ];
4834
5053
  return withClears(shape11(props), unset2);
4835
5054
  };
4836
- var handlers27 = pveHandlers({
5055
+ var handlers23 = pveHandlers({
4837
5056
  readRole: "provision",
4838
5057
  attributes: (live, props) => ({
4839
5058
  alias: typeof live["alias"] === "string" ? live["alias"] : "",
@@ -4849,13 +5068,13 @@ var handlers27 = pveHandlers({
4849
5068
  endpoint: { create: "pve:POST /cluster/sdn/vnets", update: "pve:PUT /cluster/sdn/vnets/{vnet}" },
4850
5069
  matches: (attributes2, props) => attributes2.zone === props.zone && attributes2.alias === (props.alias ?? "") && attributes2.tag === props.tag && attributes2.vlanaware === (props.vlanaware === true) && attributes2.isolatePorts === (props.isolatePorts === true),
4851
5070
  path: (props) => `cluster/sdn/vnets/${props.vnet}`,
4852
- updateForm: updateForm3
5071
+ updateForm: updateForm6
4853
5072
  });
4854
- var ProxmoxSdnVnetProvider = () => Provider32.effect(ProxmoxSdnVnet, Effect59.succeed(ProxmoxSdnVnet.Provider.of(handlers27)));
5073
+ var ProxmoxSdnVnetProvider = () => Provider32.effect(ProxmoxSdnVnet, Effect62.succeed(ProxmoxSdnVnet.Provider.of(handlers23)));
4855
5074
  // src/proxmox/sdn-zone.ts
4856
5075
  import { Resource as Resource33 } from "alchemy";
4857
5076
  import * as Provider33 from "alchemy/Provider";
4858
- import * as Effect60 from "effect/Effect";
5077
+ import * as Effect63 from "effect/Effect";
4859
5078
  var ProxmoxSdnZone = Resource33("Proxmox.SdnZone");
4860
5079
  var sameIpam = (live, declared2) => (live === "" ? "pve" : live) === (declared2 === undefined || declared2 === "" ? "pve" : declared2);
4861
5080
  var body6 = (props) => ({
@@ -4876,7 +5095,7 @@ var clearable = {
4876
5095
  vlan: ["dns", "dnszone", "ipam", "mtu", "nodes"],
4877
5096
  vxlan: ["dns", "dnszone", "ipam", "mtu", "nodes"]
4878
5097
  };
4879
- var handlers28 = pveHandlers({
5098
+ var handlers24 = pveHandlers({
4880
5099
  readRole: "provision",
4881
5100
  attributes: (live, props) => {
4882
5101
  const liveType = text(live["type"]);
@@ -4908,15 +5127,15 @@ var handlers28 = pveHandlers({
4908
5127
  return withClears(fields, clear);
4909
5128
  }
4910
5129
  });
4911
- var ProxmoxSdnZoneProvider = () => Provider33.effect(ProxmoxSdnZone, Effect60.succeed(ProxmoxSdnZone.Provider.of(handlers28)));
5130
+ var ProxmoxSdnZoneProvider = () => Provider33.effect(ProxmoxSdnZone, Effect63.succeed(ProxmoxSdnZone.Provider.of(handlers24)));
4912
5131
  // src/proxmox/storage.ts
4913
5132
  import { Resource as Resource34 } from "alchemy";
4914
5133
  import * as Provider34 from "alchemy/Provider";
4915
- import * as Effect61 from "effect/Effect";
5134
+ import * as Effect64 from "effect/Effect";
4916
5135
  var ProxmoxStorage = Resource34("Proxmox.Storage", {
4917
5136
  defaultRemovalPolicy: "retain"
4918
5137
  });
4919
- var str3 = (value, fallback = "") => typeof value === "string" ? value : fallback;
5138
+ var str2 = (value, fallback = "") => typeof value === "string" ? value : fallback;
4920
5139
  var field6 = (name, value) => value === undefined ? {} : { [name]: value };
4921
5140
  var locatorForm = (locator) => ({ ...locator });
4922
5141
  var tokens = (value) => value.split(",").map((part) => part.trim()).filter((part) => part !== "").sort().join(",");
@@ -4932,19 +5151,19 @@ var mutable2 = (props) => ({
4932
5151
  ...field6("prune-backups", props["prune-backups"]),
4933
5152
  ...field6("shared", flag(props.shared))
4934
5153
  });
4935
- var handlers29 = pveHandlers({
5154
+ var handlers25 = pveHandlers({
4936
5155
  readRole: "provision",
4937
5156
  attributes: (live, props) => ({
4938
- comment: str3(live["comment"]),
4939
- content: str3(live["content"]),
5157
+ comment: str2(live["comment"]),
5158
+ content: str2(live["content"]),
4940
5159
  disable: bool(live["disable"]),
4941
5160
  maxfiles: typeof live["maxfiles"] === "number" ? live["maxfiles"] : -1,
4942
- nodes: str3(live["nodes"]),
4943
- preallocation: str3(live["preallocation"]),
4944
- "prune-backups": str3(live["prune-backups"]),
5161
+ nodes: str2(live["nodes"]),
5162
+ preallocation: str2(live["preallocation"]),
5163
+ "prune-backups": str2(live["prune-backups"]),
4945
5164
  shared: bool(live["shared"]),
4946
5165
  storage: props.storage,
4947
- type: str3(live["type"], props.type)
5166
+ type: str2(live["type"], props.type)
4948
5167
  }),
4949
5168
  collection: () => "storage",
4950
5169
  createForm: (props) => ({
@@ -4958,17 +5177,17 @@ var handlers29 = pveHandlers({
4958
5177
  path: (props) => `storage/${props.storage}`,
4959
5178
  updateForm: mutable2
4960
5179
  });
4961
- var ProxmoxStorageProvider = () => Provider34.effect(ProxmoxStorage, Effect61.succeed(ProxmoxStorage.Provider.of(handlers29)));
5180
+ var ProxmoxStorageProvider = () => Provider34.effect(ProxmoxStorage, Effect64.succeed(ProxmoxStorage.Provider.of(handlers25)));
4962
5181
  // src/proxmox/zfs-pool.ts
4963
5182
  import { Resource as Resource35 } from "alchemy";
4964
5183
  import * as Provider35 from "alchemy/Provider";
4965
- import * as Effect63 from "effect/Effect";
5184
+ import * as Effect66 from "effect/Effect";
4966
5185
 
4967
5186
  // src/proxmox/zfs-pool-write.ts
4968
- import * as Effect62 from "effect/Effect";
5187
+ import * as Effect65 from "effect/Effect";
4969
5188
  var ZFS_POOL_ENDPOINT = { create: "pve:POST /nodes/{node}/disks/zfs" };
4970
5189
  var zfsPoolEndpoint = (props) => props.devices === undefined || props.raidlevel === undefined ? {} : ZFS_POOL_ENDPOINT;
4971
- var createForm7 = (props) => ({
5190
+ var createForm10 = (props) => ({
4972
5191
  ...props.devices === undefined ? {} : { devices: props.devices.join(",") },
4973
5192
  name: props.name,
4974
5193
  ...props.raidlevel === undefined ? {} : { raidlevel: props.raidlevel },
@@ -4977,24 +5196,24 @@ var createForm7 = (props) => ({
4977
5196
  ...props["draid-config"] === undefined ? {} : { "draid-config": props["draid-config"] }
4978
5197
  });
4979
5198
  var SETTLE_ATTEMPTS = 30;
4980
- var createPool = (props, collection2, path2, read4) => Effect62.gen(function* () {
5199
+ var createPool = (props, collection2, path2, read4) => Effect65.gen(function* () {
4981
5200
  const existing = yield* read4(props);
4982
5201
  if (existing !== undefined)
4983
5202
  return existing;
4984
5203
  if (props.devices === undefined || props.raidlevel === undefined) {
4985
- return yield* Effect62.die(new Error(`${props.node}/${props.name}: this is an adopt-only declaration (no \`devices\` or ` + "`raidlevel`) and the pool is not there to adopt. An adopt-only Proxmox.ZfsPool never " + "creates -- declare both fields for a pool this resource should build, or point this " + "declaration at a pool that already exists on this node."));
5204
+ return yield* Effect65.die(new Error(`${props.node}/${props.name}: this is an adopt-only declaration (no \`devices\` or ` + "`raidlevel`) and the pool is not there to adopt. An adopt-only Proxmox.ZfsPool never " + "creates -- declare both fields for a pool this resource should build, or point this " + "declaration at a pool that already exists on this node."));
4986
5205
  }
4987
- const upid = yield* pve(props.target, "provision", "POST", collection2, createForm7(props));
4988
- const settle4 = (attempts) => Effect62.gen(function* () {
5206
+ const upid = yield* pve(props.target, "provision", "POST", collection2, createForm10(props));
5207
+ const settle4 = (attempts) => Effect65.gen(function* () {
4989
5208
  const live = yield* read4(props);
4990
5209
  if (live !== undefined || attempts <= 0)
4991
5210
  return live;
4992
- yield* Effect62.sleep("2 seconds");
5211
+ yield* Effect65.sleep("2 seconds");
4993
5212
  return yield* settle4(attempts - 1);
4994
5213
  });
4995
5214
  const after = yield* settle4(SETTLE_ATTEMPTS);
4996
5215
  if (after === undefined) {
4997
- return yield* Effect62.die(new Error(`${path2}: POST returned ${text(upid, "(no UPID)")} but the pool has not appeared. That ` + "POST only forks a `zfscreate` worker, so the task holds the real error -- read it " + `with \`pvesh get /nodes/${props.node}/tasks/<upid>/log\`. NOTHING WAS RETRIED AND ` + "NOTHING WAS CLEANED UP: if the worker reached `zpool create`, the declared devices " + "have already been written to."));
5216
+ return yield* Effect65.die(new Error(`${path2}: POST returned ${text(upid, "(no UPID)")} but the pool has not appeared. That ` + "POST only forks a `zfscreate` worker, so the task holds the real error -- read it " + `with \`pvesh get /nodes/${props.node}/tasks/<upid>/log\`. NOTHING WAS RETRIED AND ` + "NOTHING WAS CLEANED UP: if the worker reached `zpool create`, the declared devices " + "have already been written to."));
4998
5217
  }
4999
5218
  return after;
5000
5219
  });
@@ -5019,7 +5238,7 @@ var spec2 = {
5019
5238
  };
5020
5239
  },
5021
5240
  collection: (props) => `nodes/${props.node}/disks/zfs`,
5022
- createForm: createForm7,
5241
+ createForm: createForm10,
5023
5242
  endpoint: zfsPoolEndpoint,
5024
5243
  matches: () => true,
5025
5244
  path: (props) => `nodes/${props.node}/disks/zfs/${props.name}`
@@ -5029,15 +5248,15 @@ var spec2 = {
5029
5248
  var ProxmoxZfsPool = Resource35("Proxmox.ZfsPool", {
5030
5249
  defaultRemovalPolicy: "retain"
5031
5250
  });
5032
- var ops4 = pveOperations(spec2);
5033
- var handlers30 = {
5251
+ var ops3 = pveOperations(spec2);
5252
+ var handlers26 = {
5034
5253
  ...pveHandlers(spec2),
5035
- delete: Effect63.fn(function* ({ olds }) {
5254
+ delete: Effect66.fn(function* ({ olds }) {
5036
5255
  yield* destroyPool(olds, spec2.path(olds));
5037
5256
  }),
5038
- reconcile: ({ news }) => createPool(news, spec2.collection(news), spec2.path(news), ops4.read)
5257
+ reconcile: ({ news }) => createPool(news, spec2.collection(news), spec2.path(news), ops3.read)
5039
5258
  };
5040
- var ProxmoxZfsPoolProvider = () => Provider35.effect(ProxmoxZfsPool, Effect63.succeed(ProxmoxZfsPool.Provider.of(handlers30)));
5259
+ var ProxmoxZfsPoolProvider = () => Provider35.effect(ProxmoxZfsPool, Effect66.succeed(ProxmoxZfsPool.Provider.of(handlers26)));
5041
5260
  export {
5042
5261
  PROVISION_DEFAULTS,
5043
5262
  PROVISION_PRIVILEGES,
@@ -5114,5 +5333,5 @@ export {
5114
5333
  violations
5115
5334
  };
5116
5335
 
5117
- //# debugId=FBBA36EF988E48B764756E2164756E21
5336
+ //# debugId=E83D3C3771546F7064756E2164756E21
5118
5337
  //# sourceMappingURL=index.js.map