@homeflare/alchemy 0.15.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (516) hide show
  1. package/README.md +29 -0
  2. package/dist/linux/directory-lifecycle.d.ts +50 -0
  3. package/dist/linux/directory-lifecycle.d.ts.map +1 -0
  4. package/dist/linux/directory.d.ts +19 -0
  5. package/dist/linux/directory.d.ts.map +1 -0
  6. package/dist/linux/fake-linux-host.d.ts +35 -0
  7. package/dist/linux/fake-linux-host.d.ts.map +1 -0
  8. package/dist/linux/fake-systemd.d.ts +34 -0
  9. package/dist/linux/fake-systemd.d.ts.map +1 -0
  10. package/dist/linux/index.d.ts +27 -0
  11. package/dist/linux/index.d.ts.map +1 -0
  12. package/dist/linux/index.js +1575 -0
  13. package/dist/linux/index.js.map +38 -0
  14. package/dist/linux/providers.d.ts +17 -0
  15. package/dist/linux/providers.d.ts.map +1 -0
  16. package/dist/linux/region.d.ts +43 -0
  17. package/dist/linux/region.d.ts.map +1 -0
  18. package/dist/linux/remote-file-form.d.ts +47 -0
  19. package/dist/linux/remote-file-form.d.ts.map +1 -0
  20. package/dist/linux/remote-file-lifecycle.d.ts +36 -0
  21. package/dist/linux/remote-file-lifecycle.d.ts.map +1 -0
  22. package/dist/linux/remote-file-plan.d.ts +33 -0
  23. package/dist/linux/remote-file-plan.d.ts.map +1 -0
  24. package/dist/linux/remote-file.d.ts +28 -0
  25. package/dist/linux/remote-file.d.ts.map +1 -0
  26. package/dist/linux/ssh-command.d.ts +68 -0
  27. package/dist/linux/ssh-command.d.ts.map +1 -0
  28. package/dist/linux/ssh-runner.d.ts +22 -0
  29. package/dist/linux/ssh-runner.d.ts.map +1 -0
  30. package/dist/linux/ssh-scripts.d.ts +27 -0
  31. package/dist/linux/ssh-scripts.d.ts.map +1 -0
  32. package/dist/linux/systemctl.d.ts +67 -0
  33. package/dist/linux/systemctl.d.ts.map +1 -0
  34. package/dist/linux/unit-form.d.ts +78 -0
  35. package/dist/linux/unit-form.d.ts.map +1 -0
  36. package/dist/linux/unit-handlers.d.ts +15 -0
  37. package/dist/linux/unit-handlers.d.ts.map +1 -0
  38. package/dist/linux/unit-lifecycle.d.ts +40 -0
  39. package/dist/linux/unit-lifecycle.d.ts.map +1 -0
  40. package/dist/linux/unit-preflight.d.ts +41 -0
  41. package/dist/linux/unit-preflight.d.ts.map +1 -0
  42. package/dist/linux/unit-settle.d.ts +31 -0
  43. package/dist/linux/unit-settle.d.ts.map +1 -0
  44. package/dist/linux/unit.d.ts +35 -0
  45. package/dist/linux/unit.d.ts.map +1 -0
  46. package/dist/netbox/client.d.ts +52 -0
  47. package/dist/netbox/client.d.ts.map +1 -0
  48. package/dist/netbox/constraint-guard.d.ts +26 -0
  49. package/dist/netbox/constraint-guard.d.ts.map +1 -0
  50. package/dist/netbox/constraints.d.ts +76 -0
  51. package/dist/netbox/constraints.d.ts.map +1 -0
  52. package/dist/netbox/generated/constraints/index.d.ts +19 -0
  53. package/dist/netbox/generated/constraints/index.d.ts.map +1 -0
  54. package/dist/netbox/generated/constraints/netbox-ipam.d.ts +21 -0
  55. package/dist/netbox/generated/constraints/netbox-ipam.d.ts.map +1 -0
  56. package/dist/netbox/index.d.ts +17 -0
  57. package/dist/netbox/index.d.ts.map +1 -0
  58. package/dist/netbox/index.js +331 -0
  59. package/dist/netbox/index.js.map +18 -0
  60. package/dist/netbox/prefix-form.d.ts +54 -0
  61. package/dist/netbox/prefix-form.d.ts.map +1 -0
  62. package/dist/netbox/prefix.d.ts +80 -0
  63. package/dist/netbox/prefix.d.ts.map +1 -0
  64. package/dist/netbox/resource.d.ts +94 -0
  65. package/dist/netbox/resource.d.ts.map +1 -0
  66. package/dist/netbox/values.d.ts +24 -0
  67. package/dist/netbox/values.d.ts.map +1 -0
  68. package/dist/proxmox/api-token-form.d.ts.map +1 -1
  69. package/dist/proxmox/apply-endpoints.d.ts +30 -0
  70. package/dist/proxmox/apply-endpoints.d.ts.map +1 -0
  71. package/dist/proxmox/ceph-daemon-form.d.ts +29 -1
  72. package/dist/proxmox/ceph-daemon-form.d.ts.map +1 -1
  73. package/dist/proxmox/ceph-daemon.d.ts.map +1 -1
  74. package/dist/proxmox/ceph-flag.d.ts.map +1 -1
  75. package/dist/proxmox/ceph-fs.d.ts.map +1 -1
  76. package/dist/proxmox/ceph-osd-write.d.ts +19 -0
  77. package/dist/proxmox/ceph-osd-write.d.ts.map +1 -1
  78. package/dist/proxmox/ceph-pool.d.ts.map +1 -1
  79. package/dist/proxmox/constraints.d.ts +28 -5
  80. package/dist/proxmox/constraints.d.ts.map +1 -1
  81. package/dist/proxmox/generated/constraints/index.d.ts +1 -1
  82. package/dist/proxmox/generated/constraints/index.d.ts.map +1 -1
  83. package/dist/proxmox/generated/constraints/pbs-config.d.ts +6 -1
  84. package/dist/proxmox/generated/constraints/pbs-config.d.ts.map +1 -1
  85. package/dist/proxmox/generated/constraints/pve-access.d.ts +6 -1
  86. package/dist/proxmox/generated/constraints/pve-access.d.ts.map +1 -1
  87. package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts +23 -0
  88. package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts.map +1 -0
  89. package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts +23 -0
  90. package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts.map +1 -0
  91. package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts +23 -0
  92. package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts.map +1 -0
  93. package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts +23 -0
  94. package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts.map +1 -0
  95. package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts +23 -0
  96. package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts.map +1 -0
  97. package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts +23 -0
  98. package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts.map +1 -0
  99. package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts +23 -0
  100. package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts.map +1 -0
  101. package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts +23 -0
  102. package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts.map +1 -0
  103. package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts +23 -0
  104. package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts.map +1 -0
  105. package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts +23 -0
  106. package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts.map +1 -0
  107. package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts +23 -0
  108. package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts.map +1 -0
  109. package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts +23 -0
  110. package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts.map +1 -0
  111. package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts +23 -0
  112. package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts.map +1 -0
  113. package/dist/proxmox/generated/constraints/pve-pools.d.ts +6 -1
  114. package/dist/proxmox/generated/constraints/pve-pools.d.ts.map +1 -1
  115. package/dist/proxmox/generated/constraints/pve-storage.d.ts +6 -1
  116. package/dist/proxmox/generated/constraints/pve-storage.d.ts.map +1 -1
  117. package/dist/proxmox/generated/pbs/access-users.d.ts +137 -0
  118. package/dist/proxmox/generated/pbs/access-users.d.ts.map +1 -0
  119. package/dist/proxmox/generated/pbs/access.d.ts +171 -0
  120. package/dist/proxmox/generated/pbs/access.d.ts.map +1 -0
  121. package/dist/proxmox/generated/pbs/admin-datastore-store.d.ts +252 -0
  122. package/dist/proxmox/generated/pbs/admin-datastore-store.d.ts.map +1 -0
  123. package/dist/proxmox/generated/pbs/admin-metrics.d.ts +147 -0
  124. package/dist/proxmox/generated/pbs/admin-metrics.d.ts.map +1 -0
  125. package/dist/proxmox/generated/pbs/admin.d.ts +217 -0
  126. package/dist/proxmox/generated/pbs/admin.d.ts.map +1 -0
  127. package/dist/proxmox/generated/pbs/backup.d.ts +114 -0
  128. package/dist/proxmox/generated/pbs/backup.d.ts.map +1 -0
  129. package/dist/proxmox/generated/pbs/config-access-pam.d.ts +218 -0
  130. package/dist/proxmox/generated/pbs/config-access-pam.d.ts.map +1 -0
  131. package/dist/proxmox/generated/pbs/config-access.d.ts +203 -0
  132. package/dist/proxmox/generated/pbs/config-access.d.ts.map +1 -0
  133. package/dist/proxmox/generated/pbs/config-datastore.d.ts +195 -0
  134. package/dist/proxmox/generated/pbs/config-datastore.d.ts.map +1 -0
  135. package/dist/proxmox/generated/pbs/config-media-pool.d.ts +207 -0
  136. package/dist/proxmox/generated/pbs/config-media-pool.d.ts.map +1 -0
  137. package/dist/proxmox/generated/pbs/config-notifications-matchers.d.ts +159 -0
  138. package/dist/proxmox/generated/pbs/config-notifications-matchers.d.ts.map +1 -0
  139. package/dist/proxmox/generated/pbs/config-notifications.d.ts +199 -0
  140. package/dist/proxmox/generated/pbs/config-notifications.d.ts.map +1 -0
  141. package/dist/proxmox/generated/pbs/config-remote.d.ts +175 -0
  142. package/dist/proxmox/generated/pbs/config-remote.d.ts.map +1 -0
  143. package/dist/proxmox/generated/pbs/config-sync.d.ts +145 -0
  144. package/dist/proxmox/generated/pbs/config-sync.d.ts.map +1 -0
  145. package/dist/proxmox/generated/pbs/config-tape.d.ts +200 -0
  146. package/dist/proxmox/generated/pbs/config-tape.d.ts.map +1 -0
  147. package/dist/proxmox/generated/pbs/config-verify.d.ts +77 -0
  148. package/dist/proxmox/generated/pbs/config-verify.d.ts.map +1 -0
  149. package/dist/proxmox/generated/pbs/config.d.ts +119 -0
  150. package/dist/proxmox/generated/pbs/config.d.ts.map +1 -0
  151. package/dist/proxmox/generated/pbs/nodes-node-dns.d.ts +178 -0
  152. package/dist/proxmox/generated/pbs/nodes-node-dns.d.ts.map +1 -0
  153. package/dist/proxmox/generated/pbs/nodes-node-services.d.ts +205 -0
  154. package/dist/proxmox/generated/pbs/nodes-node-services.d.ts.map +1 -0
  155. package/dist/proxmox/generated/pbs/nodes-node.d.ts +166 -0
  156. package/dist/proxmox/generated/pbs/nodes-node.d.ts.map +1 -0
  157. package/dist/proxmox/generated/pbs/nodes.d.ts +171 -0
  158. package/dist/proxmox/generated/pbs/nodes.d.ts.map +1 -0
  159. package/dist/proxmox/generated/pbs/ping.d.ts +18 -0
  160. package/dist/proxmox/generated/pbs/ping.d.ts.map +1 -0
  161. package/dist/proxmox/generated/pbs/pull.d.ts +37 -0
  162. package/dist/proxmox/generated/pbs/pull.d.ts.map +1 -0
  163. package/dist/proxmox/generated/pbs/push.d.ts +36 -0
  164. package/dist/proxmox/generated/pbs/push.d.ts.map +1 -0
  165. package/dist/proxmox/generated/pbs/reader.d.ts +41 -0
  166. package/dist/proxmox/generated/pbs/reader.d.ts.map +1 -0
  167. package/dist/proxmox/generated/pbs/root.d.ts +16 -0
  168. package/dist/proxmox/generated/pbs/root.d.ts.map +1 -0
  169. package/dist/proxmox/generated/pbs/status.d.ts +54 -0
  170. package/dist/proxmox/generated/pbs/status.d.ts.map +1 -0
  171. package/dist/proxmox/generated/pbs/tape-drive.d.ts +181 -0
  172. package/dist/proxmox/generated/pbs/tape-drive.d.ts.map +1 -0
  173. package/dist/proxmox/generated/pbs/tape-media.d.ts +126 -0
  174. package/dist/proxmox/generated/pbs/tape-media.d.ts.map +1 -0
  175. package/dist/proxmox/generated/pbs/tape.d.ts +90 -0
  176. package/dist/proxmox/generated/pbs/tape.d.ts.map +1 -0
  177. package/dist/proxmox/generated/pbs/version.d.ts +20 -0
  178. package/dist/proxmox/generated/pbs/version.d.ts.map +1 -0
  179. package/dist/proxmox/generated/pbs.d.ts +44 -662
  180. package/dist/proxmox/generated/pbs.d.ts.map +1 -1
  181. package/dist/proxmox/generated/pve/access-roles.d.ts +164 -0
  182. package/dist/proxmox/generated/pve/access-roles.d.ts.map +1 -0
  183. package/dist/proxmox/generated/pve/access-users.d.ts +152 -0
  184. package/dist/proxmox/generated/pve/access-users.d.ts.map +1 -0
  185. package/dist/proxmox/generated/pve/access.d.ts +221 -0
  186. package/dist/proxmox/generated/pve/access.d.ts.map +1 -0
  187. package/dist/proxmox/generated/pve/cluster-acme-plugins-id.d.ts +45 -0
  188. package/dist/proxmox/generated/pve/cluster-acme-plugins-id.d.ts.map +1 -0
  189. package/dist/proxmox/generated/pve/cluster-acme-plugins.d.ts +47 -0
  190. package/dist/proxmox/generated/pve/cluster-acme-plugins.d.ts.map +1 -0
  191. package/dist/proxmox/generated/pve/cluster-acme-tos.d.ts +240 -0
  192. package/dist/proxmox/generated/pve/cluster-acme-tos.d.ts.map +1 -0
  193. package/dist/proxmox/generated/pve/cluster-bulk-action.d.ts +200 -0
  194. package/dist/proxmox/generated/pve/cluster-bulk-action.d.ts.map +1 -0
  195. package/dist/proxmox/generated/pve/cluster-firewall-refs.d.ts +109 -0
  196. package/dist/proxmox/generated/pve/cluster-firewall-refs.d.ts.map +1 -0
  197. package/dist/proxmox/generated/pve/cluster-firewall.d.ts +225 -0
  198. package/dist/proxmox/generated/pve/cluster-firewall.d.ts.map +1 -0
  199. package/dist/proxmox/generated/pve/cluster-ha.d.ts +199 -0
  200. package/dist/proxmox/generated/pve/cluster-ha.d.ts.map +1 -0
  201. package/dist/proxmox/generated/pve/cluster-jobs.d.ts +183 -0
  202. package/dist/proxmox/generated/pve/cluster-jobs.d.ts.map +1 -0
  203. package/dist/proxmox/generated/pve/cluster-metrics.d.ts +110 -0
  204. package/dist/proxmox/generated/pve/cluster-metrics.d.ts.map +1 -0
  205. package/dist/proxmox/generated/pve/cluster-notifications-endpoints.d.ts +224 -0
  206. package/dist/proxmox/generated/pve/cluster-notifications-endpoints.d.ts.map +1 -0
  207. package/dist/proxmox/generated/pve/cluster-notifications.d.ts +91 -0
  208. package/dist/proxmox/generated/pve/cluster-notifications.d.ts.map +1 -0
  209. package/dist/proxmox/generated/pve/cluster-options.d.ts +85 -0
  210. package/dist/proxmox/generated/pve/cluster-options.d.ts.map +1 -0
  211. package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models-cputype.d.ts +48 -0
  212. package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models-cputype.d.ts.map +1 -0
  213. package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models.d.ts +45 -0
  214. package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models.d.ts.map +1 -0
  215. package/dist/proxmox/generated/pve/cluster-replication.d.ts +130 -0
  216. package/dist/proxmox/generated/pve/cluster-replication.d.ts.map +1 -0
  217. package/dist/proxmox/generated/pve/cluster-sdn-fabrics.d.ts +220 -0
  218. package/dist/proxmox/generated/pve/cluster-sdn-fabrics.d.ts.map +1 -0
  219. package/dist/proxmox/generated/pve/cluster-sdn-ipams.d.ts +229 -0
  220. package/dist/proxmox/generated/pve/cluster-sdn-ipams.d.ts.map +1 -0
  221. package/dist/proxmox/generated/pve/cluster-sdn-vnets-vnet.d.ts +229 -0
  222. package/dist/proxmox/generated/pve/cluster-sdn-vnets-vnet.d.ts.map +1 -0
  223. package/dist/proxmox/generated/pve/cluster-sdn-vnets.d.ts +51 -0
  224. package/dist/proxmox/generated/pve/cluster-sdn-vnets.d.ts.map +1 -0
  225. package/dist/proxmox/generated/pve/cluster-sdn-zones.d.ts +225 -0
  226. package/dist/proxmox/generated/pve/cluster-sdn-zones.d.ts.map +1 -0
  227. package/dist/proxmox/generated/pve/cluster-sdn.d.ts +202 -0
  228. package/dist/proxmox/generated/pve/cluster-sdn.d.ts.map +1 -0
  229. package/dist/proxmox/generated/pve/cluster.d.ts +69 -0
  230. package/dist/proxmox/generated/pve/cluster.d.ts.map +1 -0
  231. package/dist/proxmox/generated/pve/nodes-node-ceph.d.ts +167 -0
  232. package/dist/proxmox/generated/pve/nodes-node-ceph.d.ts.map +1 -0
  233. package/dist/proxmox/generated/pve/nodes-node-config.d.ts +43 -0
  234. package/dist/proxmox/generated/pve/nodes-node-config.d.ts.map +1 -0
  235. package/dist/proxmox/generated/pve/nodes-node-disks.d.ts +225 -0
  236. package/dist/proxmox/generated/pve/nodes-node-disks.d.ts.map +1 -0
  237. package/dist/proxmox/generated/pve/nodes-node-firewall.d.ts +145 -0
  238. package/dist/proxmox/generated/pve/nodes-node-firewall.d.ts.map +1 -0
  239. package/dist/proxmox/generated/pve/nodes-node-hardware.d.ts +195 -0
  240. package/dist/proxmox/generated/pve/nodes-node-hardware.d.ts.map +1 -0
  241. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-firewall.d.ts +191 -0
  242. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-firewall.d.ts.map +1 -0
  243. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-move-volume.d.ts +33 -0
  244. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-move-volume.d.ts.map +1 -0
  245. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-mtunnel.d.ts +56 -0
  246. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-mtunnel.d.ts.map +1 -0
  247. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-resize.d.ts +28 -0
  248. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-resize.d.ts.map +1 -0
  249. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-rrd.d.ts +186 -0
  250. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-rrd.d.ts.map +1 -0
  251. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid.d.ts +191 -0
  252. package/dist/proxmox/generated/pve/nodes-node-lxc-vmid.d.ts.map +1 -0
  253. package/dist/proxmox/generated/pve/nodes-node-network.d.ts +150 -0
  254. package/dist/proxmox/generated/pve/nodes-node-network.d.ts.map +1 -0
  255. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-config.d.ts +301 -0
  256. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-config.d.ts.map +1 -0
  257. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-dbus-vmstate.d.ts +170 -0
  258. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-dbus-vmstate.d.ts.map +1 -0
  259. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-firewall-rules.d.ts +144 -0
  260. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-firewall-rules.d.ts.map +1 -0
  261. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-move-disk.d.ts +34 -0
  262. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-move-disk.d.ts.map +1 -0
  263. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-mtunnel.d.ts +137 -0
  264. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-mtunnel.d.ts.map +1 -0
  265. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-status.d.ts +179 -0
  266. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-status.d.ts.map +1 -0
  267. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid.d.ts +143 -0
  268. package/dist/proxmox/generated/pve/nodes-node-qemu-vmid.d.ts.map +1 -0
  269. package/dist/proxmox/generated/pve/nodes-node-qemu.d.ts +158 -0
  270. package/dist/proxmox/generated/pve/nodes-node-qemu.d.ts.map +1 -0
  271. package/dist/proxmox/generated/pve/nodes-node-replication.d.ts +187 -0
  272. package/dist/proxmox/generated/pve/nodes-node-replication.d.ts.map +1 -0
  273. package/dist/proxmox/generated/pve/nodes-node-services.d.ts +115 -0
  274. package/dist/proxmox/generated/pve/nodes-node-services.d.ts.map +1 -0
  275. package/dist/proxmox/generated/pve/nodes-node-storage-storage.d.ts +202 -0
  276. package/dist/proxmox/generated/pve/nodes-node-storage-storage.d.ts.map +1 -0
  277. package/dist/proxmox/generated/pve/nodes-node-storage.d.ts +40 -0
  278. package/dist/proxmox/generated/pve/nodes-node-storage.d.ts.map +1 -0
  279. package/dist/proxmox/generated/pve/nodes-node-subscription.d.ts +173 -0
  280. package/dist/proxmox/generated/pve/nodes-node-subscription.d.ts.map +1 -0
  281. package/dist/proxmox/generated/pve/nodes-node-vzdump.d.ts +97 -0
  282. package/dist/proxmox/generated/pve/nodes-node-vzdump.d.ts.map +1 -0
  283. package/dist/proxmox/generated/pve/nodes-node.d.ts +207 -0
  284. package/dist/proxmox/generated/pve/nodes-node.d.ts.map +1 -0
  285. package/dist/proxmox/generated/pve/nodes.d.ts +236 -0
  286. package/dist/proxmox/generated/pve/nodes.d.ts.map +1 -0
  287. package/dist/proxmox/generated/pve/pools.d.ts +82 -0
  288. package/dist/proxmox/generated/pve/pools.d.ts.map +1 -0
  289. package/dist/proxmox/generated/pve/storage.d.ts +159 -0
  290. package/dist/proxmox/generated/pve/storage.d.ts.map +1 -0
  291. package/dist/proxmox/generated/pve/version.d.ts +21 -0
  292. package/dist/proxmox/generated/pve/version.d.ts.map +1 -0
  293. package/dist/proxmox/generated/pve.d.ts +69 -4766
  294. package/dist/proxmox/generated/pve.d.ts.map +1 -1
  295. package/dist/proxmox/index.js +996 -372
  296. package/dist/proxmox/index.js.map +48 -32
  297. package/dist/proxmox/lxc-lifecycle.d.ts.map +1 -1
  298. package/dist/proxmox/metric-server-form.d.ts +11 -0
  299. package/dist/proxmox/metric-server-form.d.ts.map +1 -1
  300. package/dist/proxmox/metric-server.d.ts.map +1 -1
  301. package/dist/proxmox/network-apply.d.ts.map +1 -1
  302. package/dist/proxmox/node-network-form.d.ts +15 -0
  303. package/dist/proxmox/node-network-form.d.ts.map +1 -1
  304. package/dist/proxmox/node-network.d.ts.map +1 -1
  305. package/dist/proxmox/notification-target-endpoint.d.ts +26 -0
  306. package/dist/proxmox/notification-target-endpoint.d.ts.map +1 -0
  307. package/dist/proxmox/notification-target-form.d.ts +7 -0
  308. package/dist/proxmox/notification-target-form.d.ts.map +1 -1
  309. package/dist/proxmox/notification-target.d.ts.map +1 -1
  310. package/dist/proxmox/pbs-notification-target-endpoint.d.ts +29 -0
  311. package/dist/proxmox/pbs-notification-target-endpoint.d.ts.map +1 -0
  312. package/dist/proxmox/pbs-notification-target-lifecycle.d.ts.map +1 -1
  313. package/dist/proxmox/qemu.d.ts +10 -0
  314. package/dist/proxmox/qemu.d.ts.map +1 -1
  315. package/dist/proxmox/resource-guard.d.ts +15 -0
  316. package/dist/proxmox/resource-guard.d.ts.map +1 -0
  317. package/dist/proxmox/resource-spec.d.ts +15 -4
  318. package/dist/proxmox/resource-spec.d.ts.map +1 -1
  319. package/dist/proxmox/resource.d.ts +8 -0
  320. package/dist/proxmox/resource.d.ts.map +1 -1
  321. package/dist/proxmox/sdn-apply.d.ts.map +1 -1
  322. package/dist/proxmox/sdn-subnet.d.ts.map +1 -1
  323. package/dist/proxmox/zfs-pool-write.d.ts +11 -0
  324. package/dist/proxmox/zfs-pool-write.d.ts.map +1 -1
  325. package/dist/proxmox/zfs-pool.d.ts.map +1 -1
  326. package/docs/linux-host-measured.md +56 -0
  327. package/docs/linux-host.md +192 -0
  328. package/docs/netbox.md +103 -0
  329. package/package.json +9 -1
  330. package/src/linux/directory-lifecycle.ts +220 -0
  331. package/src/linux/directory.test.ts +132 -0
  332. package/src/linux/directory.ts +71 -0
  333. package/src/linux/fake-linux-host.ts +197 -0
  334. package/src/linux/fake-systemd.ts +103 -0
  335. package/src/linux/index.ts +32 -0
  336. package/src/linux/providers.ts +26 -0
  337. package/src/linux/region.test.ts +89 -0
  338. package/src/linux/region.ts +118 -0
  339. package/src/linux/remote-file-form.ts +96 -0
  340. package/src/linux/remote-file-lifecycle.test.ts +191 -0
  341. package/src/linux/remote-file-lifecycle.ts +161 -0
  342. package/src/linux/remote-file-plan.ts +154 -0
  343. package/src/linux/remote-file.ts +88 -0
  344. package/src/linux/ssh-command.test.ts +140 -0
  345. package/src/linux/ssh-command.ts +99 -0
  346. package/src/linux/ssh-runner.ts +188 -0
  347. package/src/linux/ssh-scripts.ts +105 -0
  348. package/src/linux/systemctl.ts +145 -0
  349. package/src/linux/unit-form.ts +183 -0
  350. package/src/linux/unit-handlers.ts +73 -0
  351. package/src/linux/unit-lifecycle.test.ts +220 -0
  352. package/src/linux/unit-lifecycle.ts +175 -0
  353. package/src/linux/unit-preflight.test.ts +128 -0
  354. package/src/linux/unit-preflight.ts +100 -0
  355. package/src/linux/unit-settle.ts +110 -0
  356. package/src/linux/unit.ts +81 -0
  357. package/src/netbox/client.test.ts +67 -0
  358. package/src/netbox/client.ts +146 -0
  359. package/src/netbox/constraint-guard.ts +57 -0
  360. package/src/netbox/constraints.test.ts +114 -0
  361. package/src/netbox/constraints.ts +152 -0
  362. package/src/netbox/generated/constraints/index.ts +23 -0
  363. package/src/netbox/generated/constraints/netbox-ipam.ts +32 -0
  364. package/src/netbox/index.ts +34 -0
  365. package/src/netbox/prefix-form.test.ts +127 -0
  366. package/src/netbox/prefix-form.ts +84 -0
  367. package/src/netbox/prefix.ts +136 -0
  368. package/src/netbox/resource.ts +208 -0
  369. package/src/netbox/values.test.ts +66 -0
  370. package/src/netbox/values.ts +40 -0
  371. package/src/proxmox/api-token-form.ts +5 -0
  372. package/src/proxmox/apply-endpoints.ts +34 -0
  373. package/src/proxmox/ceph-daemon-form.ts +56 -2
  374. package/src/proxmox/ceph-daemon.ts +10 -21
  375. package/src/proxmox/ceph-flag.ts +1 -0
  376. package/src/proxmox/ceph-fs.ts +1 -0
  377. package/src/proxmox/ceph-osd-write.ts +30 -4
  378. package/src/proxmox/ceph-pool.ts +10 -0
  379. package/src/proxmox/constraints-dialect.test.ts +165 -0
  380. package/src/proxmox/constraints-forms.test.ts +228 -0
  381. package/src/proxmox/constraints-host-forms.test.ts +132 -0
  382. package/src/proxmox/constraints-live.test.ts +155 -0
  383. package/src/proxmox/constraints-node-forms.test.ts +197 -0
  384. package/src/proxmox/constraints-required.test.ts +172 -0
  385. package/src/proxmox/constraints.test.ts +8 -63
  386. package/src/proxmox/constraints.ts +31 -6
  387. package/src/proxmox/generated/constraints/index.ts +27 -3
  388. package/src/proxmox/generated/constraints/pbs-config.ts +63 -1
  389. package/src/proxmox/generated/constraints/pve-access.ts +15 -3
  390. package/src/proxmox/generated/constraints/pve-cluster-backup.ts +74 -0
  391. package/src/proxmox/generated/constraints/pve-cluster-ceph.ts +27 -0
  392. package/src/proxmox/generated/constraints/pve-cluster-firewall.ts +35 -0
  393. package/src/proxmox/generated/constraints/pve-cluster-ha.ts +55 -0
  394. package/src/proxmox/generated/constraints/pve-cluster-metrics.ts +59 -0
  395. package/src/proxmox/generated/constraints/pve-cluster-notifications.ts +80 -0
  396. package/src/proxmox/generated/constraints/pve-cluster-replication.ts +43 -0
  397. package/src/proxmox/generated/constraints/pve-cluster-sdn.ts +90 -0
  398. package/src/proxmox/generated/constraints/pve-nodes-ceph.ts +57 -0
  399. package/src/proxmox/generated/constraints/pve-nodes-disks.ts +31 -0
  400. package/src/proxmox/generated/constraints/pve-nodes-lxc.ts +82 -0
  401. package/src/proxmox/generated/constraints/pve-nodes-network.ts +77 -0
  402. package/src/proxmox/generated/constraints/pve-nodes-qemu.ts +125 -0
  403. package/src/proxmox/generated/constraints/pve-pools.ts +6 -1
  404. package/src/proxmox/generated/constraints/pve-storage.ts +8 -3
  405. package/src/proxmox/generated/pbs/access-users.ts +137 -0
  406. package/src/proxmox/generated/pbs/access.ts +232 -0
  407. package/src/proxmox/generated/pbs/admin-datastore-store.ts +238 -0
  408. package/src/proxmox/generated/pbs/admin-metrics.ts +147 -0
  409. package/src/proxmox/generated/pbs/admin.ts +225 -0
  410. package/src/proxmox/generated/pbs/backup.ts +121 -0
  411. package/src/proxmox/generated/pbs/config-access-pam.ts +228 -0
  412. package/src/proxmox/generated/pbs/config-access.ts +228 -0
  413. package/src/proxmox/generated/pbs/config-datastore.ts +217 -0
  414. package/src/proxmox/generated/pbs/config-media-pool.ts +232 -0
  415. package/src/proxmox/generated/pbs/config-notifications-matchers.ts +187 -0
  416. package/src/proxmox/generated/pbs/config-notifications.ts +221 -0
  417. package/src/proxmox/generated/pbs/config-remote.ts +192 -0
  418. package/src/proxmox/generated/pbs/config-sync.ts +167 -0
  419. package/src/proxmox/generated/pbs/config-tape.ts +226 -0
  420. package/src/proxmox/generated/pbs/config-verify.ts +87 -0
  421. package/src/proxmox/generated/pbs/config.ts +135 -0
  422. package/src/proxmox/generated/pbs/nodes-node-dns.ts +229 -0
  423. package/src/proxmox/generated/pbs/nodes-node-services.ts +181 -0
  424. package/src/proxmox/generated/pbs/nodes-node.ts +244 -0
  425. package/src/proxmox/generated/pbs/nodes.ts +160 -0
  426. package/src/proxmox/generated/pbs/ping.ts +16 -0
  427. package/src/proxmox/generated/pbs/pull.ts +37 -0
  428. package/src/proxmox/generated/pbs/push.ts +36 -0
  429. package/src/proxmox/generated/pbs/reader.ts +41 -0
  430. package/src/proxmox/generated/pbs/root.ts +16 -0
  431. package/src/proxmox/generated/pbs/status.ts +50 -0
  432. package/src/proxmox/generated/pbs/tape-drive.ts +220 -0
  433. package/src/proxmox/generated/pbs/tape-media.ts +133 -0
  434. package/src/proxmox/generated/pbs/tape.ts +92 -0
  435. package/src/proxmox/generated/pbs/version.ts +16 -0
  436. package/src/proxmox/generated/pbs.ts +44 -787
  437. package/src/proxmox/generated/pve/access-roles.ts +168 -0
  438. package/src/proxmox/generated/pve/access-users.ts +155 -0
  439. package/src/proxmox/generated/pve/access.ts +217 -0
  440. package/src/proxmox/generated/pve/cluster-acme-plugins-id.ts +367 -0
  441. package/src/proxmox/generated/pve/cluster-acme-plugins.ts +366 -0
  442. package/src/proxmox/generated/pve/cluster-acme-tos.ts +232 -0
  443. package/src/proxmox/generated/pve/cluster-bulk-action.ts +227 -0
  444. package/src/proxmox/generated/pve/cluster-firewall-refs.ts +110 -0
  445. package/src/proxmox/generated/pve/cluster-firewall.ts +236 -0
  446. package/src/proxmox/generated/pve/cluster-ha.ts +203 -0
  447. package/src/proxmox/generated/pve/cluster-jobs.ts +190 -0
  448. package/src/proxmox/generated/pve/cluster-metrics.ts +116 -0
  449. package/src/proxmox/generated/pve/cluster-notifications-endpoints.ts +244 -0
  450. package/src/proxmox/generated/pve/cluster-notifications.ts +99 -0
  451. package/src/proxmox/generated/pve/cluster-options.ts +189 -0
  452. package/src/proxmox/generated/pve/cluster-qemu-custom-cpu-models-cputype.ts +272 -0
  453. package/src/proxmox/generated/pve/cluster-qemu-custom-cpu-models.ts +268 -0
  454. package/src/proxmox/generated/pve/cluster-replication.ts +127 -0
  455. package/src/proxmox/generated/pve/cluster-sdn-fabrics.ts +218 -0
  456. package/src/proxmox/generated/pve/cluster-sdn-ipams.ts +229 -0
  457. package/src/proxmox/generated/pve/cluster-sdn-vnets-vnet.ts +245 -0
  458. package/src/proxmox/generated/pve/cluster-sdn-vnets.ts +49 -0
  459. package/src/proxmox/generated/pve/cluster-sdn-zones.ts +224 -0
  460. package/src/proxmox/generated/pve/cluster-sdn.ts +201 -0
  461. package/src/proxmox/generated/pve/cluster.ts +71 -0
  462. package/src/proxmox/generated/pve/nodes-node-ceph.ts +167 -0
  463. package/src/proxmox/generated/pve/nodes-node-config.ts +56 -0
  464. package/src/proxmox/generated/pve/nodes-node-disks.ts +237 -0
  465. package/src/proxmox/generated/pve/nodes-node-firewall.ts +239 -0
  466. package/src/proxmox/generated/pve/nodes-node-hardware.ts +215 -0
  467. package/src/proxmox/generated/pve/nodes-node-lxc-vmid-firewall.ts +232 -0
  468. package/src/proxmox/generated/pve/nodes-node-lxc-vmid-move-volume.ts +1059 -0
  469. package/src/proxmox/generated/pve/nodes-node-lxc-vmid-mtunnel.ts +49 -0
  470. package/src/proxmox/generated/pve/nodes-node-lxc-vmid-resize.ts +285 -0
  471. package/src/proxmox/generated/pve/nodes-node-lxc-vmid-rrd.ts +184 -0
  472. package/src/proxmox/generated/pve/nodes-node-lxc-vmid.ts +240 -0
  473. package/src/proxmox/generated/pve/nodes-node-network.ts +234 -0
  474. package/src/proxmox/generated/pve/nodes-node-qemu-vmid-config.ts +441 -0
  475. package/src/proxmox/generated/pve/nodes-node-qemu-vmid-dbus-vmstate.ts +215 -0
  476. package/src/proxmox/generated/pve/nodes-node-qemu-vmid-firewall-rules.ts +145 -0
  477. package/src/proxmox/generated/pve/nodes-node-qemu-vmid-move-disk.ts +664 -0
  478. package/src/proxmox/generated/pve/nodes-node-qemu-vmid-mtunnel.ts +188 -0
  479. package/src/proxmox/generated/pve/nodes-node-qemu-vmid-status.ts +233 -0
  480. package/src/proxmox/generated/pve/nodes-node-qemu-vmid.ts +187 -0
  481. package/src/proxmox/generated/pve/nodes-node-qemu.ts +204 -0
  482. package/src/proxmox/generated/pve/nodes-node-replication.ts +187 -0
  483. package/src/proxmox/generated/pve/nodes-node-services.ts +221 -0
  484. package/src/proxmox/generated/pve/nodes-node-storage-storage.ts +226 -0
  485. package/src/proxmox/generated/pve/nodes-node-storage.ts +40 -0
  486. package/src/proxmox/generated/pve/nodes-node-subscription.ts +168 -0
  487. package/src/proxmox/generated/pve/nodes-node-vzdump.ts +96 -0
  488. package/src/proxmox/generated/pve/nodes-node.ts +211 -0
  489. package/src/proxmox/generated/pve/nodes.ts +235 -0
  490. package/src/proxmox/generated/pve/pools.ts +78 -0
  491. package/src/proxmox/generated/pve/storage.ts +213 -0
  492. package/src/proxmox/generated/pve/version.ts +21 -0
  493. package/src/proxmox/generated/pve.ts +69 -7403
  494. package/src/proxmox/lxc-interior.test.ts +17 -1
  495. package/src/proxmox/lxc-lifecycle.ts +48 -9
  496. package/src/proxmox/metric-server-form.ts +10 -0
  497. package/src/proxmox/metric-server.ts +11 -2
  498. package/src/proxmox/network-apply.ts +4 -0
  499. package/src/proxmox/node-network-form.ts +19 -0
  500. package/src/proxmox/node-network.ts +9 -3
  501. package/src/proxmox/notification-target-endpoint.ts +46 -0
  502. package/src/proxmox/notification-target-form.ts +11 -0
  503. package/src/proxmox/notification-target.ts +5 -2
  504. package/src/proxmox/pbs-notification-target-endpoint.ts +45 -0
  505. package/src/proxmox/pbs-notification-target-lifecycle.ts +16 -14
  506. package/src/proxmox/qemu.ts +9 -1
  507. package/src/proxmox/resource-guard.ts +61 -0
  508. package/src/proxmox/resource-spec.ts +16 -4
  509. package/src/proxmox/resource.ts +27 -27
  510. package/src/proxmox/sdn-apply.ts +4 -0
  511. package/src/proxmox/sdn-subnet.ts +5 -0
  512. package/src/proxmox/zfs-pool-write.ts +12 -0
  513. package/src/proxmox/zfs-pool.ts +2 -1
  514. package/dist/proxmox/generated/constraints/pve-cluster.d.ts +0 -18
  515. package/dist/proxmox/generated/constraints/pve-cluster.d.ts.map +0 -1
  516. package/src/proxmox/generated/constraints/pve-cluster.ts +0 -174
@@ -0,0 +1,191 @@
1
+ /**
2
+ * RemoteFile against the fake Linux host (no ssh, no real writes): whole-file mode, region mode,
3
+ * drift detection and the ownership refusals.
4
+ */
5
+ import { describe, expect, test } from 'bun:test';
6
+ import { fakeLinuxHost } from './fake-linux-host.ts';
7
+ import { fileProblems } from './remote-file-form.ts';
8
+ import {
9
+ deleteFile,
10
+ diffFile,
11
+ readFileAttributes,
12
+ reconcileFile,
13
+ } from './remote-file-lifecycle.ts';
14
+
15
+ const PATH = '/etc/example/app.conf';
16
+ const props = { content: 'listen = 127.0.0.1:9000\n', path: PATH };
17
+ const decode = (bytes: Uint8Array | undefined) => new TextDecoder().decode(bytes);
18
+
19
+ const host = (euid = 0) =>
20
+ fakeLinuxHost({
21
+ dirs: { '/etc': 0, '/etc/example': 0, '/home/someone': 1001 },
22
+ euid,
23
+ groups: { root: 0, staff: 50 },
24
+ users: {
25
+ root: { gid: 0, home: '/root', uid: 0 },
26
+ someone: { gid: 1001, home: '/home/someone', uid: 1001 },
27
+ },
28
+ });
29
+
30
+ const writes = (calls: string[][]) => calls.filter((call) => call[0] === 'write').length;
31
+
32
+ describe('whole-file mode', () => {
33
+ test('writes with the default mode and returns what is on the host', async () => {
34
+ const fake = host();
35
+ const attrs = await reconcileFile(fake.runner, props);
36
+ expect(attrs).toMatchObject({ mode: 0o644, path: PATH, size: props.content.length, uid: 0 });
37
+ expect(decode(fake.files.get(PATH)?.bytes)).toBe(props.content);
38
+ });
39
+
40
+ test('an already-matching file is not rewritten', async () => {
41
+ const fake = host();
42
+ const first = await reconcileFile(fake.runner, props);
43
+ await reconcileFile(fake.runner, props, first);
44
+ expect(writes(fake.calls)).toBe(1);
45
+ });
46
+
47
+ test('mode and owner drift is detected, not just content', async () => {
48
+ const fake = host();
49
+ const output = await reconcileFile(fake.runner, { ...props, mode: 0o640 });
50
+ expect(await diffFile(fake.runner, { ...props, mode: 0o640 }, output)).toEqual({
51
+ action: 'noop',
52
+ });
53
+ // A hand `chmod` on the host, with the content untouched.
54
+ const entry = fake.files.get(PATH);
55
+ if (entry !== undefined) entry.mode = 0o666;
56
+ expect(await diffFile(fake.runner, { ...props, mode: 0o640 }, output)).toEqual({
57
+ action: 'update',
58
+ });
59
+ const fixed = await reconcileFile(fake.runner, { ...props, mode: 0o640 }, output);
60
+ expect(fixed.mode).toBe(0o640);
61
+ });
62
+
63
+ test('owner and group resolve by name, and a foreign owner needs root', async () => {
64
+ const fake = host();
65
+ const owned = await reconcileFile(fake.runner, { ...props, group: 'staff', owner: 'someone' });
66
+ expect(owned).toMatchObject({ gid: 50, uid: 1001 });
67
+ const unprivileged = host(1001);
68
+ await expect(
69
+ reconcileFile(unprivileged.runner, { ...props, owner: 'root', path: '/home/someone/x' }),
70
+ ).rejects.toThrow(/only root may chown/);
71
+ });
72
+
73
+ test('a file that is not this resource is refused unless adoption is on', async () => {
74
+ const fake = host();
75
+ await fake.runner.writeFileAtomic(PATH, new TextEncoder().encode('someone else\n'), {
76
+ mode: 0o644,
77
+ });
78
+ await expect(reconcileFile(fake.runner, props)).rejects.toThrow(/deploy --adopt/);
79
+ const adopted = await reconcileFile(fake.runner, props, undefined, true);
80
+ expect(adopted.sha256).not.toBe('');
81
+ });
82
+
83
+ test('delete removes the file and is idempotent', async () => {
84
+ const fake = host();
85
+ const attrs = await reconcileFile(fake.runner, props);
86
+ await deleteFile(fake.runner, attrs);
87
+ await deleteFile(fake.runner, attrs);
88
+ expect(fake.files.has(PATH)).toBe(false);
89
+ });
90
+ });
91
+
92
+ describe('region mode', () => {
93
+ const VENDOR = '# vendor ruleset\nanchor "system"\nrule one\n';
94
+ const region = { name: 'homeflare' };
95
+ const block = { content: 'anchor "declared"\n', path: '/etc/vendor.conf', region };
96
+
97
+ const withVendor = () => {
98
+ const fake = host();
99
+ fake.files.set('/etc/vendor.conf', {
100
+ bytes: new TextEncoder().encode(VENDOR),
101
+ gid: 0,
102
+ kind: 'file',
103
+ mode: 0o600,
104
+ uid: 0,
105
+ });
106
+ return fake;
107
+ };
108
+
109
+ test('editing the block leaves the rest of the file byte-identical', async () => {
110
+ const fake = withVendor();
111
+ const output = await reconcileFile(fake.runner, block);
112
+ const after = decode(fake.files.get('/etc/vendor.conf')?.bytes);
113
+ expect(after.startsWith(VENDOR)).toBe(true);
114
+ await reconcileFile(fake.runner, { ...block, content: 'anchor "changed"\n' }, output);
115
+ expect(decode(fake.files.get('/etc/vendor.conf')?.bytes).startsWith(VENDOR)).toBe(true);
116
+ });
117
+
118
+ test("the other owner's mode and uid are preserved, never re-declared", async () => {
119
+ const fake = withVendor();
120
+ await reconcileFile(fake.runner, block);
121
+ expect(fake.files.get('/etc/vendor.conf')?.mode).toBe(0o600);
122
+ });
123
+
124
+ test('a change anywhere else in the file is not this resource’s drift', async () => {
125
+ const fake = withVendor();
126
+ const output = await reconcileFile(fake.runner, block);
127
+ const edited = `${decode(fake.files.get('/etc/vendor.conf')?.bytes)}rule two\n`;
128
+ fake.files.set('/etc/vendor.conf', {
129
+ bytes: new TextEncoder().encode(edited),
130
+ gid: 0,
131
+ kind: 'file',
132
+ mode: 0o600,
133
+ uid: 0,
134
+ });
135
+ expect(await diffFile(fake.runner, block, output)).toEqual({ action: 'noop' });
136
+ });
137
+
138
+ test('delete removes only the block', async () => {
139
+ const fake = withVendor();
140
+ const output = await reconcileFile(fake.runner, block);
141
+ await deleteFile(fake.runner, output);
142
+ expect(decode(fake.files.get('/etc/vendor.conf')?.bytes)).toBe(VENDOR);
143
+ });
144
+
145
+ test('a missing file is a refusal, because a typo is likelier than an intention', async () => {
146
+ const fake = host();
147
+ await expect(
148
+ reconcileFile(fake.runner, { ...block, path: '/etc/example/absent.conf' }),
149
+ ).rejects.toThrow(/does not create the file it lives in/);
150
+ const created = await reconcileFile(fake.runner, {
151
+ ...block,
152
+ create: true,
153
+ path: '/etc/example/absent.conf',
154
+ });
155
+ expect(created.region).toEqual(region);
156
+ });
157
+
158
+ test('mode and owner without create are refused at validation', () => {
159
+ expect(fileProblems({ ...block, mode: 0o600 })).toHaveLength(1);
160
+ expect(fileProblems({ ...block, create: true, mode: 0o600 })).toEqual([]);
161
+ });
162
+
163
+ test('a block already there with other content needs --adopt', async () => {
164
+ const fake = withVendor();
165
+ fake.files.set('/etc/vendor.conf', {
166
+ bytes: new TextEncoder().encode(
167
+ `${VENDOR}# BEGIN homeflare\nsomething else\n# END homeflare\n`,
168
+ ),
169
+ gid: 0,
170
+ kind: 'file',
171
+ mode: 0o600,
172
+ uid: 0,
173
+ });
174
+ await expect(reconcileFile(fake.runner, block)).rejects.toThrow(/managed region/);
175
+ await expect(reconcileFile(fake.runner, block, undefined, true)).resolves.toBeDefined();
176
+ });
177
+ });
178
+
179
+ describe('a failed read refuses', () => {
180
+ test('a stat that throws is never read as “nothing is there”', async () => {
181
+ const fake = host();
182
+ const broken = {
183
+ ...fake.runner,
184
+ stat: async () => {
185
+ throw new Error('ssh somewhere: the remote command did not report a status');
186
+ },
187
+ };
188
+ await expect(reconcileFile(broken, props)).rejects.toThrow(/did not report a status/);
189
+ await expect(readFileAttributes(broken, PATH, undefined)).rejects.toThrow(/did not report/);
190
+ });
191
+ });
@@ -0,0 +1,161 @@
1
+ /**
2
+ * RemoteFile's diff / reconcile / delete over a HostRunner, so the whole lifecycle runs against a
3
+ * fake host in tests with no ssh and no real write. What it would write is worked out next door
4
+ * (remote-file-plan.ts); this file is the part that decides to act.
5
+ *
6
+ * ⛔ IN REGION MODE, DRIFT MEANS THE REGION AND NOTHING ELSE. Every byte outside the markers belongs
7
+ * to another owner, so a change there must never plan an update — otherwise the two owners fight
8
+ * on every deploy and this resource loses the right to be in that file at all.
9
+ * ⚠️ THE WRITE REPLACES THE INODE, in both modes. A process holding the old file keeps reading it
10
+ * until it reopens, and a hard link to the path stops tracking. That is the price of never
11
+ * leaving a torn file, and it is the trade the launchd family already makes.
12
+ */
13
+ import type { Diff } from 'alchemy/Diff';
14
+ import { sha256Hex } from '../launchd/job-form.ts';
15
+ import { type HostRunner, canActAsRoot } from '../launchd/runner.ts';
16
+ import { removeRegion } from './region.ts';
17
+ import {
18
+ DEFAULT_MODE,
19
+ type RemoteFileAttributes,
20
+ type RemoteFileProps,
21
+ } from './remote-file-form.ts';
22
+ import {
23
+ decoder,
24
+ digestOf,
25
+ encoder,
26
+ identity,
27
+ ownedDigest,
28
+ planWrite,
29
+ readFileAttributes,
30
+ refuse,
31
+ } from './remote-file-plan.ts';
32
+
33
+ export { readFileAttributes } from './remote-file-plan.ts';
34
+
35
+ /**
36
+ * ★ A PLAN THAT WILL WRITE ASKS THE RUNNER FIRST (`checkWrite`, when it has one), so a runner's own
37
+ * refusals fail the plan instead of arriving halfway through the apply.
38
+ */
39
+ export const diffFile = async (
40
+ runner: HostRunner,
41
+ news: RemoteFileProps,
42
+ output: RemoteFileAttributes,
43
+ ): Promise<Diff> => {
44
+ const want = await identity(runner, news);
45
+ if (news.path !== output.path) {
46
+ // ★ Create-before-delete: two paths hold two files at once, so nothing forces deleteFirst.
47
+ await runner.checkWrite?.(news.path, { mode: news.mode ?? DEFAULT_MODE, ...want });
48
+ return { action: 'replace' };
49
+ }
50
+ const planned = await planWrite(runner, news, want);
51
+ // ⛔ BOTH digests: the live one catches a hand edit, the STORED one catches a write that landed
52
+ // and whose deploy then died before committing. Either alone reports `noop` over a wrong file.
53
+ if (planned.converged && output.contentSha256 === digestOf(news.content)) {
54
+ return { action: 'noop' };
55
+ }
56
+ await runner.checkWrite?.(news.path, planned.write);
57
+ return { action: 'update' };
58
+ };
59
+
60
+ /**
61
+ * `adopt` is whether adoption is on AND this apply is a create or an unfinished generation of our
62
+ * own (ownership/adopt.ts). It lets such a generation take over what is already at the path — the
63
+ * takeover the plan's probe would have allowed, had it run.
64
+ * ★ IN REGION MODE THE FILE BEING THERE IS NOT A TAKEOVER: it belongs to someone else by
65
+ * definition. What this resource claims is the block, so only a block already present with other
66
+ * content is the thing adoption is asked about.
67
+ */
68
+ export const reconcileFile = async (
69
+ runner: HostRunner,
70
+ props: RemoteFileProps,
71
+ output?: RemoteFileAttributes,
72
+ adopt = false,
73
+ ): Promise<RemoteFileAttributes> => {
74
+ const want = await identity(runner, props);
75
+ // ⛔ NO SILENT SUDO: handing a file to another user is root's call (chown(2)), so say so up front.
76
+ if (want.uid !== undefined && want.uid !== runner.effectiveUid() && !canActAsRoot(runner)) {
77
+ throw refuse(
78
+ props.path,
79
+ `owner ${String(props.owner)} is not the deploying user; only root may chown. Deploy through ` +
80
+ 'a root destination or a privileged HostRunner. This provider never calls sudo itself.',
81
+ );
82
+ }
83
+ const moved = output !== undefined && output.path !== props.path;
84
+ const prior = moved ? undefined : output;
85
+ const planned = await planWrite(runner, props, want);
86
+ const live = ownedDigest(planned.before, props.region);
87
+ const declared = digestOf(props.content);
88
+ /**
89
+ * ⛔ SOMETHING WE DO NOT OWN IS NEVER OVERWRITTEN. With no prior state the engine's probe has
90
+ * already refused whatever it found — except where it never looked: a replace's new path, and a
91
+ * create whose props still held an Output at plan time. A path typo there would otherwise
92
+ * overwrite a system file. ★ With adoption on, a create takes it over, as the probe would have.
93
+ */
94
+ if (
95
+ prior === undefined &&
96
+ live !== undefined &&
97
+ live !== declared &&
98
+ !(adopt && output === undefined)
99
+ ) {
100
+ throw refuse(
101
+ props.path,
102
+ `${props.region === undefined ? 'already exists' : 'already carries that managed region'}` +
103
+ ' and is not this resource. Remove it, or declare it as a new resource and deploy --adopt.',
104
+ );
105
+ }
106
+ if (!planned.converged) await runner.writeFileAtomic(props.path, planned.bytes, planned.write);
107
+ /**
108
+ * ⚠️ READ BACK, never echo the declaration: a umask, an ACL or a runner that ignored `uid` shows
109
+ * up here as a refusal instead of as a forever-`update`.
110
+ */
111
+ const after = await readFileAttributes(runner, props.path, props.region);
112
+ if (
113
+ after === undefined ||
114
+ after.sha256 !== sha256Hex(planned.bytes) ||
115
+ after.contentSha256 !== declared
116
+ ) {
117
+ /**
118
+ * ⚠️ Roll back a CREATE only: left behind, the next plan's recovery read finds a file with no
119
+ * state, reports it Unowned, and every later deploy demands --adopt for our own file.
120
+ * ⛔ NEVER IN REGION MODE — the file was someone else's before this deploy and must survive it.
121
+ */
122
+ if (planned.before === undefined && props.region === undefined) {
123
+ await runner.removeFile(props.path).catch(() => undefined);
124
+ }
125
+ throw refuse(
126
+ props.path,
127
+ 'the write returned but the file on disk does not match the declaration',
128
+ );
129
+ }
130
+ // ★ Create-before-delete, as the replace would have been: the old path goes once the new one is
131
+ // written and verified.
132
+ if (moved && output !== undefined) await deleteFile(runner, output);
133
+ return after;
134
+ };
135
+
136
+ /**
137
+ * Whole-file mode removes the file. ⛔ REGION MODE REMOVES ONLY THE BLOCK, leaving every other byte
138
+ * identical — removing the file would delete another owner's configuration. Idempotent both ways.
139
+ */
140
+ export const deleteFile = async (
141
+ runner: HostRunner,
142
+ output: RemoteFileAttributes,
143
+ ): Promise<void> => {
144
+ const stat = await runner.stat(output.path);
145
+ if (stat === undefined) return;
146
+ if (stat.kind !== 'file') throw refuse(output.path, `is now a ${stat.kind}; not touching it`);
147
+ if (output.region === undefined) {
148
+ await runner.removeFile(output.path);
149
+ return;
150
+ }
151
+ const bytes = await runner.readFile(output.path);
152
+ if (bytes === undefined) return;
153
+ const before = decoder.decode(bytes);
154
+ const next = removeRegion(before, output.region);
155
+ if (next === before) return;
156
+ await runner.writeFileAtomic(output.path, encoder.encode(next), {
157
+ gid: stat.gid,
158
+ mode: stat.mode,
159
+ uid: stat.uid,
160
+ });
161
+ };
@@ -0,0 +1,154 @@
1
+ /**
2
+ * What RemoteFile would write, worked out from what is on the host right now — the half of the
3
+ * lifecycle that reads and decides, with no write in it.
4
+ *
5
+ * ⛔ IN REGION MODE THE LIVE FILE IS AN INPUT, not merely something to compare against: the declared
6
+ * block is spliced into the bytes that are there, so every surrounding line survives verbatim.
7
+ * That is why this cannot be a pure function of the props the way a whole-file render is.
8
+ */
9
+ import { sha256Hex } from '../launchd/job-form.ts';
10
+ import type { FileStat, HostRunner, WriteOptions } from '../launchd/runner.ts';
11
+ import { type RegionSpec, readRegion, spliceRegion } from './region.ts';
12
+ import {
13
+ DEFAULT_MODE,
14
+ type RemoteFileAttributes,
15
+ type RemoteFileProps,
16
+ fileProblems,
17
+ } from './remote-file-form.ts';
18
+
19
+ export const decoder = new TextDecoder();
20
+ export const encoder = new TextEncoder();
21
+
22
+ export const refuse = (path: string, message: string): Error =>
23
+ new Error(`Remote.File ${path}: ${message}`);
24
+
25
+ const numericId = (value: string | number): number | undefined =>
26
+ typeof value === 'number' ? value : /^\d+$/.test(value) ? Number(value) : undefined;
27
+
28
+ export type Identity = { readonly uid?: number; readonly gid?: number };
29
+
30
+ /** Validate, then resolve owner and group names to ids through the runner. */
31
+ export const identity = async (runner: HostRunner, props: RemoteFileProps): Promise<Identity> => {
32
+ const found = fileProblems(props);
33
+ if (found.length > 0) throw refuse(props.path, found.join('; '));
34
+ let uid: number | undefined;
35
+ let gid: number | undefined;
36
+ if (props.owner !== undefined) {
37
+ uid = numericId(props.owner) ?? (await runner.lookupUser(String(props.owner)))?.uid;
38
+ if (uid === undefined) throw refuse(props.path, `no user ${String(props.owner)} on this host`);
39
+ }
40
+ if (props.group !== undefined) {
41
+ gid = numericId(props.group) ?? (await runner.lookupGroup(String(props.group)));
42
+ if (gid === undefined) throw refuse(props.path, `no group ${String(props.group)} on this host`);
43
+ }
44
+ return { ...(uid === undefined ? {} : { uid }), ...(gid === undefined ? {} : { gid }) };
45
+ };
46
+
47
+ export const digestOf = (text: string): string => sha256Hex(encoder.encode(text));
48
+
49
+ /** The digest of the part this resource owns, or `undefined` when that part is not there. */
50
+ export const ownedDigest = (
51
+ file: string | undefined,
52
+ region: RegionSpec | undefined,
53
+ ): string | undefined => {
54
+ if (file === undefined) return undefined;
55
+ if (region === undefined) return digestOf(file);
56
+ const body = readRegion(file, region);
57
+ return body === undefined ? undefined : digestOf(body);
58
+ };
59
+
60
+ /** The file as it is now, plus the digest of the part this resource owns. */
61
+ export const readFileAttributes = async (
62
+ runner: HostRunner,
63
+ path: string,
64
+ region: RegionSpec | undefined,
65
+ ): Promise<RemoteFileAttributes | undefined> => {
66
+ const stat = await runner.stat(path);
67
+ if (stat === undefined) return undefined;
68
+ const bytes = stat.kind === 'file' ? await runner.readFile(path) : undefined;
69
+ const text = bytes === undefined ? undefined : decoder.decode(bytes);
70
+ return {
71
+ contentSha256: ownedDigest(text, region) ?? '',
72
+ gid: stat.gid,
73
+ mode: stat.mode,
74
+ path,
75
+ ...(region === undefined ? {} : { region }),
76
+ sha256: bytes === undefined ? '' : sha256Hex(bytes),
77
+ size: stat.size,
78
+ uid: stat.uid,
79
+ };
80
+ };
81
+
82
+ const matches = (stat: FileStat | undefined, write: WriteOptions): boolean =>
83
+ stat !== undefined &&
84
+ stat.mode === write.mode &&
85
+ (write.uid === undefined || stat.uid === write.uid) &&
86
+ (write.gid === undefined || stat.gid === write.gid);
87
+
88
+ export type Plan = {
89
+ /** The whole file's new bytes, in both modes. */
90
+ readonly bytes: Uint8Array;
91
+ readonly write: WriteOptions;
92
+ /** The bytes on disk right now, or `undefined` when nothing is there. */
93
+ readonly before: string | undefined;
94
+ /** Nothing to do: the file already says what we declare, with the mode and owner we declare. */
95
+ readonly converged: boolean;
96
+ };
97
+
98
+ /**
99
+ * ⛔ A SYMLINK OR A DIRECTORY IS REFUSED, NOT REPLACED. The write is a rename(2), which over a
100
+ * symlink replaces the LINK — quietly taking the path from whatever put it there (a config
101
+ * manager's `/etc` entries are symlinks, and so is `/etc/resolv.conf` on most Linux hosts).
102
+ */
103
+ export const planWrite = async (
104
+ runner: HostRunner,
105
+ props: RemoteFileProps,
106
+ want: Identity,
107
+ ): Promise<Plan> => {
108
+ const stat = await runner.stat(props.path);
109
+ if (stat !== undefined && stat.kind !== 'file') {
110
+ throw refuse(
111
+ props.path,
112
+ `is a ${stat.kind}; remove it from whatever owns it before declaring it here`,
113
+ );
114
+ }
115
+ const current = stat === undefined ? undefined : await runner.readFile(props.path);
116
+ const before = current === undefined ? undefined : decoder.decode(current);
117
+ const declaredMode = props.mode ?? DEFAULT_MODE;
118
+ if (props.region === undefined) {
119
+ const write: WriteOptions = { mode: declaredMode, ...want };
120
+ return {
121
+ before,
122
+ bytes: encoder.encode(props.content),
123
+ converged: before === props.content && matches(stat, write),
124
+ write,
125
+ };
126
+ }
127
+ if (before === undefined) {
128
+ if (props.create !== true) {
129
+ throw refuse(
130
+ props.path,
131
+ 'does not exist, and a managed region does not create the file it lives in. Check the ' +
132
+ 'path, declare the file itself, or pass create: true.',
133
+ );
134
+ }
135
+ return {
136
+ before,
137
+ bytes: encoder.encode(spliceRegion('', props.region, props.content)),
138
+ converged: false,
139
+ write: { mode: declaredMode, ...want },
140
+ };
141
+ }
142
+ const next = spliceRegion(before, props.region, props.content);
143
+ /**
144
+ * ⛔ THE FILE'S OWN MODE AND OWNER ARE COPIED BACK, never re-declared. We borrowed a block; the
145
+ * rename would otherwise hand a file someone else owns a mode and an owner this stack chose,
146
+ * which is a second claim nobody made.
147
+ */
148
+ return {
149
+ before,
150
+ bytes: encoder.encode(next),
151
+ converged: next === before,
152
+ write: { gid: stat?.gid ?? 0, mode: stat?.mode ?? declaredMode, uid: stat?.uid ?? 0 },
153
+ };
154
+ };
@@ -0,0 +1,88 @@
1
+ /**
2
+ * `Remote.File` — one text file on a Linux host, declared: content, mode, owner, group — or one
3
+ * MANAGED BLOCK inside a file this resource does not own.
4
+ *
5
+ * - create / update — atomic write (stage beside the target, chown, chmod, rename), skipped when
6
+ * the host already says what we declare; then read back and compared.
7
+ * - read — lstat plus the SHA-256 of the file and of the part this resource owns.
8
+ * - diff — the declared digest against the stored AND the on-disk one, plus mode and owner.
9
+ * - replace — only when `path` changes; create-before-delete.
10
+ * - delete — removes the file, or in region mode only the block.
11
+ *
12
+ * ⛔ NEVER A SECRET — see remote-file-form.ts.
13
+ * ⚠️ NOTHING IS ADOPTED WITHOUT `--adopt` (docs/ownership.md): a file already at the path, or a
14
+ * block already carrying the markers, reads as `Unowned`.
15
+ * ★ IT IS A SEPARATE RESOURCE FROM `Host.File`, not a flag on it: the two have different attributes
16
+ * (a region name that a delete cannot work without) and different promises about the rest of the
17
+ * file, and folding them together would make every existing Mac HostFile's state shape change.
18
+ */
19
+ import { Resource } from 'alchemy';
20
+ import { Unowned } from 'alchemy/AdoptPolicy';
21
+ import { isResolved } from 'alchemy/Diff';
22
+ import * as Provider from 'alchemy/Provider';
23
+ import * as Effect from 'effect/Effect';
24
+ import { lift, resolvedString } from '../launchd/host-effect.ts';
25
+ import { HostRunnerService } from '../launchd/runner.ts';
26
+ import { adoptsAtApply } from '../ownership/adopt.ts';
27
+ import { noteUnfinished } from '../ownership/resume.ts';
28
+ import type { RemoteFileAttributes, RemoteFileProps } from './remote-file-form.ts';
29
+ import {
30
+ deleteFile,
31
+ diffFile,
32
+ readFileAttributes,
33
+ reconcileFile,
34
+ } from './remote-file-lifecycle.ts';
35
+
36
+ export type { RemoteFileAttributes, RemoteFileProps } from './remote-file-form.ts';
37
+
38
+ export interface RemoteFile extends Resource<
39
+ 'Remote.File',
40
+ RemoteFileProps,
41
+ RemoteFileAttributes
42
+ > {}
43
+
44
+ export const RemoteFile = Resource<RemoteFile>('Remote.File');
45
+
46
+ export const RemoteFileProvider = () =>
47
+ Provider.effect(
48
+ RemoteFile,
49
+ Effect.gen(function* () {
50
+ const runner = yield* HostRunnerService;
51
+ return RemoteFile.Provider.of({
52
+ /** ⛔ A host's files are not a list of things this stack owns. */
53
+ list: () => Effect.succeed([]),
54
+
55
+ read: ({ olds, output }) =>
56
+ lift(async () => {
57
+ const found = await readFileAttributes(runner, olds.path, olds.region);
58
+ if (found === undefined) return undefined;
59
+ // ⛔ In region mode an existing file is NOT this resource: only the block is. A file
60
+ // with no such block reads as nothing, so the create writes the block instead of
61
+ // demanding --adopt for a file it was never claiming.
62
+ if (olds.region !== undefined && found.contentSha256 === '') return undefined;
63
+ return output === undefined ? Unowned(found) : found;
64
+ }),
65
+
66
+ diff: ({ instanceId, news, output }) => {
67
+ // ★ An unfinished generation of our own: `--adopt` may resume it (ownership/resume.ts).
68
+ if (output === undefined) return noteUnfinished(instanceId);
69
+ if (isResolved(news)) return lift(() => diffFile(runner, news, output));
70
+ // ⛔ A new path is a replace even while `content` is unresolved (an Output templated in):
71
+ // the engine's default would be an update, which writes the new path and never removes
72
+ // the old one.
73
+ const path = resolvedString(news, 'path');
74
+ return Effect.succeed(
75
+ path !== undefined && path !== output.path ? { action: 'replace' as const } : undefined,
76
+ );
77
+ },
78
+
79
+ // ★ `--adopt` reaches the apply too: the probe never ran for a create with an Output prop.
80
+ reconcile: ({ fqn, instanceId, news, output }) =>
81
+ Effect.flatMap(adoptsAtApply({ fqn, instanceId, output }), (adopt) =>
82
+ lift(() => reconcileFile(runner, news, output, adopt)),
83
+ ),
84
+
85
+ delete: ({ output }) => lift(() => deleteFile(runner, output)),
86
+ });
87
+ }),
88
+ );