@homeflare/alchemy 0.30.0 → 0.31.1

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