@homeflare/alchemy 0.15.0 → 0.19.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.
- package/README.md +29 -0
- package/dist/linux/directory-lifecycle.d.ts +50 -0
- package/dist/linux/directory-lifecycle.d.ts.map +1 -0
- package/dist/linux/directory.d.ts +19 -0
- package/dist/linux/directory.d.ts.map +1 -0
- package/dist/linux/fake-linux-host.d.ts +35 -0
- package/dist/linux/fake-linux-host.d.ts.map +1 -0
- package/dist/linux/fake-systemd.d.ts +34 -0
- package/dist/linux/fake-systemd.d.ts.map +1 -0
- package/dist/linux/index.d.ts +27 -0
- package/dist/linux/index.d.ts.map +1 -0
- package/dist/linux/index.js +1554 -0
- package/dist/linux/index.js.map +37 -0
- package/dist/linux/providers.d.ts +17 -0
- package/dist/linux/providers.d.ts.map +1 -0
- package/dist/linux/region.d.ts +43 -0
- package/dist/linux/region.d.ts.map +1 -0
- package/dist/linux/remote-file-form.d.ts +47 -0
- package/dist/linux/remote-file-form.d.ts.map +1 -0
- package/dist/linux/remote-file-lifecycle.d.ts +36 -0
- package/dist/linux/remote-file-lifecycle.d.ts.map +1 -0
- package/dist/linux/remote-file-plan.d.ts +33 -0
- package/dist/linux/remote-file-plan.d.ts.map +1 -0
- package/dist/linux/remote-file.d.ts +28 -0
- package/dist/linux/remote-file.d.ts.map +1 -0
- package/dist/linux/ssh-command.d.ts +68 -0
- package/dist/linux/ssh-command.d.ts.map +1 -0
- package/dist/linux/ssh-runner.d.ts +22 -0
- package/dist/linux/ssh-runner.d.ts.map +1 -0
- package/dist/linux/ssh-scripts.d.ts +27 -0
- package/dist/linux/ssh-scripts.d.ts.map +1 -0
- package/dist/linux/systemctl.d.ts +67 -0
- package/dist/linux/systemctl.d.ts.map +1 -0
- package/dist/linux/unit-form.d.ts +78 -0
- package/dist/linux/unit-form.d.ts.map +1 -0
- package/dist/linux/unit-handlers.d.ts +15 -0
- package/dist/linux/unit-handlers.d.ts.map +1 -0
- package/dist/linux/unit-lifecycle.d.ts +44 -0
- package/dist/linux/unit-lifecycle.d.ts.map +1 -0
- package/dist/linux/unit-settle.d.ts +31 -0
- package/dist/linux/unit-settle.d.ts.map +1 -0
- package/dist/linux/unit.d.ts +35 -0
- package/dist/linux/unit.d.ts.map +1 -0
- package/dist/netbox/client.d.ts +52 -0
- package/dist/netbox/client.d.ts.map +1 -0
- package/dist/netbox/constraint-guard.d.ts +26 -0
- package/dist/netbox/constraint-guard.d.ts.map +1 -0
- package/dist/netbox/constraints.d.ts +76 -0
- package/dist/netbox/constraints.d.ts.map +1 -0
- package/dist/netbox/generated/constraints/index.d.ts +19 -0
- package/dist/netbox/generated/constraints/index.d.ts.map +1 -0
- package/dist/netbox/generated/constraints/netbox-ipam.d.ts +21 -0
- package/dist/netbox/generated/constraints/netbox-ipam.d.ts.map +1 -0
- package/dist/netbox/index.d.ts +17 -0
- package/dist/netbox/index.d.ts.map +1 -0
- package/dist/netbox/index.js +331 -0
- package/dist/netbox/index.js.map +18 -0
- package/dist/netbox/prefix-form.d.ts +54 -0
- package/dist/netbox/prefix-form.d.ts.map +1 -0
- package/dist/netbox/prefix.d.ts +80 -0
- package/dist/netbox/prefix.d.ts.map +1 -0
- package/dist/netbox/resource.d.ts +94 -0
- package/dist/netbox/resource.d.ts.map +1 -0
- package/dist/netbox/values.d.ts +24 -0
- package/dist/netbox/values.d.ts.map +1 -0
- package/dist/proxmox/api-token-form.d.ts.map +1 -1
- package/dist/proxmox/apply-endpoints.d.ts +30 -0
- package/dist/proxmox/apply-endpoints.d.ts.map +1 -0
- package/dist/proxmox/ceph-daemon-form.d.ts +29 -1
- package/dist/proxmox/ceph-daemon-form.d.ts.map +1 -1
- package/dist/proxmox/ceph-daemon.d.ts.map +1 -1
- package/dist/proxmox/ceph-flag.d.ts.map +1 -1
- package/dist/proxmox/ceph-fs.d.ts.map +1 -1
- package/dist/proxmox/ceph-osd-write.d.ts +19 -0
- package/dist/proxmox/ceph-osd-write.d.ts.map +1 -1
- package/dist/proxmox/ceph-pool.d.ts.map +1 -1
- package/dist/proxmox/constraints.d.ts +28 -5
- package/dist/proxmox/constraints.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/index.d.ts +1 -1
- package/dist/proxmox/generated/constraints/index.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/pbs-config.d.ts +6 -1
- package/dist/proxmox/generated/constraints/pbs-config.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/pve-access.d.ts +6 -1
- package/dist/proxmox/generated/constraints/pve-access.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-pools.d.ts +6 -1
- package/dist/proxmox/generated/constraints/pve-pools.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/pve-storage.d.ts +6 -1
- package/dist/proxmox/generated/constraints/pve-storage.d.ts.map +1 -1
- package/dist/proxmox/generated/pbs/access-users.d.ts +137 -0
- package/dist/proxmox/generated/pbs/access-users.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/access.d.ts +171 -0
- package/dist/proxmox/generated/pbs/access.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/admin-datastore-store.d.ts +252 -0
- package/dist/proxmox/generated/pbs/admin-datastore-store.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/admin-metrics.d.ts +147 -0
- package/dist/proxmox/generated/pbs/admin-metrics.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/admin.d.ts +217 -0
- package/dist/proxmox/generated/pbs/admin.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/backup.d.ts +114 -0
- package/dist/proxmox/generated/pbs/backup.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-access-pam.d.ts +218 -0
- package/dist/proxmox/generated/pbs/config-access-pam.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-access.d.ts +203 -0
- package/dist/proxmox/generated/pbs/config-access.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-datastore.d.ts +195 -0
- package/dist/proxmox/generated/pbs/config-datastore.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-media-pool.d.ts +207 -0
- package/dist/proxmox/generated/pbs/config-media-pool.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-notifications-matchers.d.ts +159 -0
- package/dist/proxmox/generated/pbs/config-notifications-matchers.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-notifications.d.ts +199 -0
- package/dist/proxmox/generated/pbs/config-notifications.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-remote.d.ts +175 -0
- package/dist/proxmox/generated/pbs/config-remote.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-sync.d.ts +145 -0
- package/dist/proxmox/generated/pbs/config-sync.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-tape.d.ts +200 -0
- package/dist/proxmox/generated/pbs/config-tape.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-verify.d.ts +77 -0
- package/dist/proxmox/generated/pbs/config-verify.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config.d.ts +119 -0
- package/dist/proxmox/generated/pbs/config.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/nodes-node-dns.d.ts +178 -0
- package/dist/proxmox/generated/pbs/nodes-node-dns.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/nodes-node-services.d.ts +205 -0
- package/dist/proxmox/generated/pbs/nodes-node-services.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/nodes-node.d.ts +166 -0
- package/dist/proxmox/generated/pbs/nodes-node.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/nodes.d.ts +171 -0
- package/dist/proxmox/generated/pbs/nodes.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/ping.d.ts +18 -0
- package/dist/proxmox/generated/pbs/ping.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/pull.d.ts +37 -0
- package/dist/proxmox/generated/pbs/pull.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/push.d.ts +36 -0
- package/dist/proxmox/generated/pbs/push.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/reader.d.ts +41 -0
- package/dist/proxmox/generated/pbs/reader.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/root.d.ts +16 -0
- package/dist/proxmox/generated/pbs/root.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/status.d.ts +54 -0
- package/dist/proxmox/generated/pbs/status.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/tape-drive.d.ts +181 -0
- package/dist/proxmox/generated/pbs/tape-drive.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/tape-media.d.ts +126 -0
- package/dist/proxmox/generated/pbs/tape-media.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/tape.d.ts +90 -0
- package/dist/proxmox/generated/pbs/tape.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/version.d.ts +20 -0
- package/dist/proxmox/generated/pbs/version.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs.d.ts +44 -662
- package/dist/proxmox/generated/pbs.d.ts.map +1 -1
- package/dist/proxmox/generated/pve/access-roles.d.ts +164 -0
- package/dist/proxmox/generated/pve/access-roles.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/access-users.d.ts +152 -0
- package/dist/proxmox/generated/pve/access-users.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/access.d.ts +221 -0
- package/dist/proxmox/generated/pve/access.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-acme-plugins-id.d.ts +45 -0
- package/dist/proxmox/generated/pve/cluster-acme-plugins-id.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-acme-plugins.d.ts +47 -0
- package/dist/proxmox/generated/pve/cluster-acme-plugins.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-acme-tos.d.ts +240 -0
- package/dist/proxmox/generated/pve/cluster-acme-tos.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-bulk-action.d.ts +200 -0
- package/dist/proxmox/generated/pve/cluster-bulk-action.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-firewall-refs.d.ts +109 -0
- package/dist/proxmox/generated/pve/cluster-firewall-refs.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-firewall.d.ts +225 -0
- package/dist/proxmox/generated/pve/cluster-firewall.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-ha.d.ts +199 -0
- package/dist/proxmox/generated/pve/cluster-ha.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-jobs.d.ts +183 -0
- package/dist/proxmox/generated/pve/cluster-jobs.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-metrics.d.ts +110 -0
- package/dist/proxmox/generated/pve/cluster-metrics.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-notifications-endpoints.d.ts +224 -0
- package/dist/proxmox/generated/pve/cluster-notifications-endpoints.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-notifications.d.ts +91 -0
- package/dist/proxmox/generated/pve/cluster-notifications.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-options.d.ts +85 -0
- package/dist/proxmox/generated/pve/cluster-options.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models-cputype.d.ts +48 -0
- package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models-cputype.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models.d.ts +45 -0
- package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-replication.d.ts +130 -0
- package/dist/proxmox/generated/pve/cluster-replication.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-sdn-fabrics.d.ts +220 -0
- package/dist/proxmox/generated/pve/cluster-sdn-fabrics.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-sdn-ipams.d.ts +229 -0
- package/dist/proxmox/generated/pve/cluster-sdn-ipams.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-sdn-vnets-vnet.d.ts +229 -0
- package/dist/proxmox/generated/pve/cluster-sdn-vnets-vnet.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-sdn-vnets.d.ts +51 -0
- package/dist/proxmox/generated/pve/cluster-sdn-vnets.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-sdn-zones.d.ts +225 -0
- package/dist/proxmox/generated/pve/cluster-sdn-zones.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-sdn.d.ts +202 -0
- package/dist/proxmox/generated/pve/cluster-sdn.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster.d.ts +69 -0
- package/dist/proxmox/generated/pve/cluster.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-ceph.d.ts +167 -0
- package/dist/proxmox/generated/pve/nodes-node-ceph.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-config.d.ts +43 -0
- package/dist/proxmox/generated/pve/nodes-node-config.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-disks.d.ts +225 -0
- package/dist/proxmox/generated/pve/nodes-node-disks.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-firewall.d.ts +145 -0
- package/dist/proxmox/generated/pve/nodes-node-firewall.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-hardware.d.ts +195 -0
- package/dist/proxmox/generated/pve/nodes-node-hardware.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-firewall.d.ts +191 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-firewall.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-move-volume.d.ts +33 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-move-volume.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-mtunnel.d.ts +56 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-mtunnel.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-resize.d.ts +28 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-resize.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-rrd.d.ts +186 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-rrd.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid.d.ts +191 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-network.d.ts +150 -0
- package/dist/proxmox/generated/pve/nodes-node-network.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-config.d.ts +301 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-config.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-dbus-vmstate.d.ts +170 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-dbus-vmstate.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-firewall-rules.d.ts +144 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-firewall-rules.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-move-disk.d.ts +34 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-move-disk.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-mtunnel.d.ts +137 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-mtunnel.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-status.d.ts +179 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-status.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid.d.ts +143 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu.d.ts +158 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-replication.d.ts +187 -0
- package/dist/proxmox/generated/pve/nodes-node-replication.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-services.d.ts +115 -0
- package/dist/proxmox/generated/pve/nodes-node-services.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-storage-storage.d.ts +202 -0
- package/dist/proxmox/generated/pve/nodes-node-storage-storage.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-storage.d.ts +40 -0
- package/dist/proxmox/generated/pve/nodes-node-storage.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-subscription.d.ts +173 -0
- package/dist/proxmox/generated/pve/nodes-node-subscription.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-vzdump.d.ts +97 -0
- package/dist/proxmox/generated/pve/nodes-node-vzdump.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node.d.ts +207 -0
- package/dist/proxmox/generated/pve/nodes-node.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes.d.ts +236 -0
- package/dist/proxmox/generated/pve/nodes.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/pools.d.ts +82 -0
- package/dist/proxmox/generated/pve/pools.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/storage.d.ts +159 -0
- package/dist/proxmox/generated/pve/storage.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/version.d.ts +21 -0
- package/dist/proxmox/generated/pve/version.d.ts.map +1 -0
- package/dist/proxmox/generated/pve.d.ts +69 -4766
- package/dist/proxmox/generated/pve.d.ts.map +1 -1
- package/dist/proxmox/index.js +996 -372
- package/dist/proxmox/index.js.map +48 -32
- package/dist/proxmox/lxc-lifecycle.d.ts.map +1 -1
- package/dist/proxmox/metric-server-form.d.ts +11 -0
- package/dist/proxmox/metric-server-form.d.ts.map +1 -1
- package/dist/proxmox/metric-server.d.ts.map +1 -1
- package/dist/proxmox/network-apply.d.ts.map +1 -1
- package/dist/proxmox/node-network-form.d.ts +15 -0
- package/dist/proxmox/node-network-form.d.ts.map +1 -1
- package/dist/proxmox/node-network.d.ts.map +1 -1
- package/dist/proxmox/notification-target-endpoint.d.ts +26 -0
- package/dist/proxmox/notification-target-endpoint.d.ts.map +1 -0
- package/dist/proxmox/notification-target-form.d.ts +7 -0
- package/dist/proxmox/notification-target-form.d.ts.map +1 -1
- package/dist/proxmox/notification-target.d.ts.map +1 -1
- package/dist/proxmox/pbs-notification-target-endpoint.d.ts +29 -0
- package/dist/proxmox/pbs-notification-target-endpoint.d.ts.map +1 -0
- package/dist/proxmox/pbs-notification-target-lifecycle.d.ts.map +1 -1
- package/dist/proxmox/qemu.d.ts +10 -0
- package/dist/proxmox/qemu.d.ts.map +1 -1
- package/dist/proxmox/resource-guard.d.ts +15 -0
- package/dist/proxmox/resource-guard.d.ts.map +1 -0
- package/dist/proxmox/resource-spec.d.ts +15 -4
- package/dist/proxmox/resource-spec.d.ts.map +1 -1
- package/dist/proxmox/resource.d.ts +8 -0
- package/dist/proxmox/resource.d.ts.map +1 -1
- package/dist/proxmox/sdn-apply.d.ts.map +1 -1
- package/dist/proxmox/sdn-subnet.d.ts.map +1 -1
- package/dist/proxmox/zfs-pool-write.d.ts +11 -0
- package/dist/proxmox/zfs-pool-write.d.ts.map +1 -1
- package/dist/proxmox/zfs-pool.d.ts.map +1 -1
- package/docs/linux-host-measured.md +56 -0
- package/docs/linux-host.md +186 -0
- package/docs/netbox.md +103 -0
- package/package.json +9 -1
- package/src/linux/directory-lifecycle.ts +220 -0
- package/src/linux/directory.test.ts +132 -0
- package/src/linux/directory.ts +71 -0
- package/src/linux/fake-linux-host.ts +197 -0
- package/src/linux/fake-systemd.ts +103 -0
- package/src/linux/index.ts +32 -0
- package/src/linux/providers.ts +26 -0
- package/src/linux/region.test.ts +89 -0
- package/src/linux/region.ts +118 -0
- package/src/linux/remote-file-form.ts +96 -0
- package/src/linux/remote-file-lifecycle.test.ts +191 -0
- package/src/linux/remote-file-lifecycle.ts +161 -0
- package/src/linux/remote-file-plan.ts +154 -0
- package/src/linux/remote-file.ts +88 -0
- package/src/linux/ssh-command.test.ts +140 -0
- package/src/linux/ssh-command.ts +99 -0
- package/src/linux/ssh-runner.ts +188 -0
- package/src/linux/ssh-scripts.ts +105 -0
- package/src/linux/systemctl.ts +145 -0
- package/src/linux/unit-form.ts +183 -0
- package/src/linux/unit-handlers.ts +73 -0
- package/src/linux/unit-lifecycle.test.ts +220 -0
- package/src/linux/unit-lifecycle.ts +193 -0
- package/src/linux/unit-settle.ts +110 -0
- package/src/linux/unit.ts +81 -0
- package/src/netbox/client.test.ts +67 -0
- package/src/netbox/client.ts +146 -0
- package/src/netbox/constraint-guard.ts +57 -0
- package/src/netbox/constraints.test.ts +114 -0
- package/src/netbox/constraints.ts +152 -0
- package/src/netbox/generated/constraints/index.ts +23 -0
- package/src/netbox/generated/constraints/netbox-ipam.ts +32 -0
- package/src/netbox/index.ts +34 -0
- package/src/netbox/prefix-form.test.ts +127 -0
- package/src/netbox/prefix-form.ts +84 -0
- package/src/netbox/prefix.ts +136 -0
- package/src/netbox/resource.ts +208 -0
- package/src/netbox/values.test.ts +66 -0
- package/src/netbox/values.ts +40 -0
- package/src/proxmox/api-token-form.ts +5 -0
- package/src/proxmox/apply-endpoints.ts +34 -0
- package/src/proxmox/ceph-daemon-form.ts +56 -2
- package/src/proxmox/ceph-daemon.ts +10 -21
- package/src/proxmox/ceph-flag.ts +1 -0
- package/src/proxmox/ceph-fs.ts +1 -0
- package/src/proxmox/ceph-osd-write.ts +30 -4
- package/src/proxmox/ceph-pool.ts +10 -0
- package/src/proxmox/constraints-dialect.test.ts +165 -0
- package/src/proxmox/constraints-forms.test.ts +220 -0
- package/src/proxmox/constraints-host-forms.test.ts +132 -0
- package/src/proxmox/constraints-live.test.ts +155 -0
- package/src/proxmox/constraints-node-forms.test.ts +188 -0
- package/src/proxmox/constraints-required.test.ts +172 -0
- package/src/proxmox/constraints.test.ts +8 -63
- package/src/proxmox/constraints.ts +31 -6
- package/src/proxmox/generated/constraints/index.ts +27 -3
- package/src/proxmox/generated/constraints/pbs-config.ts +63 -1
- package/src/proxmox/generated/constraints/pve-access.ts +15 -3
- package/src/proxmox/generated/constraints/pve-cluster-backup.ts +74 -0
- package/src/proxmox/generated/constraints/pve-cluster-ceph.ts +27 -0
- package/src/proxmox/generated/constraints/pve-cluster-firewall.ts +35 -0
- package/src/proxmox/generated/constraints/pve-cluster-ha.ts +55 -0
- package/src/proxmox/generated/constraints/pve-cluster-metrics.ts +59 -0
- package/src/proxmox/generated/constraints/pve-cluster-notifications.ts +80 -0
- package/src/proxmox/generated/constraints/pve-cluster-replication.ts +43 -0
- package/src/proxmox/generated/constraints/pve-cluster-sdn.ts +90 -0
- package/src/proxmox/generated/constraints/pve-nodes-ceph.ts +57 -0
- package/src/proxmox/generated/constraints/pve-nodes-disks.ts +31 -0
- package/src/proxmox/generated/constraints/pve-nodes-lxc.ts +82 -0
- package/src/proxmox/generated/constraints/pve-nodes-network.ts +77 -0
- package/src/proxmox/generated/constraints/pve-nodes-qemu.ts +125 -0
- package/src/proxmox/generated/constraints/pve-pools.ts +6 -1
- package/src/proxmox/generated/constraints/pve-storage.ts +8 -3
- package/src/proxmox/generated/pbs/access-users.ts +137 -0
- package/src/proxmox/generated/pbs/access.ts +232 -0
- package/src/proxmox/generated/pbs/admin-datastore-store.ts +238 -0
- package/src/proxmox/generated/pbs/admin-metrics.ts +147 -0
- package/src/proxmox/generated/pbs/admin.ts +225 -0
- package/src/proxmox/generated/pbs/backup.ts +121 -0
- package/src/proxmox/generated/pbs/config-access-pam.ts +228 -0
- package/src/proxmox/generated/pbs/config-access.ts +228 -0
- package/src/proxmox/generated/pbs/config-datastore.ts +217 -0
- package/src/proxmox/generated/pbs/config-media-pool.ts +232 -0
- package/src/proxmox/generated/pbs/config-notifications-matchers.ts +187 -0
- package/src/proxmox/generated/pbs/config-notifications.ts +221 -0
- package/src/proxmox/generated/pbs/config-remote.ts +192 -0
- package/src/proxmox/generated/pbs/config-sync.ts +167 -0
- package/src/proxmox/generated/pbs/config-tape.ts +226 -0
- package/src/proxmox/generated/pbs/config-verify.ts +87 -0
- package/src/proxmox/generated/pbs/config.ts +135 -0
- package/src/proxmox/generated/pbs/nodes-node-dns.ts +229 -0
- package/src/proxmox/generated/pbs/nodes-node-services.ts +181 -0
- package/src/proxmox/generated/pbs/nodes-node.ts +244 -0
- package/src/proxmox/generated/pbs/nodes.ts +160 -0
- package/src/proxmox/generated/pbs/ping.ts +16 -0
- package/src/proxmox/generated/pbs/pull.ts +37 -0
- package/src/proxmox/generated/pbs/push.ts +36 -0
- package/src/proxmox/generated/pbs/reader.ts +41 -0
- package/src/proxmox/generated/pbs/root.ts +16 -0
- package/src/proxmox/generated/pbs/status.ts +50 -0
- package/src/proxmox/generated/pbs/tape-drive.ts +220 -0
- package/src/proxmox/generated/pbs/tape-media.ts +133 -0
- package/src/proxmox/generated/pbs/tape.ts +92 -0
- package/src/proxmox/generated/pbs/version.ts +16 -0
- package/src/proxmox/generated/pbs.ts +44 -787
- package/src/proxmox/generated/pve/access-roles.ts +168 -0
- package/src/proxmox/generated/pve/access-users.ts +155 -0
- package/src/proxmox/generated/pve/access.ts +217 -0
- package/src/proxmox/generated/pve/cluster-acme-plugins-id.ts +367 -0
- package/src/proxmox/generated/pve/cluster-acme-plugins.ts +366 -0
- package/src/proxmox/generated/pve/cluster-acme-tos.ts +232 -0
- package/src/proxmox/generated/pve/cluster-bulk-action.ts +227 -0
- package/src/proxmox/generated/pve/cluster-firewall-refs.ts +110 -0
- package/src/proxmox/generated/pve/cluster-firewall.ts +236 -0
- package/src/proxmox/generated/pve/cluster-ha.ts +203 -0
- package/src/proxmox/generated/pve/cluster-jobs.ts +190 -0
- package/src/proxmox/generated/pve/cluster-metrics.ts +116 -0
- package/src/proxmox/generated/pve/cluster-notifications-endpoints.ts +244 -0
- package/src/proxmox/generated/pve/cluster-notifications.ts +99 -0
- package/src/proxmox/generated/pve/cluster-options.ts +189 -0
- package/src/proxmox/generated/pve/cluster-qemu-custom-cpu-models-cputype.ts +272 -0
- package/src/proxmox/generated/pve/cluster-qemu-custom-cpu-models.ts +268 -0
- package/src/proxmox/generated/pve/cluster-replication.ts +127 -0
- package/src/proxmox/generated/pve/cluster-sdn-fabrics.ts +218 -0
- package/src/proxmox/generated/pve/cluster-sdn-ipams.ts +229 -0
- package/src/proxmox/generated/pve/cluster-sdn-vnets-vnet.ts +245 -0
- package/src/proxmox/generated/pve/cluster-sdn-vnets.ts +49 -0
- package/src/proxmox/generated/pve/cluster-sdn-zones.ts +224 -0
- package/src/proxmox/generated/pve/cluster-sdn.ts +201 -0
- package/src/proxmox/generated/pve/cluster.ts +71 -0
- package/src/proxmox/generated/pve/nodes-node-ceph.ts +167 -0
- package/src/proxmox/generated/pve/nodes-node-config.ts +56 -0
- package/src/proxmox/generated/pve/nodes-node-disks.ts +237 -0
- package/src/proxmox/generated/pve/nodes-node-firewall.ts +239 -0
- package/src/proxmox/generated/pve/nodes-node-hardware.ts +215 -0
- package/src/proxmox/generated/pve/nodes-node-lxc-vmid-firewall.ts +232 -0
- package/src/proxmox/generated/pve/nodes-node-lxc-vmid-move-volume.ts +1059 -0
- package/src/proxmox/generated/pve/nodes-node-lxc-vmid-mtunnel.ts +49 -0
- package/src/proxmox/generated/pve/nodes-node-lxc-vmid-resize.ts +285 -0
- package/src/proxmox/generated/pve/nodes-node-lxc-vmid-rrd.ts +184 -0
- package/src/proxmox/generated/pve/nodes-node-lxc-vmid.ts +240 -0
- package/src/proxmox/generated/pve/nodes-node-network.ts +234 -0
- package/src/proxmox/generated/pve/nodes-node-qemu-vmid-config.ts +441 -0
- package/src/proxmox/generated/pve/nodes-node-qemu-vmid-dbus-vmstate.ts +215 -0
- package/src/proxmox/generated/pve/nodes-node-qemu-vmid-firewall-rules.ts +145 -0
- package/src/proxmox/generated/pve/nodes-node-qemu-vmid-move-disk.ts +664 -0
- package/src/proxmox/generated/pve/nodes-node-qemu-vmid-mtunnel.ts +188 -0
- package/src/proxmox/generated/pve/nodes-node-qemu-vmid-status.ts +233 -0
- package/src/proxmox/generated/pve/nodes-node-qemu-vmid.ts +187 -0
- package/src/proxmox/generated/pve/nodes-node-qemu.ts +204 -0
- package/src/proxmox/generated/pve/nodes-node-replication.ts +187 -0
- package/src/proxmox/generated/pve/nodes-node-services.ts +221 -0
- package/src/proxmox/generated/pve/nodes-node-storage-storage.ts +226 -0
- package/src/proxmox/generated/pve/nodes-node-storage.ts +40 -0
- package/src/proxmox/generated/pve/nodes-node-subscription.ts +168 -0
- package/src/proxmox/generated/pve/nodes-node-vzdump.ts +96 -0
- package/src/proxmox/generated/pve/nodes-node.ts +211 -0
- package/src/proxmox/generated/pve/nodes.ts +235 -0
- package/src/proxmox/generated/pve/pools.ts +78 -0
- package/src/proxmox/generated/pve/storage.ts +213 -0
- package/src/proxmox/generated/pve/version.ts +21 -0
- package/src/proxmox/generated/pve.ts +69 -7403
- package/src/proxmox/lxc-interior.test.ts +17 -1
- package/src/proxmox/lxc-lifecycle.ts +48 -9
- package/src/proxmox/metric-server-form.ts +10 -0
- package/src/proxmox/metric-server.ts +11 -2
- package/src/proxmox/network-apply.ts +4 -0
- package/src/proxmox/node-network-form.ts +19 -0
- package/src/proxmox/node-network.ts +9 -3
- package/src/proxmox/notification-target-endpoint.ts +46 -0
- package/src/proxmox/notification-target-form.ts +11 -0
- package/src/proxmox/notification-target.ts +5 -2
- package/src/proxmox/pbs-notification-target-endpoint.ts +45 -0
- package/src/proxmox/pbs-notification-target-lifecycle.ts +16 -14
- package/src/proxmox/qemu.ts +9 -1
- package/src/proxmox/resource-guard.ts +61 -0
- package/src/proxmox/resource-spec.ts +16 -4
- package/src/proxmox/resource.ts +27 -27
- package/src/proxmox/sdn-apply.ts +4 -0
- package/src/proxmox/sdn-subnet.ts +5 -0
- package/src/proxmox/zfs-pool-write.ts +12 -0
- package/src/proxmox/zfs-pool.ts +2 -1
- package/dist/proxmox/generated/constraints/pve-cluster.d.ts +0 -18
- package/dist/proxmox/generated/constraints/pve-cluster.d.ts.map +0 -1
- package/src/proxmox/generated/constraints/pve-cluster.ts +0 -174
package/docs/netbox.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# NetBox — `@homeflare/alchemy/netbox`
|
|
2
|
+
|
|
3
|
+
NetBox is a record of decisions, not a controller. Nothing here reboots anything; the worst a
|
|
4
|
+
mistake does is write the wrong intent into the source of truth everything else is checked against.
|
|
5
|
+
That shapes every posture below.
|
|
6
|
+
|
|
7
|
+
## Credentials
|
|
8
|
+
|
|
9
|
+
Two environment variables, read **at call time**, never props:
|
|
10
|
+
|
|
11
|
+
| variable | holds |
|
|
12
|
+
| -------------- | ------------------------------------------------------ |
|
|
13
|
+
| `NETBOX_URL` | the instance origin, e.g. `https://netbox.example.com` |
|
|
14
|
+
| `NETBOX_TOKEN` | a scoped NetBox API token |
|
|
15
|
+
|
|
16
|
+
⛔ **Neither has a default.** A package that fell back to a loopback address would let a consumer
|
|
17
|
+
who forgot the variable watch every call fail against a machine that is not theirs, instead of
|
|
18
|
+
being told which variable is missing.
|
|
19
|
+
|
|
20
|
+
⚠️ **`Authorization: Token <value>`, not `Bearer`.** NetBox is Django REST Framework using DRF's
|
|
21
|
+
own `TokenAuthentication`. Measured: `Bearer` returns 401 and `Token` returns 200 — and a wrong
|
|
22
|
+
scheme and a wrong credential are indistinguishable in the response.
|
|
23
|
+
|
|
24
|
+
⛔ **Mint a short-lived, scoped token.** A NetBox token can be given an expiry, a source-IP
|
|
25
|
+
restriction and `write_enabled: false`; a plan-only run wants the last of those.
|
|
26
|
+
|
|
27
|
+
## Adopt is the default posture
|
|
28
|
+
|
|
29
|
+
`reconcile` LOCATES before it writes. An object the instance already holds is bound, not
|
|
30
|
+
duplicated — NetBox predates this code by years on any real estate, and a provider that created a
|
|
31
|
+
second `10.0.0.0/24` on first deploy would be worse than no provider.
|
|
32
|
+
|
|
33
|
+
⛔ **An ambiguous identity FAILS.** `soleMatch` refuses more than one candidate rather than taking
|
|
34
|
+
the first: binding to "whichever NetBox ordered first" would make the next plan bind to the other
|
|
35
|
+
row and report drift that is not there.
|
|
36
|
+
|
|
37
|
+
★ **Narrowing is split: a filter the schema declares, then a rule in this process.** `locate`
|
|
38
|
+
sends only filters the vendor document states; `identifies` picks the one row among the candidates.
|
|
39
|
+
|
|
40
|
+
⚠️ **`Netbox.Prefix` uses that split for the VRF, and the reason is a guess that was caught.** A
|
|
41
|
+
prefix is unique per-VRF, so the CIDR alone can match several rows. The obvious server-side
|
|
42
|
+
narrowing is `vrf_id=null` for the global table — NetBox does define `FILTERS_NULL_CHOICE_VALUE =
|
|
43
|
+
'null'`. ⛔ But measured in the vendor's own source at v4.7.0, `PrefixFilterSet.vrf_id` is a plain
|
|
44
|
+
`ModelMultipleChoiceFilter` with no `null_value`: it does not opt in, so `'null'` fails queryset
|
|
45
|
+
validation and NetBox answers **400 on every plan for every global prefix**. The VRF is compared
|
|
46
|
+
here instead, where the rule is readable and testable without a server.
|
|
47
|
+
|
|
48
|
+
## Removal is `retain`
|
|
49
|
+
|
|
50
|
+
⛔ **Deleting a NetBox row deletes history.** Child prefixes reparent, IP assignments detach, and
|
|
51
|
+
the change log is the only trace left. Every family here defaults to `retain`, so dropping a
|
|
52
|
+
declaration does not delete the object unless the caller opts into `.pipe(RemovalPolicy.destroy())`.
|
|
53
|
+
The `delete` handler is fully implemented anyway — a stub that silently does nothing lies to
|
|
54
|
+
whoever reads the plan.
|
|
55
|
+
|
|
56
|
+
## An undeclared field is not an instruction to clear it
|
|
57
|
+
|
|
58
|
+
🔴 **This shipped wrong once, and the shape of the mistake is worth keeping.** `Netbox.Prefix`
|
|
59
|
+
sent `description: ''` whenever the prop was absent. On a create that is invisible — the field was
|
|
60
|
+
empty anyway. ⛔ **On an adopt it is data loss**: a prefix's description is usually the only written
|
|
61
|
+
trace of why that range exists, and the first deploy that adopted one would have PATCHed it to
|
|
62
|
+
empty, with a plan that read `update`.
|
|
63
|
+
|
|
64
|
+
★ **So the line is drawn at what the vendor itself defaults.** `status`, `is_pool` and
|
|
65
|
+
`mark_utilized` have defaults in the schema, so omitting one genuinely means "the default" and
|
|
66
|
+
settling it says what NetBox would have done anyway. `description`, `comments` and every optional
|
|
67
|
+
foreign key have no such default — the schema's `''` is the absence of a value, not a decision —
|
|
68
|
+
so they are **omitted from the body and uncompared** until you declare them.
|
|
69
|
+
|
|
70
|
+
⚠️ **The cost, stated:** prose cannot be cleared by omission. Clearing it is `description: ''`,
|
|
71
|
+
written on purpose, which is the readable way to say a destructive thing.
|
|
72
|
+
|
|
73
|
+
⛔ **Whatever `matches` compares, `body` must send.** A field compared but never written produces
|
|
74
|
+
a plan that says `update` forever: the PATCH omits it, so the next read is unchanged. The two move
|
|
75
|
+
together, and `prefix-form.test.ts` asserts it.
|
|
76
|
+
|
|
77
|
+
## Read and write are different shapes
|
|
78
|
+
|
|
79
|
+
⛔ **This is the bug that makes a resource never converge, and it is handled in `values.ts`.** A
|
|
80
|
+
prefix is WRITTEN with `status: "active"` and READ BACK as `{value: "active", label: "Active"}`; a
|
|
81
|
+
foreign key is written as `4` and read back as `{id: 4, url: …, display: …}`. A `matches` that
|
|
82
|
+
compared those directly would report drift on every plan, for every object, forever — and each
|
|
83
|
+
"update" would PATCH the same value back.
|
|
84
|
+
|
|
85
|
+
## Vendor constraints, checked before the request
|
|
86
|
+
|
|
87
|
+
Every write is checked against a table **generated from NetBox's own OpenAPI document** — not from
|
|
88
|
+
hand-typed limits. A declaration the vendor would reject dies at plan time with the endpoint named,
|
|
89
|
+
rather than half-way through a deploy that has already adopted ten objects.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { bodyViolations, constraintsFor } from '@homeflare/alchemy/netbox';
|
|
93
|
+
|
|
94
|
+
constraintsFor('netbox:POST /api/ipam/prefixes/')['description']?.maxLength; // 200
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
⚠️ **What it deliberately does NOT enforce**, so a green plan is not read as more than it is: a
|
|
98
|
+
`format` name DRF validates server-side; a pattern whose Python `\w` is Unicode where JavaScript's
|
|
99
|
+
is ASCII (recorded as `patternSource` with no `pattern`); and uniqueness, VRF containment and
|
|
100
|
+
custom-field validators, which live in Django rather than in the schema and still arrive as a 400.
|
|
101
|
+
|
|
102
|
+
Provenance, the re-fetch recipe and the coverage report: [`codegen/netbox.md`](../../../codegen/netbox.md)
|
|
103
|
+
and [`docs/netbox-coverage.md`](../../../docs/netbox-coverage.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homeflare/alchemy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Custom Alchemy providers for Cloudflare, Proxmox, OpenBao, Forgejo, Talos, launchd and Caddy, and a verifier that proves adoptions are no-ops.",
|
|
5
5
|
"homepage": "https://github.com/taslabs-net/homeflare-kit/tree/main/packages/alchemy#readme",
|
|
6
6
|
"bugs": "https://github.com/taslabs-net/homeflare-kit/issues",
|
|
@@ -56,6 +56,14 @@
|
|
|
56
56
|
"types": "./dist/launchd/index.d.ts",
|
|
57
57
|
"default": "./dist/launchd/index.js"
|
|
58
58
|
},
|
|
59
|
+
"./netbox": {
|
|
60
|
+
"types": "./dist/netbox/index.d.ts",
|
|
61
|
+
"default": "./dist/netbox/index.js"
|
|
62
|
+
},
|
|
63
|
+
"./linux": {
|
|
64
|
+
"types": "./dist/linux/index.d.ts",
|
|
65
|
+
"default": "./dist/linux/index.js"
|
|
66
|
+
},
|
|
59
67
|
"./caddy": {
|
|
60
68
|
"types": "./dist/caddy/index.d.ts",
|
|
61
69
|
"default": "./dist/caddy/index.js"
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HostDirectory's props, validation and lifecycle over a HostRunner.
|
|
3
|
+
*
|
|
4
|
+
* ★ WHY IT EXISTS AT ALL. `Host.File` and `Remote.File` both refuse to create a parent directory,
|
|
5
|
+
* on purpose — creating one would invent an owner and a mode nobody declared. The consequence is
|
|
6
|
+
* that a first deploy into a new tree fails on its first file. This resource is the declaration
|
|
7
|
+
* that was missing, not a convenience flag on the file.
|
|
8
|
+
* ⛔ ONE DIRECTORY, NEVER A CHAIN. A missing parent is a refusal that names it, so a tree is a
|
|
9
|
+
* chain of declarations whose modes and owners are all visible in the plan. `mkdir -p` would
|
|
10
|
+
* create the intermediate levels at whatever umask the deploy happened to run with.
|
|
11
|
+
* ⛔ DELETE IS `rmdir`, NEVER RECURSIVE. A directory that still holds files is a refusal: removing
|
|
12
|
+
* a declaration must not be able to remove data the declaration never mentioned.
|
|
13
|
+
*/
|
|
14
|
+
import type { Diff } from 'alchemy/Diff';
|
|
15
|
+
import { pathProblems } from '../launchd/host-file-form.ts';
|
|
16
|
+
import { type HostRunner, canActAsRoot } from '../launchd/runner.ts';
|
|
17
|
+
|
|
18
|
+
export interface HostDirectoryProps {
|
|
19
|
+
/** Absolute path. ⚠️ Its parent must already exist — declare it as another HostDirectory. */
|
|
20
|
+
path: string;
|
|
21
|
+
/** Permission bits. @default 0o755 */
|
|
22
|
+
mode?: number;
|
|
23
|
+
/** User name or numeric uid. Omitted: whoever the runner writes as. */
|
|
24
|
+
owner?: string | number;
|
|
25
|
+
/** Group name or numeric gid. Omitted: the parent's default group. */
|
|
26
|
+
group?: string | number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface HostDirectoryAttributes {
|
|
30
|
+
path: string;
|
|
31
|
+
mode: number;
|
|
32
|
+
uid: number;
|
|
33
|
+
gid: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const DEFAULT_DIR_MODE = 0o755;
|
|
37
|
+
|
|
38
|
+
export const refuse = (path: string, message: string): Error =>
|
|
39
|
+
new Error(`Host.Directory ${path}: ${message}`);
|
|
40
|
+
|
|
41
|
+
const idProblems = (name: string, value: string | number | undefined): string[] => {
|
|
42
|
+
if (value === undefined) return [];
|
|
43
|
+
if (typeof value === 'number') {
|
|
44
|
+
return Number.isSafeInteger(value) && value >= 0 ? [] : [`${name} must be a non-negative id`];
|
|
45
|
+
}
|
|
46
|
+
return /^[A-Za-z_][A-Za-z0-9_.-]*$/.test(value) || /^\d+$/.test(value)
|
|
47
|
+
? []
|
|
48
|
+
: [`${name} ${JSON.stringify(value)} is not a valid user or group name`];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const directoryProblems = (props: HostDirectoryProps): string[] => {
|
|
52
|
+
const found = pathProblems(props.path);
|
|
53
|
+
const mode = props.mode ?? DEFAULT_DIR_MODE;
|
|
54
|
+
if (!Number.isSafeInteger(mode) || mode < 0 || mode > 0o7777) found.push('mode must be 0–0o7777');
|
|
55
|
+
found.push(...idProblems('owner', props.owner), ...idProblems('group', props.group));
|
|
56
|
+
return found;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const numericId = (value: string | number): number | undefined =>
|
|
60
|
+
typeof value === 'number' ? value : /^\d+$/.test(value) ? Number(value) : undefined;
|
|
61
|
+
|
|
62
|
+
type Desired = { readonly mode: number; readonly uid?: number; readonly gid?: number };
|
|
63
|
+
|
|
64
|
+
const desired = async (runner: HostRunner, props: HostDirectoryProps): Promise<Desired> => {
|
|
65
|
+
const found = directoryProblems(props);
|
|
66
|
+
if (found.length > 0) throw refuse(props.path, found.join('; '));
|
|
67
|
+
let uid: number | undefined;
|
|
68
|
+
let gid: number | undefined;
|
|
69
|
+
if (props.owner !== undefined) {
|
|
70
|
+
uid = numericId(props.owner) ?? (await runner.lookupUser(String(props.owner)))?.uid;
|
|
71
|
+
if (uid === undefined) throw refuse(props.path, `no user ${String(props.owner)} on this host`);
|
|
72
|
+
}
|
|
73
|
+
if (props.group !== undefined) {
|
|
74
|
+
gid = numericId(props.group) ?? (await runner.lookupGroup(String(props.group)));
|
|
75
|
+
if (gid === undefined) throw refuse(props.path, `no group ${String(props.group)} on this host`);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
mode: props.mode ?? DEFAULT_DIR_MODE,
|
|
79
|
+
...(uid === undefined ? {} : { uid }),
|
|
80
|
+
...(gid === undefined ? {} : { gid }),
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const readDirectory = async (
|
|
85
|
+
runner: HostRunner,
|
|
86
|
+
path: string,
|
|
87
|
+
): Promise<HostDirectoryAttributes | undefined> => {
|
|
88
|
+
const stat = await runner.stat(path);
|
|
89
|
+
if (stat === undefined || stat.kind !== 'directory') return undefined;
|
|
90
|
+
return { gid: stat.gid, mode: stat.mode, path, uid: stat.uid };
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const octal = (mode: number) => mode.toString(8).padStart(3, '0');
|
|
94
|
+
|
|
95
|
+
/** One program, checked: anything but exit 0 is an Error naming the argv and the host's words. */
|
|
96
|
+
const must = async (runner: HostRunner, path: string, argv: readonly string[]): Promise<void> => {
|
|
97
|
+
const result = await runner.exec(argv);
|
|
98
|
+
if (result.exitCode !== 0) {
|
|
99
|
+
throw refuse(
|
|
100
|
+
path,
|
|
101
|
+
`${argv.join(' ')} -> ${String(result.exitCode)}: ${result.stderr.trim().slice(0, 300)}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create it when absent, then bring mode and ownership to the declaration and read back.
|
|
108
|
+
* ⚠️ `mkdir -m` sets the mode with chmod(2) after mkdir(2), so it is exact — plain `mkdir` would
|
|
109
|
+
* mask the declared bits through the deploying shell's umask and the drift would return forever.
|
|
110
|
+
*/
|
|
111
|
+
export const reconcileDirectory = async (
|
|
112
|
+
runner: HostRunner,
|
|
113
|
+
props: HostDirectoryProps,
|
|
114
|
+
output?: HostDirectoryAttributes,
|
|
115
|
+
adopt = false,
|
|
116
|
+
): Promise<HostDirectoryAttributes> => {
|
|
117
|
+
const want = await desired(runner, props);
|
|
118
|
+
if (want.uid !== undefined && want.uid !== runner.effectiveUid() && !canActAsRoot(runner)) {
|
|
119
|
+
throw refuse(
|
|
120
|
+
props.path,
|
|
121
|
+
`owner ${String(props.owner)} is not the deploying user; only root may chown. Deploy through ` +
|
|
122
|
+
'a root destination or a privileged HostRunner. This provider never calls sudo itself.',
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
const stat = await runner.stat(props.path);
|
|
126
|
+
if (stat !== undefined && stat.kind !== 'directory') {
|
|
127
|
+
throw refuse(props.path, `is a ${stat.kind}, not a directory`);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* ⛔ A DIRECTORY THIS RESOURCE DOES NOT OWN IS NEVER RE-MODED. Where the engine's probe never
|
|
131
|
+
* looked — a replace's new path, a create whose props were still an Output — an existing
|
|
132
|
+
* directory with a different mode or owner is someone else's. ★ Adoption takes it over.
|
|
133
|
+
*/
|
|
134
|
+
if (output === undefined && stat !== undefined && !adopt) {
|
|
135
|
+
const drifted = stat.mode !== want.mode || (want.uid !== undefined && stat.uid !== want.uid);
|
|
136
|
+
if (drifted) {
|
|
137
|
+
throw refuse(
|
|
138
|
+
props.path,
|
|
139
|
+
'already exists with another mode or owner and is not this resource. Remove it, or ' +
|
|
140
|
+
'declare it as a new resource and deploy with --adopt.',
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (stat === undefined) {
|
|
145
|
+
const parent = props.path.slice(0, props.path.lastIndexOf('/')) || '/';
|
|
146
|
+
const above = await runner.stat(parent);
|
|
147
|
+
if (above === undefined || above.kind !== 'directory') {
|
|
148
|
+
throw refuse(props.path, `its parent ${parent} does not exist; declare that directory too`);
|
|
149
|
+
}
|
|
150
|
+
await must(runner, props.path, ['mkdir', '-m', octal(want.mode), '--', props.path]);
|
|
151
|
+
} else if (stat.mode !== want.mode) {
|
|
152
|
+
await must(runner, props.path, ['chmod', octal(want.mode), '--', props.path]);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* ★ Only when it is actually wrong. A `chown` that changes nothing is still a write on the host
|
|
156
|
+
* and a line in the deploy log, and "a converged resource is not touched" is the rule this
|
|
157
|
+
* whole family is judged by.
|
|
158
|
+
*/
|
|
159
|
+
const owner = ownerMatches(
|
|
160
|
+
{ gid: stat?.gid ?? -1, mode: 0, path: props.path, uid: stat?.uid ?? -1 },
|
|
161
|
+
want,
|
|
162
|
+
)
|
|
163
|
+
? undefined
|
|
164
|
+
: ownerArg(want);
|
|
165
|
+
if (owner !== undefined) await must(runner, props.path, ['chown', owner, '--', props.path]);
|
|
166
|
+
const after = await readDirectory(runner, props.path);
|
|
167
|
+
// ⚠️ READ BACK: a runner that ignored the mode shows up here, not as a forever-`update`.
|
|
168
|
+
if (after === undefined || after.mode !== want.mode || !ownerMatches(after, want)) {
|
|
169
|
+
throw refuse(props.path, 'the directory on the host does not match the declaration');
|
|
170
|
+
}
|
|
171
|
+
return after;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/** ⛔ Never `<uid>:` — GNU chown reads a trailing colon as that user's LOGIN group, undeclared. */
|
|
175
|
+
const ownerArg = (want: Desired): string | undefined => {
|
|
176
|
+
if (want.uid === undefined) return want.gid === undefined ? undefined : `:${String(want.gid)}`;
|
|
177
|
+
return want.gid === undefined ? String(want.uid) : `${String(want.uid)}:${String(want.gid)}`;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const ownerMatches = (live: HostDirectoryAttributes, want: Desired): boolean =>
|
|
181
|
+
(want.uid === undefined || live.uid === want.uid) &&
|
|
182
|
+
(want.gid === undefined || live.gid === want.gid);
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* ★ A plan that will write asks the runner first (`checkWrite`, when it has one) — a directory is
|
|
186
|
+
* created and chmod'd through `exec`, but a runner that refuses to touch the path at all should
|
|
187
|
+
* say so at plan time rather than mid-apply.
|
|
188
|
+
*/
|
|
189
|
+
export const diffDirectory = async (
|
|
190
|
+
runner: HostRunner,
|
|
191
|
+
news: HostDirectoryProps,
|
|
192
|
+
output: HostDirectoryAttributes,
|
|
193
|
+
): Promise<Diff> => {
|
|
194
|
+
const want = await desired(runner, news);
|
|
195
|
+
if (news.path !== output.path) return { action: 'replace' };
|
|
196
|
+
const live = await readDirectory(runner, news.path);
|
|
197
|
+
// ⛔ BOTH: the live directory catches a hand chmod, the stored attributes catch a deploy that
|
|
198
|
+
// died after the mkdir and before its commit.
|
|
199
|
+
const converged =
|
|
200
|
+
live !== undefined &&
|
|
201
|
+
live.mode === want.mode &&
|
|
202
|
+
ownerMatches(live, want) &&
|
|
203
|
+
output.mode === want.mode &&
|
|
204
|
+
ownerMatches(output, want);
|
|
205
|
+
if (converged) return { action: 'noop' };
|
|
206
|
+
await runner.checkWrite?.(news.path, want);
|
|
207
|
+
return { action: 'update' };
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/** Remove it. ⛔ `rmdir`: a directory that still holds anything is a refusal, never a recursion. */
|
|
211
|
+
export const deleteDirectory = async (
|
|
212
|
+
runner: HostRunner,
|
|
213
|
+
output: HostDirectoryAttributes,
|
|
214
|
+
): Promise<void> => {
|
|
215
|
+
const stat = await runner.stat(output.path);
|
|
216
|
+
if (stat === undefined) return;
|
|
217
|
+
if (stat.kind !== 'directory')
|
|
218
|
+
throw refuse(output.path, `is now a ${stat.kind}; not removing it`);
|
|
219
|
+
await must(runner, output.path, ['rmdir', '--', output.path]);
|
|
220
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HostDirectory against the fake host: the resource that exists because every file resource refuses
|
|
3
|
+
* to create a parent.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, test } from 'bun:test';
|
|
6
|
+
import {
|
|
7
|
+
deleteDirectory,
|
|
8
|
+
diffDirectory,
|
|
9
|
+
directoryProblems,
|
|
10
|
+
readDirectory,
|
|
11
|
+
reconcileDirectory,
|
|
12
|
+
} from './directory-lifecycle.ts';
|
|
13
|
+
import { fakeLinuxHost } from './fake-linux-host.ts';
|
|
14
|
+
|
|
15
|
+
const host = () =>
|
|
16
|
+
fakeLinuxHost({
|
|
17
|
+
dirs: { '/opt/app': 0 },
|
|
18
|
+
groups: { app: 60 },
|
|
19
|
+
users: { app: { gid: 60, home: '/opt/app', uid: 900 } },
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('create', () => {
|
|
23
|
+
test('makes the directory with the declared mode and owner, then reads it back', async () => {
|
|
24
|
+
const fake = host();
|
|
25
|
+
const attrs = await reconcileDirectory(fake.runner, {
|
|
26
|
+
group: 'app',
|
|
27
|
+
mode: 0o750,
|
|
28
|
+
owner: 'app',
|
|
29
|
+
path: '/opt/app/etc',
|
|
30
|
+
});
|
|
31
|
+
expect(attrs).toEqual({ gid: 60, mode: 0o750, path: '/opt/app/etc', uid: 900 });
|
|
32
|
+
// ⚠️ `mkdir -m` carries the mode, so a umask never masks a declared bit.
|
|
33
|
+
expect(fake.calls.some((call) => call[0] === 'mkdir' && call.includes('750'))).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('a missing parent is a refusal that names it, never an mkdir -p', async () => {
|
|
37
|
+
const fake = host();
|
|
38
|
+
await expect(reconcileDirectory(fake.runner, { path: '/opt/app/one/two' })).rejects.toThrow(
|
|
39
|
+
/its parent \/opt\/app\/one does not exist/,
|
|
40
|
+
);
|
|
41
|
+
expect(fake.calls.some((call) => call.includes('-p'))).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('a path that is a file is refused rather than replaced', async () => {
|
|
45
|
+
const fake = host();
|
|
46
|
+
await fake.runner.writeFileAtomic('/opt/app/thing', new Uint8Array(), { mode: 0o644 });
|
|
47
|
+
await expect(reconcileDirectory(fake.runner, { path: '/opt/app/thing' })).rejects.toThrow(
|
|
48
|
+
/is a file, not a directory/,
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('drift', () => {
|
|
54
|
+
test('a hand chmod is an update, and reconcile puts it back', async () => {
|
|
55
|
+
const fake = host();
|
|
56
|
+
const output = await reconcileDirectory(fake.runner, { mode: 0o750, path: '/opt/app/etc' });
|
|
57
|
+
expect(await diffDirectory(fake.runner, { mode: 0o750, path: '/opt/app/etc' }, output)).toEqual(
|
|
58
|
+
{
|
|
59
|
+
action: 'noop',
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
const live = fake.modes.get('/opt/app/etc');
|
|
63
|
+
if (live !== undefined) live.mode = 0o777;
|
|
64
|
+
expect(await diffDirectory(fake.runner, { mode: 0o750, path: '/opt/app/etc' }, output)).toEqual(
|
|
65
|
+
{
|
|
66
|
+
action: 'update',
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
const fixed = await reconcileDirectory(
|
|
70
|
+
fake.runner,
|
|
71
|
+
{ mode: 0o750, path: '/opt/app/etc' },
|
|
72
|
+
output,
|
|
73
|
+
);
|
|
74
|
+
expect(fixed.mode).toBe(0o750);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('a directory that is not this resource is refused unless adoption is on', async () => {
|
|
78
|
+
const fake = host();
|
|
79
|
+
await reconcileDirectory(fake.runner, { mode: 0o700, path: '/opt/app/etc' });
|
|
80
|
+
await expect(
|
|
81
|
+
reconcileDirectory(fake.runner, { mode: 0o755, path: '/opt/app/etc' }),
|
|
82
|
+
).rejects.toThrow(/deploy with --adopt/);
|
|
83
|
+
const adopted = await reconcileDirectory(
|
|
84
|
+
fake.runner,
|
|
85
|
+
{ mode: 0o755, path: '/opt/app/etc' },
|
|
86
|
+
undefined,
|
|
87
|
+
true,
|
|
88
|
+
);
|
|
89
|
+
expect(adopted.mode).toBe(0o755);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('a directory that already matches is not touched — not even a no-op chown', async () => {
|
|
93
|
+
const fake = host();
|
|
94
|
+
const props = { group: 'app', mode: 0o755, owner: 'app', path: '/opt/app/etc' };
|
|
95
|
+
const output = await reconcileDirectory(fake.runner, props);
|
|
96
|
+
const before = fake.calls.length;
|
|
97
|
+
await reconcileDirectory(fake.runner, props, output);
|
|
98
|
+
const after = fake.calls.slice(before).map((call) => call[0]);
|
|
99
|
+
expect(after).not.toContain('mkdir');
|
|
100
|
+
expect(after).not.toContain('chmod');
|
|
101
|
+
// ⛔ A chown that changes nothing is still a write on the host and a line in the deploy log.
|
|
102
|
+
expect(after).not.toContain('chown');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('delete', () => {
|
|
107
|
+
test('is rmdir: a directory that still holds a file is a refusal', async () => {
|
|
108
|
+
const fake = host();
|
|
109
|
+
const attrs = await reconcileDirectory(fake.runner, { path: '/opt/app/etc' });
|
|
110
|
+
await fake.runner.writeFileAtomic('/opt/app/etc/kept.conf', new Uint8Array(), { mode: 0o644 });
|
|
111
|
+
await expect(deleteDirectory(fake.runner, attrs)).rejects.toThrow(/Directory not empty/);
|
|
112
|
+
expect(fake.calls.some((call) => call.includes('-r') || call.includes('-rf'))).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('an empty directory goes, and a second delete is a no-op', async () => {
|
|
116
|
+
const fake = host();
|
|
117
|
+
const attrs = await reconcileDirectory(fake.runner, { path: '/opt/app/etc' });
|
|
118
|
+
await deleteDirectory(fake.runner, attrs);
|
|
119
|
+
expect(await readDirectory(fake.runner, '/opt/app/etc')).toBeUndefined();
|
|
120
|
+
await deleteDirectory(fake.runner, attrs);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('validation', () => {
|
|
125
|
+
test('refuses a relative or unnormalised path and an impossible mode', () => {
|
|
126
|
+
expect(directoryProblems({ path: 'opt/app' })).toContain('path must be absolute');
|
|
127
|
+
expect(directoryProblems({ path: '/opt/../etc' }).length).toBeGreaterThan(0);
|
|
128
|
+
expect(directoryProblems({ mode: 0o10000, path: '/opt/app' })).toContain(
|
|
129
|
+
'mode must be 0–0o7777',
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Host.Directory` — one directory on the host, declared: path, mode, owner, group.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ NOTHING IS ADOPTED WITHOUT `--adopt`: a directory already at the path reads as `Unowned`, and
|
|
5
|
+
* where the plan never asked, reconcile honours the same flag (docs/ownership.md).
|
|
6
|
+
* ★ IT IS NOT LINUX-ONLY. `mkdir`, `chmod`, `chown` and `rmdir` are the same argv on macOS, so this
|
|
7
|
+
* resource works through any HostRunner; it lives in the linux subpath because the gap it closes
|
|
8
|
+
* was found there (a first deploy into a new tree, which every file resource refuses to create).
|
|
9
|
+
*/
|
|
10
|
+
import { Resource } from 'alchemy';
|
|
11
|
+
import { Unowned } from 'alchemy/AdoptPolicy';
|
|
12
|
+
import { isResolved } from 'alchemy/Diff';
|
|
13
|
+
import * as Provider from 'alchemy/Provider';
|
|
14
|
+
import * as Effect from 'effect/Effect';
|
|
15
|
+
import { lift, resolvedString } from '../launchd/host-effect.ts';
|
|
16
|
+
import { HostRunnerService } from '../launchd/runner.ts';
|
|
17
|
+
import { adoptsAtApply } from '../ownership/adopt.ts';
|
|
18
|
+
import { noteUnfinished } from '../ownership/resume.ts';
|
|
19
|
+
import type { HostDirectoryAttributes, HostDirectoryProps } from './directory-lifecycle.ts';
|
|
20
|
+
import {
|
|
21
|
+
deleteDirectory,
|
|
22
|
+
diffDirectory,
|
|
23
|
+
readDirectory,
|
|
24
|
+
reconcileDirectory,
|
|
25
|
+
} from './directory-lifecycle.ts';
|
|
26
|
+
|
|
27
|
+
export type { HostDirectoryAttributes, HostDirectoryProps } from './directory-lifecycle.ts';
|
|
28
|
+
|
|
29
|
+
export interface HostDirectory extends Resource<
|
|
30
|
+
'Host.Directory',
|
|
31
|
+
HostDirectoryProps,
|
|
32
|
+
HostDirectoryAttributes
|
|
33
|
+
> {}
|
|
34
|
+
|
|
35
|
+
export const HostDirectory = Resource<HostDirectory>('Host.Directory');
|
|
36
|
+
|
|
37
|
+
export const HostDirectoryProvider = () =>
|
|
38
|
+
Provider.effect(
|
|
39
|
+
HostDirectory,
|
|
40
|
+
Effect.gen(function* () {
|
|
41
|
+
const runner = yield* HostRunnerService;
|
|
42
|
+
return HostDirectory.Provider.of({
|
|
43
|
+
list: () => Effect.succeed([]),
|
|
44
|
+
|
|
45
|
+
read: ({ olds, output }) =>
|
|
46
|
+
lift(async () => {
|
|
47
|
+
const found = await readDirectory(runner, olds.path);
|
|
48
|
+
if (found === undefined) return undefined;
|
|
49
|
+
return output === undefined ? Unowned(found) : found;
|
|
50
|
+
}),
|
|
51
|
+
|
|
52
|
+
diff: ({ instanceId, news, output }) => {
|
|
53
|
+
if (output === undefined) return noteUnfinished(instanceId);
|
|
54
|
+
if (isResolved(news)) return lift(() => diffDirectory(runner, news, output));
|
|
55
|
+
// ⛔ A new path is a replace even while the rest is unresolved: the engine's default
|
|
56
|
+
// update would create the new directory and leave the old one behind forever.
|
|
57
|
+
const path = resolvedString(news, 'path');
|
|
58
|
+
return Effect.succeed(
|
|
59
|
+
path !== undefined && path !== output.path ? { action: 'replace' as const } : undefined,
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
reconcile: ({ fqn, instanceId, news, output }) =>
|
|
64
|
+
Effect.flatMap(adoptsAtApply({ fqn, instanceId, output }), (adopt) =>
|
|
65
|
+
lift(() => reconcileDirectory(runner, news, output, adopt)),
|
|
66
|
+
),
|
|
67
|
+
|
|
68
|
+
delete: ({ output }) => lift(() => deleteDirectory(runner, output)),
|
|
69
|
+
});
|
|
70
|
+
}),
|
|
71
|
+
);
|