@homeflare/alchemy 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/README.md +22 -11
  2. package/dist/caddy/config-lifecycle.d.ts +1 -1
  3. package/dist/caddy/config.d.ts.map +1 -1
  4. package/dist/caddy/index.js +221 -53
  5. package/dist/caddy/index.js.map +13 -9
  6. package/dist/launchd/fake-runner.d.ts +1 -0
  7. package/dist/launchd/fake-runner.d.ts.map +1 -1
  8. package/dist/launchd/fake-sudo.d.ts +1 -0
  9. package/dist/launchd/fake-sudo.d.ts.map +1 -1
  10. package/dist/launchd/host-effect.d.ts +9 -18
  11. package/dist/launchd/host-effect.d.ts.map +1 -1
  12. package/dist/launchd/host-file-lifecycle.d.ts +14 -1
  13. package/dist/launchd/host-file-lifecycle.d.ts.map +1 -1
  14. package/dist/launchd/host-file.d.ts +2 -1
  15. package/dist/launchd/host-file.d.ts.map +1 -1
  16. package/dist/launchd/index.js +345 -86
  17. package/dist/launchd/index.js.map +18 -12
  18. package/dist/launchd/job-lifecycle.d.ts +8 -1
  19. package/dist/launchd/job-lifecycle.d.ts.map +1 -1
  20. package/dist/launchd/job-preflight.d.ts +13 -1
  21. package/dist/launchd/job-preflight.d.ts.map +1 -1
  22. package/dist/launchd/job.d.ts +2 -1
  23. package/dist/launchd/job.d.ts.map +1 -1
  24. package/dist/launchd/runner.d.ts +7 -0
  25. package/dist/launchd/runner.d.ts.map +1 -1
  26. package/dist/launchd/sudo-acl.d.ts +25 -0
  27. package/dist/launchd/sudo-acl.d.ts.map +1 -0
  28. package/dist/launchd/sudo-guard.d.ts +31 -4
  29. package/dist/launchd/sudo-guard.d.ts.map +1 -1
  30. package/dist/launchd/sudo-runner.d.ts +3 -3
  31. package/dist/launchd/sudo-runner.d.ts.map +1 -1
  32. package/dist/launchd/sudo-said.d.ts +10 -0
  33. package/dist/launchd/sudo-said.d.ts.map +1 -0
  34. package/dist/openbao/auth-method-reconcile.d.ts +6 -2
  35. package/dist/openbao/auth-method-reconcile.d.ts.map +1 -1
  36. package/dist/openbao/auth-method.d.ts.map +1 -1
  37. package/dist/openbao/auth-role.d.ts.map +1 -1
  38. package/dist/openbao/cloudflare-role.d.ts.map +1 -1
  39. package/dist/openbao/fake-bao.d.ts +18 -0
  40. package/dist/openbao/fake-bao.d.ts.map +1 -1
  41. package/dist/openbao/fake-core.d.ts +39 -0
  42. package/dist/openbao/fake-core.d.ts.map +1 -0
  43. package/dist/openbao/fake-families.d.ts +6 -3
  44. package/dist/openbao/fake-families.d.ts.map +1 -1
  45. package/dist/openbao/fake-mounts.d.ts +16 -0
  46. package/dist/openbao/fake-mounts.d.ts.map +1 -0
  47. package/dist/openbao/fake-stack.d.ts +5 -1
  48. package/dist/openbao/fake-stack.d.ts.map +1 -1
  49. package/dist/openbao/index.js +541 -270
  50. package/dist/openbao/index.js.map +26 -21
  51. package/dist/openbao/jwt-config.d.ts.map +1 -1
  52. package/dist/openbao/jwt-role.d.ts.map +1 -1
  53. package/dist/openbao/kubernetes-role.d.ts.map +1 -1
  54. package/dist/openbao/mfa-enforcement.d.ts +6 -2
  55. package/dist/openbao/mfa-enforcement.d.ts.map +1 -1
  56. package/dist/openbao/mfa-totp.d.ts +6 -2
  57. package/dist/openbao/mfa-totp.d.ts.map +1 -1
  58. package/dist/openbao/mount-reconcile.d.ts +5 -1
  59. package/dist/openbao/mount-reconcile.d.ts.map +1 -1
  60. package/dist/openbao/mount.d.ts.map +1 -1
  61. package/dist/openbao/pki-role.d.ts.map +1 -1
  62. package/dist/openbao/plugin-reconcile.d.ts +6 -2
  63. package/dist/openbao/plugin-reconcile.d.ts.map +1 -1
  64. package/dist/openbao/plugin.d.ts.map +1 -1
  65. package/dist/openbao/policy.d.ts.map +1 -1
  66. package/dist/openbao/proxmox-role.d.ts.map +1 -1
  67. package/dist/openbao/role-reconcile.d.ts +11 -1
  68. package/dist/openbao/role-reconcile.d.ts.map +1 -1
  69. package/dist/openbao/ssh-role.d.ts.map +1 -1
  70. package/dist/ownership/adopt.d.ts +52 -0
  71. package/dist/ownership/adopt.d.ts.map +1 -0
  72. package/dist/ownership/probe.d.ts +13 -0
  73. package/dist/ownership/probe.d.ts.map +1 -0
  74. package/dist/ownership/resume.d.ts +40 -0
  75. package/dist/ownership/resume.d.ts.map +1 -0
  76. package/dist/ownership/rows.d.ts +33 -0
  77. package/dist/ownership/rows.d.ts.map +1 -0
  78. package/dist/ownership/whole.d.ts +14 -0
  79. package/dist/ownership/whole.d.ts.map +1 -0
  80. package/dist/proxmox/ceph-pool-form.d.ts +3 -3
  81. package/dist/proxmox/ceph-pool.d.ts.map +1 -1
  82. package/dist/proxmox/fake-bao-env.d.ts +3 -0
  83. package/dist/proxmox/fake-bao-env.d.ts.map +1 -0
  84. package/dist/proxmox/fake-pve-lxc.d.ts +42 -0
  85. package/dist/proxmox/fake-pve-lxc.d.ts.map +1 -0
  86. package/dist/proxmox/fake-pve.d.ts +38 -0
  87. package/dist/proxmox/fake-pve.d.ts.map +1 -0
  88. package/dist/proxmox/index.d.ts +6 -2
  89. package/dist/proxmox/index.d.ts.map +1 -1
  90. package/dist/proxmox/index.js +970 -186
  91. package/dist/proxmox/index.js.map +22 -9
  92. package/dist/proxmox/lxc-create-form.d.ts +6 -0
  93. package/dist/proxmox/lxc-create-form.d.ts.map +1 -0
  94. package/dist/proxmox/lxc-harness.d.ts +34 -0
  95. package/dist/proxmox/lxc-harness.d.ts.map +1 -0
  96. package/dist/proxmox/lxc-identity.d.ts +18 -0
  97. package/dist/proxmox/lxc-identity.d.ts.map +1 -0
  98. package/dist/proxmox/lxc-judge.d.ts +46 -0
  99. package/dist/proxmox/lxc-judge.d.ts.map +1 -0
  100. package/dist/proxmox/lxc-lifecycle.d.ts +68 -0
  101. package/dist/proxmox/lxc-lifecycle.d.ts.map +1 -0
  102. package/dist/proxmox/lxc-props.d.ts +139 -0
  103. package/dist/proxmox/lxc-props.d.ts.map +1 -0
  104. package/dist/proxmox/lxc-volume.d.ts +29 -0
  105. package/dist/proxmox/lxc-volume.d.ts.map +1 -0
  106. package/dist/proxmox/lxc-wire.d.ts +51 -0
  107. package/dist/proxmox/lxc-wire.d.ts.map +1 -0
  108. package/dist/proxmox/lxc.d.ts +27 -47
  109. package/dist/proxmox/lxc.d.ts.map +1 -1
  110. package/dist/proxmox/network-apply-read.d.ts +1 -1
  111. package/dist/proxmox/network-apply-read.d.ts.map +1 -1
  112. package/dist/proxmox/pbs-datastore.d.ts.map +1 -1
  113. package/dist/proxmox/resource.d.ts.map +1 -1
  114. package/dist/proxmox/update-guard.d.ts +27 -0
  115. package/dist/proxmox/update-guard.d.ts.map +1 -0
  116. package/dist/verify/args.d.ts +16 -0
  117. package/dist/verify/args.d.ts.map +1 -0
  118. package/dist/verify/cli.d.ts +3 -0
  119. package/dist/verify/cli.d.ts.map +1 -0
  120. package/dist/verify/cli.js +320 -0
  121. package/dist/verify/cli.js.map +15 -0
  122. package/dist/verify/fake-engine.d.ts +57 -0
  123. package/dist/verify/fake-engine.d.ts.map +1 -0
  124. package/dist/verify/index.d.ts +16 -0
  125. package/dist/verify/index.d.ts.map +1 -0
  126. package/dist/verify/index.js +297 -0
  127. package/dist/verify/index.js.map +14 -0
  128. package/dist/verify/report.d.ts +6 -0
  129. package/dist/verify/report.d.ts.map +1 -0
  130. package/dist/verify/rows.d.ts +57 -0
  131. package/dist/verify/rows.d.ts.map +1 -0
  132. package/dist/verify/spy.d.ts +31 -0
  133. package/dist/verify/spy.d.ts.map +1 -0
  134. package/dist/verify/verify.d.ts +43 -0
  135. package/dist/verify/verify.d.ts.map +1 -0
  136. package/docs/adopt-verify.md +108 -0
  137. package/docs/adopted-deploys.md +85 -0
  138. package/docs/credentials.md +15 -0
  139. package/docs/launchd-sudo.md +31 -6
  140. package/docs/launchd.md +7 -3
  141. package/docs/ownership.md +113 -0
  142. package/docs/proxmox-lxc.md +196 -0
  143. package/docs/proxmox.md +12 -0
  144. package/package.json +9 -2
  145. package/src/caddy/adopt-scope.test.ts +1 -1
  146. package/src/caddy/config-lifecycle.ts +1 -1
  147. package/src/caddy/config.ts +2 -1
  148. package/src/launchd/adopt-apply.test.ts +166 -0
  149. package/src/launchd/fake-runner.ts +26 -4
  150. package/src/launchd/host-effect.ts +1 -33
  151. package/src/launchd/host-file-lifecycle.ts +39 -14
  152. package/src/launchd/host-file.ts +13 -4
  153. package/src/launchd/job-lifecycle.ts +22 -14
  154. package/src/launchd/job-preflight.ts +18 -1
  155. package/src/launchd/job.ts +13 -4
  156. package/src/launchd/runner.ts +7 -0
  157. package/src/launchd/sudo-acl.ts +68 -0
  158. package/src/launchd/sudo-chain.test.ts +121 -0
  159. package/src/launchd/sudo-guard.ts +89 -10
  160. package/src/launchd/sudo-modes.test.ts +135 -0
  161. package/src/launchd/sudo-runner.ts +40 -59
  162. package/src/launchd/sudo-said.ts +58 -0
  163. package/src/openbao/README.md +5 -2
  164. package/src/openbao/REPLACE.md +13 -11
  165. package/src/openbao/adopt-core.test.ts +119 -0
  166. package/src/openbao/adopt-families.test.ts +97 -0
  167. package/src/openbao/adopt-holes.test.ts +168 -0
  168. package/src/openbao/adopt-recovery.test.ts +71 -0
  169. package/src/openbao/auth-method-reconcile.ts +15 -3
  170. package/src/openbao/auth-method.ts +15 -6
  171. package/src/openbao/auth-role.ts +14 -4
  172. package/src/openbao/cloudflare-role.ts +17 -4
  173. package/src/openbao/fake-bao.ts +52 -0
  174. package/src/openbao/fake-core.ts +141 -0
  175. package/src/openbao/fake-families.ts +11 -5
  176. package/src/openbao/fake-mounts.ts +74 -0
  177. package/src/openbao/fake-stack.ts +17 -4
  178. package/src/openbao/jwt-config.ts +10 -7
  179. package/src/openbao/jwt-role.ts +10 -7
  180. package/src/openbao/kubernetes-role.ts +13 -7
  181. package/src/openbao/mfa-enforcement.ts +20 -6
  182. package/src/openbao/mfa-totp.ts +22 -7
  183. package/src/openbao/mount-move.test.ts +2 -35
  184. package/src/openbao/mount-reconcile.ts +13 -2
  185. package/src/openbao/mount.ts +15 -6
  186. package/src/openbao/pki-role.ts +15 -4
  187. package/src/openbao/plugin-reconcile.ts +16 -5
  188. package/src/openbao/plugin.ts +17 -6
  189. package/src/openbao/policy.ts +17 -4
  190. package/src/openbao/proxmox-role.ts +13 -4
  191. package/src/openbao/rename-adoption.test.ts +32 -19
  192. package/src/openbao/role-reconcile.ts +23 -0
  193. package/src/openbao/ssh-role.ts +14 -4
  194. package/src/ownership/adopt.ts +122 -0
  195. package/src/ownership/ownership.test.ts +200 -0
  196. package/src/ownership/probe.ts +65 -0
  197. package/src/ownership/resume.ts +118 -0
  198. package/src/ownership/rows.ts +117 -0
  199. package/src/ownership/whole.test.ts +39 -0
  200. package/src/ownership/whole.ts +60 -0
  201. package/src/proxmox/adopt-noop.test.ts +112 -0
  202. package/src/proxmox/ceph-pool-adopt.test.ts +88 -0
  203. package/src/proxmox/ceph-pool-form.ts +3 -3
  204. package/src/proxmox/ceph-pool.ts +10 -3
  205. package/src/proxmox/fake-bao-env.ts +28 -0
  206. package/src/proxmox/fake-pve-lxc.ts +210 -0
  207. package/src/proxmox/fake-pve.ts +75 -0
  208. package/src/proxmox/index.test.ts +6 -6
  209. package/src/proxmox/index.ts +6 -2
  210. package/src/proxmox/lxc-adopt.test.ts +170 -0
  211. package/src/proxmox/lxc-create-form.ts +76 -0
  212. package/src/proxmox/lxc-create.test.ts +126 -0
  213. package/src/proxmox/lxc-guards.test.ts +158 -0
  214. package/src/proxmox/lxc-harness.ts +150 -0
  215. package/src/proxmox/lxc-identity.ts +43 -0
  216. package/src/proxmox/lxc-judge.test.ts +175 -0
  217. package/src/proxmox/lxc-judge.ts +196 -0
  218. package/src/proxmox/lxc-lifecycle.ts +200 -0
  219. package/src/proxmox/lxc-ownership.test.ts +159 -0
  220. package/src/proxmox/lxc-props.ts +215 -0
  221. package/src/proxmox/lxc-read.test.ts +60 -0
  222. package/src/proxmox/lxc-volume.test.ts +96 -0
  223. package/src/proxmox/lxc-volume.ts +153 -0
  224. package/src/proxmox/lxc-wire.ts +210 -0
  225. package/src/proxmox/lxc.ts +216 -93
  226. package/src/proxmox/network-apply-read.ts +3 -1
  227. package/src/proxmox/pbs-datastore.ts +4 -3
  228. package/src/proxmox/resource.ts +7 -7
  229. package/src/proxmox/update-guard.ts +31 -0
  230. package/src/verify/args.ts +82 -0
  231. package/src/verify/cli.test.ts +86 -0
  232. package/src/verify/cli.ts +49 -0
  233. package/src/verify/fake-engine.ts +158 -0
  234. package/src/verify/index.ts +36 -0
  235. package/src/verify/report.ts +52 -0
  236. package/src/verify/rows.ts +169 -0
  237. package/src/verify/spy.test.ts +141 -0
  238. package/src/verify/spy.ts +149 -0
  239. package/src/verify/verify.test.ts +124 -0
  240. package/src/verify/verify.ts +172 -0
@@ -224,6 +224,11 @@ var pve = (target, role, method, path, form) => Effect4.gen(function* () {
224
224
  // src/proxmox/resource.ts
225
225
  import { isResolved } from "alchemy/Diff";
226
226
  import * as Effect5 from "effect/Effect";
227
+
228
+ // src/proxmox/update-guard.ts
229
+ var formToSend = (matches, live, props, form) => matches(live, props) || Object.keys(form).length === 0 ? undefined : form;
230
+
231
+ // src/proxmox/resource.ts
227
232
  var pveOperations = (spec) => {
228
233
  const read = (props) => pve(props.target, spec.readRole ?? "read", "GET", spec.path(props)).pipe(Effect5.map((data) => data === undefined ? undefined : spec.attributes(data, props)), Effect5.orElseSucceed(() => {
229
234
  return;
@@ -244,9 +249,9 @@ var pveOperations = (spec) => {
244
249
  const live = yield* read(news);
245
250
  if (live === undefined) {
246
251
  yield* pve(news.target, "provision", "POST", spec.collection(news), spec.createForm(news));
247
- } else if (spec.updateForm !== undefined && !spec.matches(live, news)) {
248
- const form = spec.updateForm(news);
249
- if (Object.keys(form).length > 0) {
252
+ } else if (spec.updateForm !== undefined) {
253
+ const form = formToSend(spec.matches, live, news, spec.updateForm(news));
254
+ if (form !== undefined) {
250
255
  yield* pve(news.target, "provision", "PUT", spec.path(news), form);
251
256
  }
252
257
  }
@@ -835,8 +840,8 @@ var ProxmoxCephPoolProvider = () => Provider8.effect(ProxmoxCephPool, Effect17.s
835
840
  yield* confirmAbsent(news, spec.collection(news), object(news));
836
841
  upid = yield* pve(news.target, "provision", "POST", collection(news), createBody(news));
837
842
  } else {
838
- const form = updateBody(news);
839
- if (Object.keys(form).length > 0) {
843
+ const form = formToSend(spec.matches, live, news, updateBody(news));
844
+ if (form !== undefined) {
840
845
  upid = yield* pve(news.target, "provision", "PUT", object(news), form);
841
846
  }
842
847
  }
@@ -1041,45 +1046,868 @@ var handlers9 = pveHandlers({
1041
1046
  var ProxmoxHaRuleProvider = () => Provider12.effect(ProxmoxHaRule, Effect21.succeed(ProxmoxHaRule.Provider.of(handlers9)));
1042
1047
  // src/proxmox/lxc.ts
1043
1048
  import { Resource as Resource13 } from "alchemy";
1049
+ import { isResolved as isResolved6 } from "alchemy/Diff";
1044
1050
  import * as Provider13 from "alchemy/Provider";
1051
+ import * as Effect29 from "effect/Effect";
1052
+
1053
+ // src/ownership/adopt.ts
1054
+ import { AdoptPolicy } from "alchemy/AdoptPolicy";
1055
+ import { AlchemyContext } from "alchemy/AlchemyContext";
1056
+ import { Stack as Stack3 } from "alchemy/Stack";
1057
+ import * as Effect25 from "effect/Effect";
1058
+ import * as Option5 from "effect/Option";
1059
+
1060
+ // src/ownership/resume.ts
1061
+ import { Unowned } from "alchemy/AdoptPolicy";
1062
+ import { Artifacts } from "alchemy/Artifacts";
1045
1063
  import * as Effect22 from "effect/Effect";
1046
- var ProxmoxLxc = Resource13("Proxmox.Lxc");
1047
- var handlers10 = pveHandlers({
1048
- attributes: (live, props) => ({
1049
- cores: num(live["cores"], 1),
1050
- hostname: typeof live["hostname"] === "string" ? live["hostname"] : "",
1051
- memory: num(live["memory"], 512),
1052
- node: props.node,
1053
- onboot: live["onboot"] === 1 || live["onboot"] === true,
1054
- status: typeof live["status"] === "string" ? live["status"] : "unknown",
1055
- vmid: props.vmid
1056
- }),
1057
- collection: (props) => `nodes/${props.node}/lxc`,
1058
- createForm: (props) => ({
1059
- cores: String(props.cores ?? 1),
1060
- hostname: props.hostname ?? `ct${String(props.vmid)}`,
1061
- memory: String(props.memory ?? 512),
1062
- onboot: props.onboot === true ? "1" : "0",
1063
- ostemplate: props.ostemplate,
1064
- storage: props.storage,
1065
- vmid: String(props.vmid),
1066
- ...props.net0 === undefined ? {} : { net0: props.net0 }
1067
- }),
1068
- matches: (attributes, props) => attributes.hostname === (props.hostname ?? `ct${String(props.vmid)}`) && attributes.memory === (props.memory ?? 512) && attributes.cores === (props.cores ?? 1) && attributes.onboot === (props.onboot === true),
1069
- path: (props) => `nodes/${props.node}/lxc/${String(props.vmid)}/config`,
1070
- updateForm: (props) => ({
1071
- cores: String(props.cores ?? 1),
1072
- hostname: props.hostname ?? `ct${String(props.vmid)}`,
1073
- memory: String(props.memory ?? 512),
1074
- onboot: props.onboot === true ? "1" : "0",
1075
- ...props.net0 === undefined ? {} : { net0: props.net0 }
1076
- })
1064
+ import * as Option2 from "effect/Option";
1065
+ var PROVEN = "homeflare/ownership/resumes";
1066
+ var UNFINISHED = "homeflare/ownership/unfinished";
1067
+ var note = (key, instanceId) => Effect22.gen(function* () {
1068
+ const bag = yield* Effect22.serviceOption(Artifacts);
1069
+ if (Option2.isSome(bag))
1070
+ yield* bag.value.set(key, instanceId);
1071
+ });
1072
+ var noted = (key, instanceId) => Effect22.gen(function* () {
1073
+ const bag = yield* Effect22.serviceOption(Artifacts);
1074
+ if (Option2.isNone(bag))
1075
+ return false;
1076
+ return (yield* bag.value.get(key)) === instanceId;
1077
+ });
1078
+ var noteUnfinished = (instanceId) => Effect22.as(note(UNFINISHED, instanceId), undefined);
1079
+ var noteResume = (instanceId, recovered) => Effect22.gen(function* () {
1080
+ yield* noteUnfinished(instanceId);
1081
+ const unproven = () => Effect22.succeed(undefined);
1082
+ const found = yield* recovered.pipe(Effect22.catch(unproven), Effect22.catchDefect(unproven));
1083
+ if (found === undefined || found === null || Unowned.is(found))
1084
+ return;
1085
+ yield* note(PROVEN, instanceId);
1086
+ });
1087
+ var resumes = (instanceId) => noted(PROVEN, instanceId);
1088
+ var unfinished = (instanceId) => noted(UNFINISHED, instanceId);
1089
+ var provingResumes = (service) => {
1090
+ const { diff, read } = service;
1091
+ if (diff === undefined || read === undefined)
1092
+ return service;
1093
+ const proving = (input) => Effect22.gen(function* () {
1094
+ if (input.output === undefined) {
1095
+ yield* noteResume(input.instanceId, read({ ...input, output: undefined }));
1096
+ }
1097
+ return yield* diff(input);
1098
+ });
1099
+ return { ...service, diff: proving };
1100
+ };
1101
+
1102
+ // src/ownership/rows.ts
1103
+ import { Stack as Stack2 } from "alchemy/Stack";
1104
+ import { State, isActionState } from "alchemy/State/State";
1105
+ import * as Effect24 from "effect/Effect";
1106
+ import * as Option4 from "effect/Option";
1107
+
1108
+ // src/ownership/whole.ts
1109
+ import { Stack } from "alchemy/Stack";
1110
+ import * as Effect23 from "effect/Effect";
1111
+ import * as Option3 from "effect/Option";
1112
+ var absent = (value) => value === undefined || value === null;
1113
+ var isPlainObject = (value) => {
1114
+ if (typeof value !== "object" || value === null || Array.isArray(value))
1115
+ return false;
1116
+ const proto = Object.getPrototypeOf(value);
1117
+ return proto === Object.prototype || proto === null;
1118
+ };
1119
+ var carries = (declared, recorded) => {
1120
+ if (absent(declared))
1121
+ return true;
1122
+ if (absent(recorded))
1123
+ return false;
1124
+ if (Array.isArray(declared)) {
1125
+ if (!Array.isArray(recorded))
1126
+ return false;
1127
+ return declared.every((each, index) => carries(each, recorded[index]));
1128
+ }
1129
+ if (isPlainObject(declared)) {
1130
+ if (!isPlainObject(recorded))
1131
+ return false;
1132
+ return Object.entries(declared).every(([key, each]) => carries(each, recorded[key]));
1133
+ }
1134
+ return true;
1135
+ };
1136
+ var wholeDeclaration = (fqn, props) => Effect23.map(Effect23.serviceOption(Stack), (stack) => {
1137
+ const declared = Option3.isSome(stack) ? stack.value.resources[fqn] : undefined;
1138
+ return declared !== undefined && carries(declared.Props, props);
1139
+ });
1140
+
1141
+ // src/ownership/rows.ts
1142
+ var storeOf = Effect24.gen(function* () {
1143
+ const stack = yield* Effect24.serviceOption(Stack2);
1144
+ const state = yield* Effect24.serviceOption(State);
1145
+ if (Option4.isNone(stack) || Option4.isNone(state))
1146
+ return;
1147
+ const store = yield* state.value;
1148
+ const where = { stack: stack.value.name, stage: stack.value.stage };
1149
+ return { resources: stack.value.resources, store, where };
1150
+ });
1151
+ var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect24.orElseSucceed(() => {
1152
+ return;
1153
+ }));
1154
+ var generationOf = (row, instanceId) => {
1155
+ let at = row;
1156
+ while (typeof at === "object" && at !== null && !isActionState(at)) {
1157
+ const generation = at;
1158
+ if (generation.instanceId === instanceId)
1159
+ return generation;
1160
+ at = generation.old;
1161
+ }
1162
+ return;
1163
+ };
1164
+ var recordedGeneration = (fqn, instanceId) => Effect24.gen(function* () {
1165
+ const found = yield* storeOf;
1166
+ if (found === undefined)
1167
+ return;
1168
+ for (const at of [fqn, ...found.resources[fqn]?.FormerFqns ?? []]) {
1169
+ const generation = generationOf(yield* rowAt(found.store, found.where, at), instanceId);
1170
+ if (generation !== undefined)
1171
+ return generation;
1172
+ }
1173
+ return;
1174
+ });
1175
+ var recordedInstance = (fqn, instanceId) => Effect24.gen(function* () {
1176
+ const generation = yield* recordedGeneration(fqn, instanceId);
1177
+ if (generation === undefined)
1178
+ return "absent";
1179
+ return (yield* wholeDeclaration(fqn, generation.props)) ? "whole" : "partial";
1180
+ });
1181
+ var isCreate = (fqn, instanceId) => Effect24.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
1182
+ var forgetRefusedCreate = (fqn, instanceId) => Effect24.gen(function* () {
1183
+ const found = yield* storeOf;
1184
+ if (found === undefined)
1185
+ return;
1186
+ const row = yield* rowAt(found.store, found.where, fqn);
1187
+ const mine = row;
1188
+ if (mine?.status !== "creating" || mine.instanceId !== instanceId || mine.attr !== undefined) {
1189
+ return;
1190
+ }
1191
+ yield* found.store.delete({ ...found.where, fqn }).pipe(Effect24.ignore);
1192
+ });
1193
+
1194
+ // src/ownership/adopt.ts
1195
+ var adoptEnabled = (fqn) => Effect25.gen(function* () {
1196
+ const stack = yield* Effect25.serviceOption(Stack3);
1197
+ const scoped = Option5.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;
1198
+ if (scoped !== undefined)
1199
+ return scoped;
1200
+ const policy = yield* Effect25.serviceOption(AdoptPolicy);
1201
+ if (Option5.isSome(policy))
1202
+ return policy.value;
1203
+ const context = yield* Effect25.serviceOption(AlchemyContext);
1204
+ return Option5.isSome(context) ? context.value.adopt : false;
1205
+ });
1206
+ var adoptable = (owner) => Effect25.gen(function* () {
1207
+ if (yield* isCreate(owner.fqn, owner.instanceId))
1208
+ return true;
1209
+ return yield* unfinished(owner.instanceId);
1210
+ });
1211
+ var adoptsAtApply = (owner) => Effect25.gen(function* () {
1212
+ if (!(yield* adoptEnabled(owner.fqn)))
1213
+ return false;
1214
+ return yield* adoptable(owner);
1077
1215
  });
1078
- var ProxmoxLxcProvider = () => Provider13.effect(ProxmoxLxc, Effect22.succeed(ProxmoxLxc.Provider.of(handlers10)));
1216
+ var refuseTakeover = (owner, what) => Effect25.gen(function* () {
1217
+ if (owner.output !== undefined)
1218
+ return;
1219
+ if (yield* resumes(owner.instanceId))
1220
+ return;
1221
+ const may = yield* adoptable(owner);
1222
+ if (may && (yield* adoptEnabled(owner.fqn)))
1223
+ return;
1224
+ yield* forgetRefusedCreate(owner.fqn, owner.instanceId);
1225
+ 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.";
1226
+ return yield* Effect25.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}`));
1227
+ });
1228
+ var claimFor = (owner) => (what) => refuseTakeover(owner, what);
1229
+
1230
+ // src/ownership/probe.ts
1231
+ import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
1232
+ import * as Effect26 from "effect/Effect";
1233
+ var ownedRead = (ask, found, settled2) => Effect26.gen(function* () {
1234
+ if (found === undefined || ask.output !== undefined)
1235
+ return found;
1236
+ const recorded = yield* recordedInstance(ask.fqn, ask.instanceId);
1237
+ if (recorded === "absent")
1238
+ return Unowned2(found);
1239
+ if (recorded === "partial") {
1240
+ yield* Effect26.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");
1241
+ return Unowned2(found);
1242
+ }
1243
+ const unproven = (reason) => Effect26.as(Effect26.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);
1244
+ const ours = yield* settled2.pipe(Effect26.catch(unproven), Effect26.catchDefect(unproven));
1245
+ return ours ? found : Unowned2(found);
1246
+ });
1247
+
1248
+ // src/proxmox/lxc-wire.ts
1249
+ var pairs = (value, defaultKey) => value.split(",").map((part) => part.trim()).filter((part) => part !== "").map((part) => {
1250
+ const at = part.indexOf("=");
1251
+ if (at === -1)
1252
+ return [defaultKey ?? part, defaultKey === undefined ? "" : part];
1253
+ return [part.slice(0, at).trim(), part.slice(at + 1).trim()];
1254
+ });
1255
+ var DEFAULTS = {
1256
+ dev: { "deny-write": "0" },
1257
+ features: { force_rw_sys: "0", fuse: "0", keyctl: "0", mknod: "0", nesting: "0" },
1258
+ net: { firewall: "0", "host-managed": "0", link_down: "0", type: "veth" },
1259
+ volume: { backup: "0", keepattrs: "0", quota: "0", replicate: "1", ro: "0", shared: "0" }
1260
+ };
1261
+ var FLAGS = new Set([
1262
+ "acl",
1263
+ "backup",
1264
+ "deny-write",
1265
+ "firewall",
1266
+ "force_rw_sys",
1267
+ "fuse",
1268
+ "host-managed",
1269
+ "keepattrs",
1270
+ "keyctl",
1271
+ "link_down",
1272
+ "mknod",
1273
+ "nesting",
1274
+ "quota",
1275
+ "replicate",
1276
+ "ro",
1277
+ "shared"
1278
+ ]);
1279
+ var SETS = new Set(["mount", "mountoptions"]);
1280
+ var canonicalValue = (key, value) => {
1281
+ if (FLAGS.has(key))
1282
+ return canonicalToken(value);
1283
+ if (SETS.has(key))
1284
+ return [...new Set(value.split(";").map((part) => part.trim()))].sort().join(";");
1285
+ if (key === "hwaddr")
1286
+ return value.toUpperCase();
1287
+ return value;
1288
+ };
1289
+ var optionMap = (value, family, defaultKey) => {
1290
+ const defaults = DEFAULTS[family] ?? {};
1291
+ const out = new Map;
1292
+ for (const [key, raw] of pairs(value, defaultKey)) {
1293
+ const canonical = canonicalValue(key, raw);
1294
+ if (defaults[key] !== canonical)
1295
+ out.set(key, canonical);
1296
+ }
1297
+ return out;
1298
+ };
1299
+ var sameMap = (left, right) => left.size === right.size && [...left].every(([key, value]) => right.get(key) === value);
1300
+ var sortedForm = (map6) => [...map6].map(([key, value]) => `${key}=${value}`).sort().join(",");
1301
+ var printMap = (map6, lead = []) => [...lead.filter((key) => map6.has(key)), ...[...map6.keys()].filter((k) => !lead.includes(k))].map((key) => `${key}=${map6.get(key) ?? ""}`).join(",");
1302
+ var netMaps = (declared, live) => {
1303
+ const want = optionMap(declared, "net");
1304
+ const have = optionMap(live, "net");
1305
+ if (!want.has("hwaddr"))
1306
+ have.delete("hwaddr");
1307
+ return { have, want };
1308
+ };
1309
+ var netWrite = (declared, live) => {
1310
+ const want = new Map(pairs(declared));
1311
+ const mac = live === undefined ? undefined : new Map(pairs(live)).get("hwaddr");
1312
+ if (!want.has("hwaddr") && mac !== undefined)
1313
+ want.set("hwaddr", mac);
1314
+ return printMap(want, ["name"]);
1315
+ };
1316
+ var tagSet = (value) => [...new Set(value.split(/[;,\s]+/).filter((tag) => tag !== ""))].map((tag) => tag.toLowerCase()).sort().join(";");
1317
+ var orderedList = (value) => value.split(/[;,\s]+/).filter((part) => part !== "").join(" ");
1318
+ var descriptionText = (value) => value.replace(/\s+$/, "");
1319
+ var SCALAR_KINDS = {
1320
+ arch: "text",
1321
+ cmode: "text",
1322
+ console: "flag",
1323
+ cores: "number",
1324
+ cpulimit: "number",
1325
+ cpuunits: "number",
1326
+ description: "form",
1327
+ features: "form",
1328
+ hostname: "text",
1329
+ memory: "number",
1330
+ nameserver: "form",
1331
+ onboot: "flag",
1332
+ ostype: "text",
1333
+ protection: "flag",
1334
+ searchdomain: "form",
1335
+ startup: "form",
1336
+ swap: "number",
1337
+ tags: "form",
1338
+ timezone: "text",
1339
+ tty: "number"
1340
+ };
1341
+ var CLEARABLE = new Set([
1342
+ "description",
1343
+ "features",
1344
+ "nameserver",
1345
+ "searchdomain",
1346
+ "startup",
1347
+ "tags",
1348
+ "timezone"
1349
+ ]);
1350
+ var SCALAR_DEFAULTS = {
1351
+ arch: "amd64",
1352
+ cmode: "tty",
1353
+ console: "1",
1354
+ cpulimit: "0",
1355
+ memory: "512",
1356
+ onboot: "0",
1357
+ protection: "0",
1358
+ swap: "512",
1359
+ tty: "2"
1360
+ };
1361
+ var scalarForm = (key, value) => {
1362
+ const kind2 = SCALAR_KINDS[key];
1363
+ if (kind2 === "number")
1364
+ return String(Number(value));
1365
+ if (kind2 === "flag")
1366
+ return canonicalToken(value);
1367
+ if (key === "description")
1368
+ return descriptionText(value);
1369
+ if (key === "features")
1370
+ return sortedForm(optionMap(value, "features"));
1371
+ if (key === "nameserver" || key === "searchdomain")
1372
+ return orderedList(value);
1373
+ if (key === "startup")
1374
+ return propertyString(value, "order");
1375
+ if (key === "tags")
1376
+ return tagSet(value);
1377
+ return value;
1378
+ };
1379
+
1380
+ // src/proxmox/lxc-props.ts
1381
+ var UNSTORED = new Set([
1382
+ "description",
1383
+ "digest",
1384
+ "env",
1385
+ "lock",
1386
+ "lxc",
1387
+ "pending"
1388
+ ]);
1389
+ var INDEXED = {
1390
+ dev: 256,
1391
+ mp: 256,
1392
+ net: 32
1393
+ };
1394
+ var indexedKey = (key) => {
1395
+ const match = /^(mp|net|dev)(0|[1-9]\d*)$/.exec(key);
1396
+ if (match === null)
1397
+ return;
1398
+ const family = match[1];
1399
+ const index = Number(match[2]);
1400
+ return index < INDEXED[family] ? [family, index] : undefined;
1401
+ };
1402
+ var NOT_CONFIG = new Set([
1403
+ "node",
1404
+ "ostemplate",
1405
+ "start",
1406
+ "target",
1407
+ "vmid"
1408
+ ]);
1409
+ var declaredValue = (props, key) => props[key];
1410
+ var declaredKeys = (props) => Object.keys(props).filter((key) => !NOT_CONFIG.has(key) && declaredValue(props, key) !== undefined).sort();
1411
+ var wireValue = (value) => value === true ? "1" : value === false ? "0" : String(value);
1412
+ var isManagedKey = (key) => SCALAR_KINDS[key] !== undefined || key === "unprivileged" || key === "rootfs" || indexedKey(key) !== undefined;
1413
+ var storedConfig = (live) => {
1414
+ const out = {};
1415
+ for (const [key, value] of Object.entries(live)) {
1416
+ if (!isManagedKey(key) || UNSTORED.has(key))
1417
+ continue;
1418
+ if (value === null || value === undefined || typeof value === "object")
1419
+ continue;
1420
+ out[key] = wireValue(value);
1421
+ }
1422
+ return out;
1423
+ };
1424
+ var rawKeysOf = (live) => {
1425
+ const raw = live["lxc"];
1426
+ if (!Array.isArray(raw))
1427
+ return [];
1428
+ return raw.map((pair) => Array.isArray(pair) ? String(pair[0]) : "").filter((key) => key !== "");
1429
+ };
1430
+
1431
+ // src/proxmox/lxc-volume.ts
1432
+ var NEW_DISK = /^([^:\s]+):(\d+(?:\.\d+)?)$/;
1433
+ var UNITS = {
1434
+ G: 1024 ** 3,
1435
+ K: 1024,
1436
+ M: 1024 ** 2,
1437
+ T: 1024 ** 4
1438
+ };
1439
+ var sizeBytes = (text2) => {
1440
+ const match = /^(\d+(?:\.\d+)?)([KMGT])?$/.exec(text2.trim());
1441
+ if (match === null)
1442
+ return;
1443
+ return Math.trunc(Number(match[1]) * (match[2] === undefined ? 1 : UNITS[match[2]] ?? 1));
1444
+ };
1445
+ var parseVolume = (text2) => {
1446
+ const [first] = pairs(text2, "volume");
1447
+ const spec2 = first?.[0] === "volume" ? first[1] : "";
1448
+ const options = optionMap(text2, "volume", "volume");
1449
+ options.delete("volume");
1450
+ const sizeText = options.get("size");
1451
+ options.delete("size");
1452
+ const fresh = NEW_DISK.exec(spec2);
1453
+ if (fresh !== null) {
1454
+ const gib = `${fresh[2] ?? "0"}G`;
1455
+ return {
1456
+ kind: "new",
1457
+ options,
1458
+ size: sizeBytes(gib),
1459
+ sizeText: gib,
1460
+ source: fresh[1] ?? "",
1461
+ volname: ""
1462
+ };
1463
+ }
1464
+ const size = sizeText === undefined ? undefined : sizeBytes(sizeText);
1465
+ if (spec2.startsWith("/")) {
1466
+ const kind2 = spec2.startsWith("/dev/") ? "device" : "bind";
1467
+ return { kind: kind2, options, size, sizeText, source: spec2, volname: "" };
1468
+ }
1469
+ const at = spec2.indexOf(":");
1470
+ return {
1471
+ kind: "volume",
1472
+ options,
1473
+ size,
1474
+ sizeText,
1475
+ source: at === -1 ? spec2 : spec2.slice(0, at),
1476
+ volname: at === -1 ? "" : spec2.slice(at + 1)
1477
+ };
1478
+ };
1479
+ var isHostPath = (volume) => volume.kind === "bind" || volume.kind === "device";
1480
+ var rewrite = (live, declared) => {
1481
+ const parts = [`${live.source}:${live.volname}`];
1482
+ for (const [key, value] of declared.options)
1483
+ parts.push(`${key}=${value}`);
1484
+ if (live.sizeText !== undefined)
1485
+ parts.push(`size=${live.sizeText}`);
1486
+ return parts.join(",");
1487
+ };
1488
+ var judgeVolume = (key, declared, live) => {
1489
+ const want = parseVolume(declared);
1490
+ const have = parseVolume(live);
1491
+ if (isHostPath(want) || isHostPath(have)) {
1492
+ const same2 = want.kind === have.kind && want.source === have.source && sameMap(want.options, have.options);
1493
+ return same2 ? {} : { rootOnly: true };
1494
+ }
1495
+ if (want.source !== have.source) {
1496
+ return {
1497
+ refuse: `${key}: the volume is on ${have.source}, declared on ${want.source}. Moving it is ` + "`pct move-volume` (a copy), not a config update -- move it by hand, then declare it."
1498
+ };
1499
+ }
1500
+ if (want.kind === "volume" && want.volname !== have.volname) {
1501
+ return {
1502
+ refuse: `${key}: declared volume ${want.volname} is not the live ${have.volname}. Writing it would ` + "detach the live volume to unusedN; attach or swap volumes by hand."
1503
+ };
1504
+ }
1505
+ let resize;
1506
+ if (want.size !== undefined && have.size !== undefined && want.size !== have.size) {
1507
+ if (want.size < have.size) {
1508
+ return {
1509
+ refuse: `${key}: declared ${want.sizeText ?? "?"} is smaller than the live ` + `${have.sizeText ?? "?"}. PVE cannot shrink a container volume ("unable to shrink disk ` + 'size"), and replacing the guest to get there is not an update this resource makes.'
1510
+ };
1511
+ }
1512
+ resize = want.sizeText;
1513
+ }
1514
+ const put = sameMap(want.options, have.options) ? undefined : rewrite(have, want);
1515
+ return {
1516
+ ...put === undefined ? {} : { put },
1517
+ ...resize === undefined ? {} : { resize }
1518
+ };
1519
+ };
1520
+
1521
+ // src/proxmox/lxc-judge.ts
1522
+ var text2 = (value) => value === undefined || value === null ? undefined : wireValue(value);
1523
+ var rootOnly = (vmid, key, value, why) => `${key}: ${why} PVE lets only root@pam write it, and an API token is never root@pam. Run ` + `\`pct set ${String(vmid)} ${value === "" ? `--delete ${key}` : `--${key} '${value}'`}\` ` + "on the node, then plan again.";
1524
+ var rawFeatures = (value) => new Map(pairs(value).map(([key, entry]) => [key, key === "mount" ? entry : canonicalToken(entry)]));
1525
+ var featuresNeedRoot = (live, sent, unprivileged) => {
1526
+ if (!unprivileged)
1527
+ return true;
1528
+ const have = rawFeatures(live);
1529
+ if (sent === undefined)
1530
+ return !(have.size === 0 || have.size === 1 && have.has("nesting"));
1531
+ const want = rawFeatures(sent);
1532
+ const keys = new Set([...have.keys(), ...want.keys()]);
1533
+ return [...keys].some((key) => key !== "nesting" && (have.get(key) ?? "") !== (want.get(key) ?? ""));
1534
+ };
1535
+ var featureWhy = (unprivileged) => unprivileged ? "A feature other than nesting." : "Any feature on a privileged guest.";
1536
+ var judge = (props, live) => {
1537
+ const put = {};
1538
+ const clear = [];
1539
+ const resize = [];
1540
+ const refuse = [];
1541
+ const drift = new Set;
1542
+ const unprivileged = bool(live["unprivileged"], false);
1543
+ for (const key of declaredKeys(props)) {
1544
+ const want = wireValue(declaredValue(props, key));
1545
+ const have = text2(live[key]);
1546
+ const indexed = indexedKey(key);
1547
+ if (key === "unprivileged") {
1548
+ if (canonicalToken(want) !== canonicalToken(have ?? "0")) {
1549
+ drift.add(key);
1550
+ refuse.push(`unprivileged: live ${have ?? "0"}, declared ${want}. PVE refuses to change it ("unable ` + 'to modify read-only option"); only a backup and restore can, and that is a new guest.');
1551
+ }
1552
+ continue;
1553
+ }
1554
+ if (key === "rootfs" || indexed?.[0] === "mp") {
1555
+ if (want === "") {
1556
+ if (have === undefined)
1557
+ continue;
1558
+ drift.add(key);
1559
+ refuse.push(`${key}: detaching a mount point moves its volume to unusedN -- do it by hand.`);
1560
+ } else if (have === undefined) {
1561
+ drift.add(key);
1562
+ if (isHostPath(parseVolume(want))) {
1563
+ refuse.push(rootOnly(props.vmid, key, want, "A bind or device mount point."));
1564
+ } else
1565
+ put[key] = want;
1566
+ } else {
1567
+ const verdict = judgeVolume(key, want, have);
1568
+ if (verdict.refuse !== undefined)
1569
+ refuse.push(verdict.refuse);
1570
+ if (verdict.rootOnly === true) {
1571
+ refuse.push(rootOnly(props.vmid, key, want, "A bind or device mount point."));
1572
+ }
1573
+ if (verdict.put !== undefined)
1574
+ put[key] = verdict.put;
1575
+ if (verdict.resize !== undefined)
1576
+ resize.push({ disk: key, size: verdict.resize });
1577
+ if (Object.keys(verdict).length > 0)
1578
+ drift.add(key);
1579
+ }
1580
+ continue;
1581
+ }
1582
+ if (indexed?.[0] === "dev") {
1583
+ const same2 = want === "" ? have === undefined : have !== undefined && devSame(want, have);
1584
+ if (!same2) {
1585
+ drift.add(key);
1586
+ refuse.push(rootOnly(props.vmid, key, want, "Device passthrough."));
1587
+ }
1588
+ continue;
1589
+ }
1590
+ if (indexed?.[0] === "net") {
1591
+ if (want === "") {
1592
+ if (have !== undefined) {
1593
+ drift.add(key);
1594
+ clear.push(key);
1595
+ }
1596
+ } else if (have === undefined) {
1597
+ drift.add(key);
1598
+ put[key] = want;
1599
+ } else {
1600
+ const maps = netMaps(want, have);
1601
+ if (!sameMap(maps.want, maps.have)) {
1602
+ drift.add(key);
1603
+ put[key] = netWrite(want, have);
1604
+ }
1605
+ }
1606
+ continue;
1607
+ }
1608
+ if (!isManagedKey(key)) {
1609
+ drift.add(key);
1610
+ refuse.push(`${key}: not a config key this resource manages.`);
1611
+ continue;
1612
+ }
1613
+ const wanted = want === "" ? "" : scalarForm(key, want);
1614
+ const current = have ?? SCALAR_DEFAULTS[key];
1615
+ const had = current === undefined ? "" : scalarForm(key, current);
1616
+ if (wanted === had)
1617
+ continue;
1618
+ drift.add(key);
1619
+ const next = key === "features" ? wanted : want;
1620
+ if (key === "features" && featuresNeedRoot(have ?? "", next || undefined, unprivileged)) {
1621
+ refuse.push(rootOnly(props.vmid, key, want, featureWhy(unprivileged)));
1622
+ } else if (next === "" && CLEARABLE.has(key)) {
1623
+ clear.push(key);
1624
+ } else if (next === "") {
1625
+ refuse.push(`${key}: cannot be removed; declare the value it should have.`);
1626
+ } else
1627
+ put[key] = next;
1628
+ }
1629
+ return { clear, drift: [...drift].sort(), put, refuse, resize };
1630
+ };
1631
+ var devSame = (want, have) => sortedForm(optionMap(want, "dev", "path")) === sortedForm(optionMap(have, "dev", "path"));
1632
+
1633
+ // src/proxmox/lxc-create-form.ts
1634
+ var existingVolume = (key, value) => `${key}: ${value} names an existing volume, and PVE would unpack the template onto it. A ` + "create allocates new volumes only: declare `storage:GiB`, and attach an existing volume by " + "hand after the create.";
1635
+ var createRefusals = (props) => {
1636
+ const refuse = [];
1637
+ if (props.ostemplate === undefined || props.ostemplate === "") {
1638
+ refuse.push(`CT ${String(props.vmid)} does not exist on ${props.node}, and without \`ostemplate\` ` + "there is nothing to create it from. To adopt a guest, declare the node it is on.");
1639
+ }
1640
+ const unprivileged = props.unprivileged === undefined ? true : bool(props.unprivileged);
1641
+ for (const key of declaredKeys(props)) {
1642
+ const value = wireValue(declaredValue(props, key));
1643
+ const family = indexedKey(key)?.[0];
1644
+ if (!isManagedKey(key))
1645
+ refuse.push(`${key}: not a config key this resource manages.`);
1646
+ if (value === "")
1647
+ continue;
1648
+ if (family === "dev")
1649
+ refuse.push(rootOnly(props.vmid, key, value, "Device passthrough."));
1650
+ const volume = family === "mp" || key === "rootfs" ? parseVolume(value) : undefined;
1651
+ if (volume !== undefined && isHostPath(volume)) {
1652
+ refuse.push(rootOnly(props.vmid, key, value, "A bind or device mount point."));
1653
+ }
1654
+ if (volume?.kind === "volume")
1655
+ refuse.push(existingVolume(key, value));
1656
+ if (key === "features" && featuresNeedRoot("", value, unprivileged)) {
1657
+ refuse.push(rootOnly(props.vmid, key, value, featureWhy(unprivileged)));
1658
+ }
1659
+ }
1660
+ return refuse;
1661
+ };
1662
+ var createForm3 = (props) => {
1663
+ const form = { vmid: String(props.vmid) };
1664
+ if (props.ostemplate !== undefined)
1665
+ form["ostemplate"] = props.ostemplate;
1666
+ if (props.start !== undefined)
1667
+ form["start"] = wireValue(props.start);
1668
+ for (const key of declaredKeys(props)) {
1669
+ const value = wireValue(declaredValue(props, key));
1670
+ if (value === "")
1671
+ continue;
1672
+ form[key] = key === "features" ? scalarForm(key, value) : value;
1673
+ }
1674
+ return form;
1675
+ };
1676
+
1677
+ // src/proxmox/lxc-identity.ts
1678
+ var plain = (news, key) => {
1679
+ const value = news[key];
1680
+ return typeof value === "string" || typeof value === "number" ? value : undefined;
1681
+ };
1682
+ var identityRefusals = (news, olds, output) => {
1683
+ const was = output?.vmid ?? olds.vmid;
1684
+ const vmid = plain(news, "vmid");
1685
+ const template = plain(news, "ostemplate");
1686
+ const reasons = [];
1687
+ if (vmid !== undefined && vmid !== was) {
1688
+ reasons.push(`vmid: ${String(was)} -> ${String(vmid)} is a different guest. Declare it as a new ` + "resource; this one is retained when its declaration goes.");
1689
+ }
1690
+ if (template !== undefined && olds.ostemplate !== undefined && template !== olds.ostemplate) {
1691
+ reasons.push("ostemplate: a template is consumed at create; another one is a new guest.");
1692
+ }
1693
+ return reasons;
1694
+ };
1695
+
1696
+ // src/proxmox/lxc-lifecycle.ts
1697
+ import * as Effect28 from "effect/Effect";
1698
+
1699
+ // src/proxmox/network-apply-read.ts
1700
+ import * as Effect27 from "effect/Effect";
1701
+ var stagedDiff = (target, node) => Effect27.gen(function* () {
1702
+ const path = `nodes/${node}/network`;
1703
+ const credential = yield* leased(target, "read");
1704
+ const envelope = yield* pveEnvelopeWith(target, credential, "GET", path);
1705
+ return text(envelope.changes);
1706
+ });
1707
+ var pendingLines = (diff) => {
1708
+ if (diff === "")
1709
+ return 0;
1710
+ const changed = diff.split(`
1711
+ `).filter((line) => (line.startsWith("+") || line.startsWith("-")) && !line.startsWith("+++") && !line.startsWith("---")).length;
1712
+ return Math.max(changed, 1);
1713
+ };
1714
+ var pendingCount = (target, node) => stagedDiff(target, node).pipe(Effect27.map(pendingLines));
1715
+ var clusterHealth = (target) => pve(target, "read", "GET", "cluster/status").pipe(Effect27.map((rows) => {
1716
+ const all = rows ?? [];
1717
+ const cluster = all.find((row) => row.type === "cluster");
1718
+ return {
1719
+ offline: all.filter((row) => row.type === "node" && !bool(row.online)).map((row) => text(row.name, "<unnamed>")),
1720
+ quorate: cluster === undefined ? true : bool(cluster.quorate)
1721
+ };
1722
+ }));
1723
+ var degradedReason = (target) => clusterHealth(target).pipe(Effect27.map((health) => health.quorate && health.offline.length === 0 ? undefined : `quorate=${String(health.quorate)} offline=[${health.offline.join(", ")}]`));
1724
+ var UNREACHABLE = "the task could not be read -- the node stopped answering, or this lease cannot see it";
1725
+ var STILL_RUNNING = "still running when this provider stopped waiting";
1726
+ var ATTEMPTS = 30;
1727
+ var awaitTask = (target, credential, node, upid, attempts = ATTEMPTS) => Effect27.gen(function* () {
1728
+ const path = `nodes/${node}/tasks/${upid}/status`;
1729
+ for (let attempt = 0;attempt < attempts; attempt += 1) {
1730
+ const status = yield* pveWith(target, credential, "GET", path).pipe(Effect27.orElseSucceed(() => {
1731
+ return;
1732
+ }));
1733
+ if (status === undefined)
1734
+ return UNREACHABLE;
1735
+ if (status.status === "stopped")
1736
+ return text(status.exitstatus, "stopped with no exitstatus");
1737
+ yield* Effect27.sleep("1 second");
1738
+ }
1739
+ return STILL_RUNNING;
1740
+ });
1741
+
1742
+ // src/proxmox/lxc-lifecycle.ts
1743
+ class LxcRefusedError extends Error {
1744
+ name = "LxcRefusedError";
1745
+ }
1746
+ var guestPath = (where) => `nodes/${where.node}/lxc/${String(where.vmid)}`;
1747
+ var CREATE_POLLS = 180;
1748
+ var TASK_POLLS = 60;
1749
+ var isServerError = (error) => error instanceof PveError && error.status === 500;
1750
+ var readLive = (where) => Effect28.gen(function* () {
1751
+ const answer = yield* pve(where.target, "read", "GET", `${guestPath(where)}/config`).pipe(Effect28.map((data) => ({ data, error: undefined })), Effect28.catchIf(isServerError, (error) => Effect28.succeed({ data: undefined, error })));
1752
+ if (answer.data !== undefined && answer.data !== null)
1753
+ return answer.data;
1754
+ const rows = yield* pve(where.target, "read", "GET", "cluster/resources?type=vm");
1755
+ const found = (rows ?? []).find((row) => Number(row.vmid) === where.vmid);
1756
+ if (found !== undefined && found.node === where.node && found.type === "lxc") {
1757
+ return yield* Effect28.fail(answer.error ?? new LxcRefusedError(`CT ${String(where.vmid)} is listed but its config read empty.`));
1758
+ }
1759
+ if (found !== undefined) {
1760
+ return yield* Effect28.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."));
1761
+ }
1762
+ return;
1763
+ });
1764
+ var attributesOf = (where, live) => ({
1765
+ config: storedConfig(live),
1766
+ node: where.node,
1767
+ rawKeys: rawKeysOf(live),
1768
+ vmid: where.vmid
1769
+ });
1770
+ var task = (where, method, path, what, polls, form) => Effect28.gen(function* () {
1771
+ const credential = yield* mint(where.target, "provision");
1772
+ const upid = text(yield* pveWith(where.target, credential, method, path, form));
1773
+ if (upid === "") {
1774
+ return yield* Effect28.fail(new LxcRefusedError(`${what}: PVE returned no task id, so the write cannot be confirmed and is not claimed.`));
1775
+ }
1776
+ const exit = yield* awaitTask(where.target, credential, where.node, upid, polls);
1777
+ if (exit !== "OK") {
1778
+ return yield* Effect28.fail(new LxcRefusedError(`${what}: task ${upid} ended "${exit}". Read \`pvesh get /nodes/${where.node}/tasks/` + `${upid}/log\` before running the deploy again.`));
1779
+ }
1780
+ });
1781
+ var createGuest = (props) => task(props, "POST", `nodes/${props.node}/lxc`, `create CT ${String(props.vmid)}`, CREATE_POLLS, createForm3(props));
1782
+ var updateGuest = (props, change, digest) => Effect28.gen(function* () {
1783
+ if (Object.keys(change.put).length > 0 || change.clear.length > 0) {
1784
+ const form = withClears({ ...change.put, ...digest === "" ? {} : { digest } }, change.clear);
1785
+ yield* pve(props.target, "provision", "PUT", `${guestPath(props)}/config`, form);
1786
+ }
1787
+ for (const grow of change.resize) {
1788
+ yield* task(props, "PUT", `${guestPath(props)}/resize`, `grow ${grow.disk} of CT ${String(props.vmid)} to ${grow.size}`, TASK_POLLS, { disk: grow.disk, size: grow.size });
1789
+ }
1790
+ });
1791
+ var destroyGuest = (where, declared) => Effect28.gen(function* () {
1792
+ const live = yield* readLive(where);
1793
+ if (live === undefined)
1794
+ return;
1795
+ const drift = judge(declared, live).drift;
1796
+ if (drift.length > 0) {
1797
+ return yield* Effect28.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."));
1798
+ }
1799
+ yield* task(where, "DELETE", guestPath(where), `destroy CT ${String(where.vmid)}`, TASK_POLLS);
1800
+ });
1801
+
1802
+ // src/proxmox/lxc.ts
1803
+ var ProxmoxLxc = Resource13("Proxmox.Lxc", { defaultRemovalPolicy: "retain" });
1804
+ var refused = (reasons) => Effect29.fail(new LxcRefusedError(reasons.join(`
1805
+ `)));
1806
+ var diff = (news, olds, output) => Effect29.gen(function* () {
1807
+ const identity2 = identityRefusals(news, olds, output);
1808
+ if (identity2.length > 0)
1809
+ return yield* refused(identity2);
1810
+ if (output === undefined)
1811
+ return;
1812
+ if (!isResolved6(news))
1813
+ return;
1814
+ const live = yield* readLive(news);
1815
+ if (live === undefined) {
1816
+ const refusals = createRefusals(news);
1817
+ if (refusals.length > 0)
1818
+ return yield* refused(refusals);
1819
+ yield* Effect29.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.");
1820
+ return { action: "update" };
1821
+ }
1822
+ const change = judge(news, live);
1823
+ if (change.refuse.length > 0)
1824
+ return yield* refused(change.refuse);
1825
+ const moved = news.node !== output.node;
1826
+ if (change.drift.length === 0 && !moved)
1827
+ return { action: "noop" };
1828
+ 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.";
1829
+ yield* Effect29.logWarning(`Proxmox.Lxc ${news.node}/${String(news.vmid)}: ${what}`);
1830
+ return { action: "update" };
1831
+ });
1832
+ var reconcile = (owner, news) => Effect29.gen(function* () {
1833
+ const { output } = owner;
1834
+ const identity2 = identityRefusals(news, news, output);
1835
+ if (identity2.length > 0)
1836
+ return yield* refused(identity2);
1837
+ const live = yield* readLive(news);
1838
+ if (live === undefined) {
1839
+ const refusals = createRefusals(news);
1840
+ if (refusals.length > 0)
1841
+ return yield* refused(refusals);
1842
+ yield* createGuest(news);
1843
+ } else {
1844
+ if (output === undefined) {
1845
+ yield* refuseTakeover(owner, `Proxmox.Lxc CT ${String(news.vmid)} on ${news.node}`);
1846
+ }
1847
+ const change = judge(news, live);
1848
+ if (output === undefined && change.drift.length > 0) {
1849
+ return yield* refused([
1850
+ `CT ${String(news.vmid)} already exists on ${news.node} and differs in ` + `${change.drift.join(", ")}, but this deploy planned a create. A create never writes ` + "onto an existing guest: plan again, so the plan can show it and adopt it."
1851
+ ]);
1852
+ }
1853
+ if (change.refuse.length > 0)
1854
+ return yield* refused(change.refuse);
1855
+ if (change.drift.length > 0)
1856
+ yield* updateGuest(news, change, text(live["digest"]));
1857
+ }
1858
+ const after = yield* readLive(news);
1859
+ if (after === undefined) {
1860
+ return yield* refused([
1861
+ `CT ${String(news.vmid)}: the write returned no error but the guest is still absent.`
1862
+ ]);
1863
+ }
1864
+ const left = judge(news, after).drift;
1865
+ if (left.length > 0) {
1866
+ return yield* refused([
1867
+ `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."
1868
+ ]);
1869
+ }
1870
+ return attributesOf(news, after);
1871
+ });
1872
+ var whereOf = (olds, output) => ({
1873
+ node: output?.node ?? olds.node,
1874
+ target: olds.target,
1875
+ vmid: output?.vmid ?? olds.vmid
1876
+ });
1877
+ var read = (owner, olds) => Effect29.gen(function* () {
1878
+ const where = whereOf(olds, owner.output);
1879
+ const live = yield* readLive(where);
1880
+ if (live === undefined)
1881
+ return;
1882
+ const settled2 = Effect29.sync(() => judge(olds, live).drift.length === 0);
1883
+ return yield* ownedRead(owner, attributesOf(where, live), settled2);
1884
+ });
1885
+ var handlers10 = {
1886
+ delete: ({ olds, output }) => destroyGuest(whereOf(olds, output), olds),
1887
+ diff: ({
1888
+ news,
1889
+ olds,
1890
+ output
1891
+ }) => diff(news, olds, output),
1892
+ list: () => Effect29.succeed([]),
1893
+ read: ({
1894
+ fqn,
1895
+ instanceId,
1896
+ olds,
1897
+ output
1898
+ }) => read({ fqn, instanceId, output }, olds),
1899
+ reconcile: ({
1900
+ fqn,
1901
+ instanceId,
1902
+ news,
1903
+ output
1904
+ }) => reconcile({ fqn, instanceId, output }, news)
1905
+ };
1906
+ var ProxmoxLxcProvider = () => Provider13.effect(ProxmoxLxc, Effect29.succeed(ProxmoxLxc.Provider.of(handlers10)).pipe(Effect29.map(provingResumes)));
1079
1907
  // src/proxmox/metric-server.ts
1080
1908
  import { Resource as Resource14 } from "alchemy";
1081
1909
  import * as Provider14 from "alchemy/Provider";
1082
- import * as Effect23 from "effect/Effect";
1910
+ import * as Effect30 from "effect/Effect";
1083
1911
 
1084
1912
  // src/proxmox/metric-server-form.ts
1085
1913
  var UNSET2 = -1;
@@ -1187,67 +2015,22 @@ var handlers11 = pveHandlers({
1187
2015
  path: (props) => `cluster/metrics/server/${props.id}`,
1188
2016
  updateForm: update
1189
2017
  });
1190
- var ProxmoxMetricServerProvider = () => Provider14.effect(ProxmoxMetricServer, Effect23.succeed(ProxmoxMetricServer.Provider.of(handlers11)));
2018
+ var ProxmoxMetricServerProvider = () => Provider14.effect(ProxmoxMetricServer, Effect30.succeed(ProxmoxMetricServer.Provider.of(handlers11)));
1191
2019
  // src/proxmox/network-apply.ts
1192
2020
  import { Resource as Resource15 } from "alchemy";
1193
- import { isResolved as isResolved6 } from "alchemy/Diff";
2021
+ import { isResolved as isResolved7 } from "alchemy/Diff";
1194
2022
  import * as Provider15 from "alchemy/Provider";
1195
- import * as Effect25 from "effect/Effect";
1196
-
1197
- // src/proxmox/network-apply-read.ts
1198
- import * as Effect24 from "effect/Effect";
1199
- var stagedDiff = (target, node) => Effect24.gen(function* () {
1200
- const path = `nodes/${node}/network`;
1201
- const credential = yield* leased(target, "read");
1202
- const envelope = yield* pveEnvelopeWith(target, credential, "GET", path);
1203
- return text(envelope.changes);
1204
- });
1205
- var pendingLines = (diff) => {
1206
- if (diff === "")
1207
- return 0;
1208
- const changed = diff.split(`
1209
- `).filter((line) => (line.startsWith("+") || line.startsWith("-")) && !line.startsWith("+++") && !line.startsWith("---")).length;
1210
- return Math.max(changed, 1);
1211
- };
1212
- var pendingCount = (target, node) => stagedDiff(target, node).pipe(Effect24.map(pendingLines));
1213
- var clusterHealth = (target) => pve(target, "read", "GET", "cluster/status").pipe(Effect24.map((rows) => {
1214
- const all = rows ?? [];
1215
- const cluster = all.find((row) => row.type === "cluster");
1216
- return {
1217
- offline: all.filter((row) => row.type === "node" && !bool(row.online)).map((row) => text(row.name, "<unnamed>")),
1218
- quorate: cluster === undefined ? true : bool(cluster.quorate)
1219
- };
1220
- }));
1221
- var degradedReason = (target) => clusterHealth(target).pipe(Effect24.map((health) => health.quorate && health.offline.length === 0 ? undefined : `quorate=${String(health.quorate)} offline=[${health.offline.join(", ")}]`));
1222
- var UNREACHABLE = "the task could not be read -- the node stopped answering, or this lease cannot see it";
1223
- var STILL_RUNNING = "still running when this provider stopped waiting";
1224
- var ATTEMPTS = 30;
1225
- var awaitTask = (target, credential, node, upid) => Effect24.gen(function* () {
1226
- const path = `nodes/${node}/tasks/${upid}/status`;
1227
- for (let attempt = 0;attempt < ATTEMPTS; attempt += 1) {
1228
- const status = yield* pveWith(target, credential, "GET", path).pipe(Effect24.orElseSucceed(() => {
1229
- return;
1230
- }));
1231
- if (status === undefined)
1232
- return UNREACHABLE;
1233
- if (status.status === "stopped")
1234
- return text(status.exitstatus, "stopped with no exitstatus");
1235
- yield* Effect24.sleep("1 second");
1236
- }
1237
- return STILL_RUNNING;
1238
- });
1239
-
1240
- // src/proxmox/network-apply.ts
2023
+ import * as Effect31 from "effect/Effect";
1241
2024
  var ProxmoxNetworkApply = Resource15("Proxmox.NetworkApply");
1242
- var read = (props) => pendingCount(props.target, props.node).pipe(Effect25.map((pending) => ({ node: props.node, pending })));
1243
- var diff = (news) => Effect25.gen(function* () {
1244
- if (!isResolved6(news))
2025
+ var read2 = (props) => pendingCount(props.target, props.node).pipe(Effect31.map((pending) => ({ node: props.node, pending })));
2026
+ var diff2 = (news) => Effect31.gen(function* () {
2027
+ if (!isResolved7(news))
1245
2028
  return;
1246
- const { pending } = yield* read(news);
2029
+ const { pending } = yield* read2(news);
1247
2030
  return pending === 0 ? { action: "noop" } : { action: "update" };
1248
2031
  });
1249
- var refuse = (node, why) => Effect25.die(new Error(`Proxmox.NetworkApply ${node}: ${why}`));
1250
- var apply = (props) => Effect25.gen(function* () {
2032
+ var refuse = (node, why) => Effect31.die(new Error(`Proxmox.NetworkApply ${node}: ${why}`));
2033
+ var apply = (props) => Effect31.gen(function* () {
1251
2034
  const node = props.node;
1252
2035
  const staged = yield* pendingCount(props.target, node);
1253
2036
  if (staged === 0)
@@ -1276,17 +2059,17 @@ var apply = (props) => Effect25.gen(function* () {
1276
2059
  return { node, pending: left };
1277
2060
  });
1278
2061
  var handlers12 = {
1279
- delete: () => Effect25.void,
1280
- diff: ({ news }) => diff(news),
1281
- list: () => Effect25.succeed([]),
1282
- read: ({ olds }) => read(olds),
2062
+ delete: () => Effect31.void,
2063
+ diff: ({ news }) => diff2(news),
2064
+ list: () => Effect31.succeed([]),
2065
+ read: ({ olds }) => read2(olds),
1283
2066
  reconcile: ({ news }) => apply(news)
1284
2067
  };
1285
- var ProxmoxNetworkApplyProvider = () => Provider15.effect(ProxmoxNetworkApply, Effect25.succeed(ProxmoxNetworkApply.Provider.of(handlers12)));
2068
+ var ProxmoxNetworkApplyProvider = () => Provider15.effect(ProxmoxNetworkApply, Effect31.succeed(ProxmoxNetworkApply.Provider.of(handlers12)));
1286
2069
  // src/proxmox/node-network.ts
1287
2070
  import { Resource as Resource16 } from "alchemy";
1288
2071
  import * as Provider16 from "alchemy/Provider";
1289
- import * as Effect26 from "effect/Effect";
2072
+ import * as Effect32 from "effect/Effect";
1290
2073
 
1291
2074
  // src/proxmox/node-network-form.ts
1292
2075
  var UNSET3 = -1;
@@ -1366,11 +2149,11 @@ var handlers13 = pveHandlers({
1366
2149
  path: (props) => `nodes/${props.node}/network/${props.iface}`,
1367
2150
  updateForm: updateBody2
1368
2151
  });
1369
- var ProxmoxNodeNetworkProvider = () => Provider16.effect(ProxmoxNodeNetwork, Effect26.succeed(ProxmoxNodeNetwork.Provider.of(handlers13)));
2152
+ var ProxmoxNodeNetworkProvider = () => Provider16.effect(ProxmoxNodeNetwork, Effect32.succeed(ProxmoxNodeNetwork.Provider.of(handlers13)));
1370
2153
  // src/proxmox/notification-target.ts
1371
2154
  import { Resource as Resource17 } from "alchemy";
1372
2155
  import * as Provider17 from "alchemy/Provider";
1373
- import * as Effect27 from "effect/Effect";
2156
+ import * as Effect33 from "effect/Effect";
1374
2157
 
1375
2158
  // src/proxmox/notification-target-form.ts
1376
2159
  var REPEATED_KEY = "\x00";
@@ -1426,15 +2209,15 @@ var handlers14 = pveHandlers({
1426
2209
  path: (props) => `cluster/notifications/endpoints/${props.type}/${props.name}`,
1427
2210
  updateForm: shape4
1428
2211
  });
1429
- var ProxmoxNotificationTargetProvider = () => Provider17.effect(ProxmoxNotificationTarget, Effect27.succeed(ProxmoxNotificationTarget.Provider.of(handlers14)));
2212
+ var ProxmoxNotificationTargetProvider = () => Provider17.effect(ProxmoxNotificationTarget, Effect33.succeed(ProxmoxNotificationTarget.Provider.of(handlers14)));
1430
2213
  // src/proxmox/pbs-datastore.ts
1431
2214
  import { Resource as Resource18 } from "alchemy";
1432
- import { isResolved as isResolved7 } from "alchemy/Diff";
2215
+ import { isResolved as isResolved8 } from "alchemy/Diff";
1433
2216
  import * as Provider18 from "alchemy/Provider";
1434
- import * as Effect30 from "effect/Effect";
2217
+ import * as Effect36 from "effect/Effect";
1435
2218
 
1436
2219
  // src/proxmox/pbs-datastore-form.ts
1437
- import * as Effect28 from "effect/Effect";
2220
+ import * as Effect34 from "effect/Effect";
1438
2221
  var attributes = (live, props) => ({
1439
2222
  backend: propertyString(live["backend"]),
1440
2223
  comment: text(live["comment"]),
@@ -1478,7 +2261,7 @@ var mutable = (props) => ({
1478
2261
  ...set2("tuning", props.tuning),
1479
2262
  ...set2("verify-new", flag(props["verify-new"]))
1480
2263
  });
1481
- var createForm3 = (props) => ({
2264
+ var createForm4 = (props) => ({
1482
2265
  ...mutable(props),
1483
2266
  name: props.name,
1484
2267
  path: props.path,
@@ -1486,37 +2269,37 @@ var createForm3 = (props) => ({
1486
2269
  });
1487
2270
  var updateForm = (props) => mutable(props);
1488
2271
  var object2 = (props) => `config/datastore/${props.name}`;
1489
- var readOne = (props) => pve(props.target, "read", "GET", object2(props)).pipe(Effect28.map((live) => live === undefined || typeof live["name"] !== "string" ? undefined : attributes(live, props)), Effect28.orElseSucceed(() => {
2272
+ var readOne = (props) => pve(props.target, "read", "GET", object2(props)).pipe(Effect34.map((live) => live === undefined || typeof live["name"] !== "string" ? undefined : attributes(live, props)), Effect34.orElseSucceed(() => {
1490
2273
  return;
1491
2274
  }));
1492
2275
 
1493
2276
  // src/proxmox/pbs-datastore-guard.ts
1494
- import * as Effect29 from "effect/Effect";
2277
+ import * as Effect35 from "effect/Effect";
1495
2278
  var samePath = (left, right) => {
1496
2279
  const trim = (value) => value.length > 1 ? value.replace(/\/+$/, "") : value;
1497
2280
  return trim(left) === trim(right);
1498
2281
  };
1499
- var guardPath = (live, props) => live.path === "" || samePath(live.path, props.path) ? Effect29.void : Effect29.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."));
1500
- var guardBackend = (live, props) => props.backend === undefined || propertyString(props.backend) === live.backend ? Effect29.void : Effect29.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."));
1501
- var settle3 = (read2, done) => Effect29.gen(function* () {
2282
+ var guardPath = (live, props) => live.path === "" || samePath(live.path, props.path) ? Effect35.void : Effect35.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."));
2283
+ var guardBackend = (live, props) => props.backend === undefined || propertyString(props.backend) === live.backend ? Effect35.void : Effect35.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."));
2284
+ var settle3 = (read3, done) => Effect35.gen(function* () {
1502
2285
  for (let attempt = 0;attempt < 60; attempt += 1) {
1503
- const live = yield* read2;
2286
+ const live = yield* read3;
1504
2287
  if (done(live))
1505
2288
  return live;
1506
- yield* Effect29.sleep("2 seconds");
2289
+ yield* Effect35.sleep("2 seconds");
1507
2290
  }
1508
- return yield* read2;
2291
+ return yield* read3;
1509
2292
  });
1510
2293
 
1511
2294
  // src/proxmox/pbs-datastore.ts
1512
2295
  var PbsDatastore = Resource18("Pbs.Datastore", {
1513
2296
  defaultRemovalPolicy: "retain"
1514
2297
  });
1515
- var PbsDatastoreProvider = () => Provider18.effect(PbsDatastore, Effect30.succeed(PbsDatastore.Provider.of({
1516
- list: () => Effect30.succeed([]),
2298
+ var PbsDatastoreProvider = () => Provider18.effect(PbsDatastore, Effect36.succeed(PbsDatastore.Provider.of({
2299
+ list: () => Effect36.succeed([]),
1517
2300
  read: ({ olds }) => readOne(olds),
1518
- diff: ({ news, output }) => Effect30.gen(function* () {
1519
- if (output === undefined || !isResolved7(news))
2301
+ diff: ({ news, output }) => Effect36.gen(function* () {
2302
+ if (output === undefined || !isResolved8(news))
1520
2303
  return;
1521
2304
  const live = yield* readOne(news);
1522
2305
  if (live === undefined)
@@ -1525,37 +2308,37 @@ var PbsDatastoreProvider = () => Provider18.effect(PbsDatastore, Effect30.succee
1525
2308
  yield* guardBackend(live, news);
1526
2309
  return matches(live, news) ? { action: "noop" } : { action: "update" };
1527
2310
  }),
1528
- reconcile: Effect30.fn(function* ({ news }) {
2311
+ reconcile: Effect36.fn(function* ({ news }) {
1529
2312
  const live = yield* readOne(news);
1530
2313
  let upid;
1531
2314
  if (live === undefined) {
1532
- upid = yield* pve(news.target, "provision", "POST", "config/datastore", createForm3(news));
2315
+ upid = yield* pve(news.target, "provision", "POST", "config/datastore", createForm4(news));
1533
2316
  } else {
1534
2317
  yield* guardPath(live, news);
1535
2318
  yield* guardBackend(live, news);
1536
- const form = updateForm(news);
1537
- if (!matches(live, news) && Object.keys(form).length > 0) {
2319
+ const form = formToSend(matches, live, news, updateForm(news));
2320
+ if (form !== undefined) {
1538
2321
  yield* pve(news.target, "provision", "PUT", object2(news), form);
1539
2322
  }
1540
2323
  }
1541
2324
  const after = yield* settle3(readOne(news), (row) => row !== undefined && matches(row, news));
1542
2325
  if (after === undefined || !matches(after, news)) {
1543
- return yield* Effect30.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."));
2326
+ return yield* Effect36.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."));
1544
2327
  }
1545
2328
  return after;
1546
2329
  }),
1547
- delete: Effect30.fn(function* ({ olds }) {
2330
+ delete: Effect36.fn(function* ({ olds }) {
1548
2331
  yield* pve(olds.target, "provision", "DELETE", object2(olds));
1549
2332
  const left = yield* settle3(readOne(olds), (row) => row === undefined);
1550
2333
  if (left !== undefined) {
1551
- return yield* Effect30.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>\`.`));
2334
+ return yield* Effect36.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>\`.`));
1552
2335
  }
1553
2336
  })
1554
2337
  })));
1555
2338
  // src/proxmox/pbs-prune-job.ts
1556
2339
  import { Resource as Resource19 } from "alchemy";
1557
2340
  import * as Provider19 from "alchemy/Provider";
1558
- import * as Effect31 from "effect/Effect";
2341
+ import * as Effect37 from "effect/Effect";
1559
2342
 
1560
2343
  // src/proxmox/pbs-prune-job-form.ts
1561
2344
  var KEEP_KEYS = [
@@ -1630,15 +2413,15 @@ var handlers15 = pveHandlers({
1630
2413
  path: (props) => `config/prune/${props.id}`,
1631
2414
  updateForm: updateBody3
1632
2415
  });
1633
- var PbsPruneJobProvider = () => Provider19.effect(PbsPruneJob, Effect31.succeed(PbsPruneJob.Provider.of(handlers15)));
2416
+ var PbsPruneJobProvider = () => Provider19.effect(PbsPruneJob, Effect37.succeed(PbsPruneJob.Provider.of(handlers15)));
1634
2417
  // src/proxmox/pbs-sync-job.ts
1635
2418
  import { Resource as Resource20 } from "alchemy";
1636
2419
  import * as Provider20 from "alchemy/Provider";
1637
- import * as Effect32 from "effect/Effect";
2420
+ import * as Effect38 from "effect/Effect";
1638
2421
 
1639
2422
  // src/proxmox/pbs-sync-job-form.ts
1640
2423
  var UNSET4 = -1;
1641
- var UNITS = {
2424
+ var UNITS2 = {
1642
2425
  "": 1,
1643
2426
  B: 1,
1644
2427
  GB: 1e9,
@@ -1657,7 +2440,7 @@ var bytes = (value) => {
1657
2440
  const match = /^([0-9]+(?:\.[0-9]+)?)\s*([A-Za-z]*)$/.exec(raw);
1658
2441
  if (match === null)
1659
2442
  return UNSET4;
1660
- const unit = UNITS[match[2] ?? ""];
2443
+ const unit = UNITS2[match[2] ?? ""];
1661
2444
  return unit === undefined ? UNSET4 : Math.round(Number(match[1] ?? "0") * unit);
1662
2445
  };
1663
2446
  var filters = (value) => Array.isArray(value) ? value.map((entry) => String(entry)).join(" | ") : text(value);
@@ -1710,11 +2493,11 @@ var handlers16 = pveHandlers({
1710
2493
 
1711
2494
  // src/proxmox/pbs-sync-job.ts
1712
2495
  var PbsSyncJob = Resource20("Pbs.SyncJob");
1713
- var PbsSyncJobProvider = () => Provider20.effect(PbsSyncJob, Effect32.succeed(PbsSyncJob.Provider.of(handlers16)));
2496
+ var PbsSyncJobProvider = () => Provider20.effect(PbsSyncJob, Effect38.succeed(PbsSyncJob.Provider.of(handlers16)));
1714
2497
  // src/proxmox/pbs-verify-job.ts
1715
2498
  import { Resource as Resource21 } from "alchemy";
1716
2499
  import * as Provider21 from "alchemy/Provider";
1717
- import * as Effect33 from "effect/Effect";
2500
+ import * as Effect39 from "effect/Effect";
1718
2501
 
1719
2502
  // src/proxmox/pbs-verify-job-form.ts
1720
2503
  var rechecks = (ignoreVerified, outdatedAfter) => {
@@ -1758,11 +2541,11 @@ var handlers17 = pveHandlers({
1758
2541
  path: (props) => `config/verify/${props.id}`,
1759
2542
  updateForm: shape7
1760
2543
  });
1761
- var PbsVerifyJobProvider = () => Provider21.effect(PbsVerifyJob, Effect33.succeed(PbsVerifyJob.Provider.of(handlers17)));
2544
+ var PbsVerifyJobProvider = () => Provider21.effect(PbsVerifyJob, Effect39.succeed(PbsVerifyJob.Provider.of(handlers17)));
1762
2545
  // src/proxmox/pool.ts
1763
2546
  import { Resource as Resource22 } from "alchemy";
1764
2547
  import * as Provider22 from "alchemy/Provider";
1765
- import * as Effect34 from "effect/Effect";
2548
+ import * as Effect40 from "effect/Effect";
1766
2549
  var ProxmoxPool = Resource22("Proxmox.Pool");
1767
2550
  var poolCreateForm = (props) => ({
1768
2551
  comment: props.comment ?? "",
@@ -1786,11 +2569,11 @@ var handlers18 = pveHandlers({
1786
2569
  path: (props) => `pools/${props.poolid}`,
1787
2570
  updateForm: poolUpdateForm
1788
2571
  });
1789
- var ProxmoxPoolProvider = () => Provider22.effect(ProxmoxPool, Effect34.succeed(ProxmoxPool.Provider.of(handlers18)));
2572
+ var ProxmoxPoolProvider = () => Provider22.effect(ProxmoxPool, Effect40.succeed(ProxmoxPool.Provider.of(handlers18)));
1790
2573
  // src/proxmox/qemu.ts
1791
2574
  import { Resource as Resource23 } from "alchemy";
1792
2575
  import * as Provider23 from "alchemy/Provider";
1793
- import * as Effect35 from "effect/Effect";
2576
+ import * as Effect41 from "effect/Effect";
1794
2577
  var ProxmoxVm = Resource23("Proxmox.Vm");
1795
2578
  var shape8 = (props) => ({
1796
2579
  cores: String(props.cores ?? 1),
@@ -1816,11 +2599,11 @@ var handlers19 = pveHandlers({
1816
2599
  path: (props) => `nodes/${props.node}/qemu/${String(props.vmid)}/config`,
1817
2600
  updateForm: shape8
1818
2601
  });
1819
- var ProxmoxVmProvider = () => Provider23.effect(ProxmoxVm, Effect35.succeed(ProxmoxVm.Provider.of(handlers19)));
2602
+ var ProxmoxVmProvider = () => Provider23.effect(ProxmoxVm, Effect41.succeed(ProxmoxVm.Provider.of(handlers19)));
1820
2603
  // src/proxmox/replication-job.ts
1821
2604
  import { Resource as Resource24 } from "alchemy";
1822
2605
  import * as Provider24 from "alchemy/Provider";
1823
- import * as Effect36 from "effect/Effect";
2606
+ import * as Effect42 from "effect/Effect";
1824
2607
 
1825
2608
  // src/proxmox/replication-job-form.ts
1826
2609
  var DEFAULT_SCHEDULE = "*/15";
@@ -1870,11 +2653,11 @@ var handlers20 = pveHandlers({
1870
2653
  path: (props) => `cluster/replication/${jobId(props)}`,
1871
2654
  updateForm: updateBody5
1872
2655
  });
1873
- var ProxmoxReplicationJobProvider = () => Provider24.effect(ProxmoxReplicationJob, Effect36.succeed(ProxmoxReplicationJob.Provider.of(handlers20)));
2656
+ var ProxmoxReplicationJobProvider = () => Provider24.effect(ProxmoxReplicationJob, Effect42.succeed(ProxmoxReplicationJob.Provider.of(handlers20)));
1874
2657
  // src/proxmox/role.ts
1875
2658
  import { Resource as Resource25 } from "alchemy";
1876
2659
  import * as Provider25 from "alchemy/Provider";
1877
- import * as Effect37 from "effect/Effect";
2660
+ import * as Effect43 from "effect/Effect";
1878
2661
  var ProxmoxRole = Resource25("Proxmox.Role");
1879
2662
  var canonical = (privs) => [...new Set(privs.map((priv) => priv.trim()).filter((priv) => priv.length > 0))].sort();
1880
2663
  var livePrivs = (live) => {
@@ -1891,15 +2674,15 @@ var handlers21 = pveHandlers({
1891
2674
  path: (props) => `access/roles/${props.roleid}`,
1892
2675
  updateForm: (props) => ({ privs: canonical(props.privs).join(",") })
1893
2676
  });
1894
- var ProxmoxRoleProvider = () => Provider25.effect(ProxmoxRole, Effect37.succeed(ProxmoxRole.Provider.of(handlers21)));
2677
+ var ProxmoxRoleProvider = () => Provider25.effect(ProxmoxRole, Effect43.succeed(ProxmoxRole.Provider.of(handlers21)));
1895
2678
  // src/proxmox/sdn-apply.ts
1896
2679
  import { Resource as Resource26 } from "alchemy";
1897
- import { isResolved as isResolved8 } from "alchemy/Diff";
2680
+ import { isResolved as isResolved9 } from "alchemy/Diff";
1898
2681
  import * as Provider26 from "alchemy/Provider";
1899
- import * as Effect39 from "effect/Effect";
2682
+ import * as Effect45 from "effect/Effect";
1900
2683
 
1901
2684
  // src/proxmox/sdn-apply-read.ts
1902
- import * as Effect38 from "effect/Effect";
2685
+ import * as Effect44 from "effect/Effect";
1903
2686
  var MARKED = [
1904
2687
  "cluster/sdn/zones",
1905
2688
  "cluster/sdn/vnets",
@@ -1908,7 +2691,7 @@ var MARKED = [
1908
2691
  "cluster/sdn/route-maps"
1909
2692
  ];
1910
2693
  var subsystemAbsent = (error) => error instanceof PveError && error.status === 501;
1911
- var markedPending = (target) => Effect38.all(MARKED.map((collection2) => pve(target, "read", "GET", `${collection2}?pending=1`).pipe(Effect38.map((rows) => (rows ?? []).filter((row) => row.state !== undefined).length), Effect38.catchIf(subsystemAbsent, () => Effect38.succeed(0)))), { concurrency: "unbounded" }).pipe(Effect38.map((counts) => counts.reduce((total, count2) => total + count2, 0)));
2694
+ var markedPending = (target) => Effect44.all(MARKED.map((collection2) => pve(target, "read", "GET", `${collection2}?pending=1`).pipe(Effect44.map((rows) => (rows ?? []).filter((row) => row.state !== undefined).length), Effect44.catchIf(subsystemAbsent, () => Effect44.succeed(0)))), { concurrency: "unbounded" }).pipe(Effect44.map((counts) => counts.reduce((total, count2) => total + count2, 0)));
1912
2695
  var fabricStaged = (staged, running) => {
1913
2696
  if (staged === undefined && running === undefined)
1914
2697
  return 0;
@@ -1916,9 +2699,9 @@ var fabricStaged = (staged, running) => {
1916
2699
  return "inconsistent";
1917
2700
  return staged === running ? 0 : 1;
1918
2701
  };
1919
- var fabricPending = (target) => Effect38.all(["pending", "running"].map((view) => pve(target, "read", "GET", `cluster/sdn/fabrics/all?${view}=1`).pipe(Effect38.map(canonical2), Effect38.catchIf(subsystemAbsent, () => Effect38.succeed(undefined)))), { concurrency: "unbounded" }).pipe(Effect38.flatMap(([staged, running]) => {
2702
+ var fabricPending = (target) => Effect44.all(["pending", "running"].map((view) => pve(target, "read", "GET", `cluster/sdn/fabrics/all?${view}=1`).pipe(Effect44.map(canonical2), Effect44.catchIf(subsystemAbsent, () => Effect44.succeed(undefined)))), { concurrency: "unbounded" }).pipe(Effect44.flatMap(([staged, running]) => {
1920
2703
  const verdict = fabricStaged(staged, running);
1921
- return verdict === "inconsistent" ? Effect38.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.")) : Effect38.succeed(verdict);
2704
+ return verdict === "inconsistent" ? Effect44.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.")) : Effect44.succeed(verdict);
1922
2705
  }));
1923
2706
  var canonical2 = (value) => JSON.stringify(sortDeep(value));
1924
2707
  var sortDeep = (value) => {
@@ -1931,39 +2714,39 @@ var sortDeep = (value) => {
1931
2714
  }
1932
2715
  return value;
1933
2716
  };
1934
- var sdnPendingCount = (target) => Effect38.all([markedPending(target), fabricPending(target)], { concurrency: "unbounded" }).pipe(Effect38.map(([marked, fabric]) => marked + fabric));
2717
+ var sdnPendingCount = (target) => Effect44.all([markedPending(target), fabricPending(target)], { concurrency: "unbounded" }).pipe(Effect44.map(([marked, fabric]) => marked + fabric));
1935
2718
 
1936
2719
  // src/proxmox/sdn-apply.ts
1937
2720
  var ProxmoxSdnApply = Resource26("Proxmox.SdnApply");
1938
- var read2 = (props) => sdnPendingCount(props.target).pipe(Effect39.map((pending) => ({ pending })));
1939
- var ProxmoxSdnApplyProvider = () => Provider26.effect(ProxmoxSdnApply, Effect39.succeed(ProxmoxSdnApply.Provider.of({
1940
- list: () => Effect39.succeed([]),
1941
- read: Effect39.fn(function* ({ olds }) {
1942
- return yield* read2(olds);
2721
+ var read3 = (props) => sdnPendingCount(props.target).pipe(Effect45.map((pending) => ({ pending })));
2722
+ var ProxmoxSdnApplyProvider = () => Provider26.effect(ProxmoxSdnApply, Effect45.succeed(ProxmoxSdnApply.Provider.of({
2723
+ list: () => Effect45.succeed([]),
2724
+ read: Effect45.fn(function* ({ olds }) {
2725
+ return yield* read3(olds);
1943
2726
  }),
1944
- diff: Effect39.fn(function* ({ news }) {
1945
- if (!isResolved8(news))
2727
+ diff: Effect45.fn(function* ({ news }) {
2728
+ if (!isResolved9(news))
1946
2729
  return;
1947
- const { pending } = yield* read2(news);
2730
+ const { pending } = yield* read3(news);
1948
2731
  return pending === 0 ? { action: "noop" } : { action: "update" };
1949
2732
  }),
1950
- reconcile: Effect39.fn(function* ({ news }) {
1951
- const staged = yield* read2(news);
2733
+ reconcile: Effect45.fn(function* ({ news }) {
2734
+ const staged = yield* read3(news);
1952
2735
  if (staged.pending === 0)
1953
2736
  return staged;
1954
2737
  yield* pve(news.target, "provision", "PUT", "cluster/sdn");
1955
- const after = yield* read2(news);
2738
+ const after = yield* read3(news);
1956
2739
  if (after.pending !== 0) {
1957
- return yield* Effect39.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."));
2740
+ return yield* Effect45.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."));
1958
2741
  }
1959
2742
  return after;
1960
2743
  }),
1961
- delete: () => Effect39.void
2744
+ delete: () => Effect45.void
1962
2745
  })));
1963
2746
  // src/proxmox/sdn-subnet.ts
1964
2747
  import { Resource as Resource27 } from "alchemy";
1965
2748
  import * as Provider27 from "alchemy/Provider";
1966
- import * as Effect40 from "effect/Effect";
2749
+ import * as Effect46 from "effect/Effect";
1967
2750
 
1968
2751
  // src/proxmox/sdn-subnet-form.ts
1969
2752
  var subnetId = (props) => `${props.zone}-${props.cidr.replace("/", "-")}`;
@@ -1982,7 +2765,7 @@ var body5 = (props) => ({
1982
2765
  vnet: props.vnet,
1983
2766
  ...Object.fromEntries(Object.entries(optional3(props)).filter((entry) => entry[1] !== undefined))
1984
2767
  });
1985
- var createForm4 = (props) => ({
2768
+ var createForm5 = (props) => ({
1986
2769
  ...body5(props),
1987
2770
  subnet: props.cidr,
1988
2771
  type: "subnet"
@@ -2014,16 +2797,16 @@ var handlers22 = pveHandlers({
2014
2797
  };
2015
2798
  },
2016
2799
  collection: (props) => `cluster/sdn/vnets/${props.vnet}/subnets`,
2017
- createForm: createForm4,
2800
+ createForm: createForm5,
2018
2801
  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),
2019
2802
  path: (props) => `cluster/sdn/vnets/${props.vnet}/subnets/${subnetId(props)}`,
2020
2803
  updateForm: updateForm2
2021
2804
  });
2022
- var ProxmoxSdnSubnetProvider = () => Provider27.effect(ProxmoxSdnSubnet, Effect40.succeed(ProxmoxSdnSubnet.Provider.of(handlers22)));
2805
+ var ProxmoxSdnSubnetProvider = () => Provider27.effect(ProxmoxSdnSubnet, Effect46.succeed(ProxmoxSdnSubnet.Provider.of(handlers22)));
2023
2806
  // src/proxmox/sdn-vnet.ts
2024
2807
  import { Resource as Resource28 } from "alchemy";
2025
2808
  import * as Provider28 from "alchemy/Provider";
2026
- import * as Effect41 from "effect/Effect";
2809
+ import * as Effect47 from "effect/Effect";
2027
2810
  var ProxmoxSdnVnet = Resource28("Proxmox.SdnVnet");
2028
2811
  var tagOf = (value) => typeof value === "number" ? value : undefined;
2029
2812
  var shape10 = (props) => {
@@ -2060,11 +2843,11 @@ var handlers23 = pveHandlers({
2060
2843
  path: (props) => `cluster/sdn/vnets/${props.vnet}`,
2061
2844
  updateForm: updateForm3
2062
2845
  });
2063
- var ProxmoxSdnVnetProvider = () => Provider28.effect(ProxmoxSdnVnet, Effect41.succeed(ProxmoxSdnVnet.Provider.of(handlers23)));
2846
+ var ProxmoxSdnVnetProvider = () => Provider28.effect(ProxmoxSdnVnet, Effect47.succeed(ProxmoxSdnVnet.Provider.of(handlers23)));
2064
2847
  // src/proxmox/sdn-zone.ts
2065
2848
  import { Resource as Resource29 } from "alchemy";
2066
2849
  import * as Provider29 from "alchemy/Provider";
2067
- import * as Effect42 from "effect/Effect";
2850
+ import * as Effect48 from "effect/Effect";
2068
2851
  var ProxmoxSdnZone = Resource29("Proxmox.SdnZone");
2069
2852
  var sameIpam = (live, declared) => (live === "" ? "pve" : live) === (declared === undefined || declared === "" ? "pve" : declared);
2070
2853
  var body6 = (props) => ({
@@ -2116,11 +2899,11 @@ var handlers24 = pveHandlers({
2116
2899
  return withClears(fields, clear);
2117
2900
  }
2118
2901
  });
2119
- var ProxmoxSdnZoneProvider = () => Provider29.effect(ProxmoxSdnZone, Effect42.succeed(ProxmoxSdnZone.Provider.of(handlers24)));
2902
+ var ProxmoxSdnZoneProvider = () => Provider29.effect(ProxmoxSdnZone, Effect48.succeed(ProxmoxSdnZone.Provider.of(handlers24)));
2120
2903
  // src/proxmox/storage.ts
2121
2904
  import { Resource as Resource30 } from "alchemy";
2122
2905
  import * as Provider30 from "alchemy/Provider";
2123
- import * as Effect43 from "effect/Effect";
2906
+ import * as Effect49 from "effect/Effect";
2124
2907
  var ProxmoxStorage = Resource30("Proxmox.Storage", {
2125
2908
  defaultRemovalPolicy: "retain"
2126
2909
  });
@@ -2165,11 +2948,11 @@ var handlers25 = pveHandlers({
2165
2948
  path: (props) => `storage/${props.storage}`,
2166
2949
  updateForm: mutable2
2167
2950
  });
2168
- var ProxmoxStorageProvider = () => Provider30.effect(ProxmoxStorage, Effect43.succeed(ProxmoxStorage.Provider.of(handlers25)));
2951
+ var ProxmoxStorageProvider = () => Provider30.effect(ProxmoxStorage, Effect49.succeed(ProxmoxStorage.Provider.of(handlers25)));
2169
2952
  // src/proxmox/user.ts
2170
2953
  import { Resource as Resource31 } from "alchemy";
2171
2954
  import * as Provider31 from "alchemy/Provider";
2172
- import * as Effect44 from "effect/Effect";
2955
+ import * as Effect50 from "effect/Effect";
2173
2956
  var ProxmoxUser = Resource31("Proxmox.User");
2174
2957
  var str3 = (value) => typeof value === "string" ? value : "";
2175
2958
  var int2 = (value) => {
@@ -2209,15 +2992,15 @@ var handlers26 = pveHandlers({
2209
2992
  path: (props) => `access/users/${props.userid}`,
2210
2993
  updateForm: shape11
2211
2994
  });
2212
- var ProxmoxUserProvider = () => Provider31.effect(ProxmoxUser, Effect44.succeed(ProxmoxUser.Provider.of(handlers26)));
2995
+ var ProxmoxUserProvider = () => Provider31.effect(ProxmoxUser, Effect50.succeed(ProxmoxUser.Provider.of(handlers26)));
2213
2996
  // src/proxmox/zfs-pool.ts
2214
2997
  import { Resource as Resource32 } from "alchemy";
2215
2998
  import * as Provider32 from "alchemy/Provider";
2216
- import * as Effect46 from "effect/Effect";
2999
+ import * as Effect52 from "effect/Effect";
2217
3000
 
2218
3001
  // src/proxmox/zfs-pool-write.ts
2219
- import * as Effect45 from "effect/Effect";
2220
- var createForm5 = (props) => ({
3002
+ import * as Effect51 from "effect/Effect";
3003
+ var createForm6 = (props) => ({
2221
3004
  devices: props.devices.join(","),
2222
3005
  name: props.name,
2223
3006
  raidlevel: props.raidlevel,
@@ -2226,21 +3009,21 @@ var createForm5 = (props) => ({
2226
3009
  ...props["draid-config"] === undefined ? {} : { "draid-config": props["draid-config"] }
2227
3010
  });
2228
3011
  var SETTLE_ATTEMPTS = 30;
2229
- var createPool = (props, collection2, path, read3) => Effect45.gen(function* () {
2230
- const existing = yield* read3(props);
3012
+ var createPool = (props, collection2, path, read4) => Effect51.gen(function* () {
3013
+ const existing = yield* read4(props);
2231
3014
  if (existing !== undefined)
2232
3015
  return existing;
2233
- const upid = yield* pve(props.target, "provision", "POST", collection2, createForm5(props));
2234
- const settle4 = (attempts) => Effect45.gen(function* () {
2235
- const live = yield* read3(props);
3016
+ const upid = yield* pve(props.target, "provision", "POST", collection2, createForm6(props));
3017
+ const settle4 = (attempts) => Effect51.gen(function* () {
3018
+ const live = yield* read4(props);
2236
3019
  if (live !== undefined || attempts <= 0)
2237
3020
  return live;
2238
- yield* Effect45.sleep("2 seconds");
3021
+ yield* Effect51.sleep("2 seconds");
2239
3022
  return yield* settle4(attempts - 1);
2240
3023
  });
2241
3024
  const after = yield* settle4(SETTLE_ATTEMPTS);
2242
3025
  if (after === undefined) {
2243
- return yield* Effect45.die(new Error(`${path}: 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."));
3026
+ return yield* Effect51.die(new Error(`${path}: 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."));
2244
3027
  }
2245
3028
  return after;
2246
3029
  });
@@ -2268,19 +3051,19 @@ var spec2 = {
2268
3051
  };
2269
3052
  },
2270
3053
  collection: (props) => `nodes/${props.node}/disks/zfs`,
2271
- createForm: createForm5,
3054
+ createForm: createForm6,
2272
3055
  matches: () => true,
2273
3056
  path: (props) => `nodes/${props.node}/disks/zfs/${props.name}`
2274
3057
  };
2275
3058
  var ops5 = pveOperations(spec2);
2276
3059
  var handlers27 = {
2277
3060
  ...pveHandlers(spec2),
2278
- delete: Effect46.fn(function* ({ olds }) {
3061
+ delete: Effect52.fn(function* ({ olds }) {
2279
3062
  yield* destroyPool(olds, spec2.path(olds));
2280
3063
  }),
2281
3064
  reconcile: ({ news }) => createPool(news, spec2.collection(news), spec2.path(news), ops5.read)
2282
3065
  };
2283
- var ProxmoxZfsPoolProvider = () => Provider32.effect(ProxmoxZfsPool, Effect46.succeed(ProxmoxZfsPool.Provider.of(handlers27)));
3066
+ var ProxmoxZfsPoolProvider = () => Provider32.effect(ProxmoxZfsPool, Effect52.succeed(ProxmoxZfsPool.Provider.of(handlers27)));
2284
3067
  export {
2285
3068
  PbsDatastore,
2286
3069
  PbsDatastoreProvider,
@@ -2308,6 +3091,7 @@ export {
2308
3091
  ProxmoxHaResourceProvider,
2309
3092
  ProxmoxHaRule,
2310
3093
  ProxmoxHaRuleProvider,
3094
+ ProxmoxLxc,
2311
3095
  ProxmoxLxcProvider,
2312
3096
  ProxmoxMetricServer,
2313
3097
  ProxmoxMetricServerProvider,
@@ -2336,5 +3120,5 @@ export {
2336
3120
  ProxmoxZfsPoolProvider
2337
3121
  };
2338
3122
 
2339
- //# debugId=9783560E271AFF9D64756E2164756E21
3123
+ //# debugId=2E96DC8F7406C81F64756E2164756E21
2340
3124
  //# sourceMappingURL=index.js.map