@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
|
@@ -0,0 +1,1554 @@
|
|
|
1
|
+
// src/linux/directory.ts
|
|
2
|
+
import { Resource } from "alchemy";
|
|
3
|
+
import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
|
|
4
|
+
import { isResolved } from "alchemy/Diff";
|
|
5
|
+
import * as Provider from "alchemy/Provider";
|
|
6
|
+
import * as Effect6 from "effect/Effect";
|
|
7
|
+
|
|
8
|
+
// src/launchd/host-effect.ts
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
var lift = (step) => Effect.tryPromise({
|
|
11
|
+
catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)),
|
|
12
|
+
try: step
|
|
13
|
+
});
|
|
14
|
+
var resolvedString = (news, key) => {
|
|
15
|
+
if (typeof news !== "object" || news === null)
|
|
16
|
+
return;
|
|
17
|
+
const value = news[key];
|
|
18
|
+
return typeof value === "string" ? value : undefined;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/launchd/runner.ts
|
|
22
|
+
import * as Context from "effect/Context";
|
|
23
|
+
import * as Layer from "effect/Layer";
|
|
24
|
+
|
|
25
|
+
class HostRunnerService extends Context.Service()("homeflare/launchd/HostRunner") {
|
|
26
|
+
}
|
|
27
|
+
var hostRunnerLayer = (runner) => Layer.succeed(HostRunnerService, runner);
|
|
28
|
+
var canActAsRoot = (runner) => runner.privileged || runner.effectiveUid() === 0;
|
|
29
|
+
|
|
30
|
+
// src/ownership/adopt.ts
|
|
31
|
+
import { AdoptPolicy } from "alchemy/AdoptPolicy";
|
|
32
|
+
import { AlchemyContext } from "alchemy/AlchemyContext";
|
|
33
|
+
import { Stack as Stack3 } from "alchemy/Stack";
|
|
34
|
+
import * as Effect5 from "effect/Effect";
|
|
35
|
+
import * as Option4 from "effect/Option";
|
|
36
|
+
|
|
37
|
+
// src/ownership/resume.ts
|
|
38
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
39
|
+
import { Artifacts } from "alchemy/Artifacts";
|
|
40
|
+
import * as Effect2 from "effect/Effect";
|
|
41
|
+
import * as Option from "effect/Option";
|
|
42
|
+
var PROVEN = "homeflare/ownership/resumes";
|
|
43
|
+
var UNFINISHED = "homeflare/ownership/unfinished";
|
|
44
|
+
var note = (key, instanceId) => Effect2.gen(function* () {
|
|
45
|
+
const bag = yield* Effect2.serviceOption(Artifacts);
|
|
46
|
+
if (Option.isSome(bag))
|
|
47
|
+
yield* bag.value.set(key, instanceId);
|
|
48
|
+
});
|
|
49
|
+
var noted = (key, instanceId) => Effect2.gen(function* () {
|
|
50
|
+
const bag = yield* Effect2.serviceOption(Artifacts);
|
|
51
|
+
if (Option.isNone(bag))
|
|
52
|
+
return false;
|
|
53
|
+
return (yield* bag.value.get(key)) === instanceId;
|
|
54
|
+
});
|
|
55
|
+
var noteUnfinished = (instanceId) => Effect2.as(note(UNFINISHED, instanceId), undefined);
|
|
56
|
+
var noteResume = (instanceId, recovered) => Effect2.gen(function* () {
|
|
57
|
+
yield* noteUnfinished(instanceId);
|
|
58
|
+
const unproven = () => Effect2.succeed(undefined);
|
|
59
|
+
const found = yield* recovered.pipe(Effect2.catch(unproven), Effect2.catchDefect(unproven));
|
|
60
|
+
if (found === undefined || found === null || Unowned.is(found))
|
|
61
|
+
return;
|
|
62
|
+
yield* note(PROVEN, instanceId);
|
|
63
|
+
});
|
|
64
|
+
var resumes = (instanceId) => noted(PROVEN, instanceId);
|
|
65
|
+
var unfinished = (instanceId) => noted(UNFINISHED, instanceId);
|
|
66
|
+
var provingResumes = (service) => {
|
|
67
|
+
const { diff, read } = service;
|
|
68
|
+
if (diff === undefined || read === undefined)
|
|
69
|
+
return service;
|
|
70
|
+
const proving = (input) => Effect2.gen(function* () {
|
|
71
|
+
if (input.output === undefined) {
|
|
72
|
+
yield* noteResume(input.instanceId, read({ ...input, output: undefined }));
|
|
73
|
+
}
|
|
74
|
+
return yield* diff(input);
|
|
75
|
+
});
|
|
76
|
+
return { ...service, diff: proving };
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// src/ownership/rows.ts
|
|
80
|
+
import { Stack as Stack2 } from "alchemy/Stack";
|
|
81
|
+
import { State, isActionState } from "alchemy/State/State";
|
|
82
|
+
import * as Effect4 from "effect/Effect";
|
|
83
|
+
import * as Option3 from "effect/Option";
|
|
84
|
+
|
|
85
|
+
// src/ownership/whole.ts
|
|
86
|
+
import { Stack } from "alchemy/Stack";
|
|
87
|
+
import * as Effect3 from "effect/Effect";
|
|
88
|
+
import * as Option2 from "effect/Option";
|
|
89
|
+
var absent = (value) => value === undefined || value === null;
|
|
90
|
+
var isPlainObject = (value) => {
|
|
91
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
92
|
+
return false;
|
|
93
|
+
const proto = Object.getPrototypeOf(value);
|
|
94
|
+
return proto === Object.prototype || proto === null;
|
|
95
|
+
};
|
|
96
|
+
var carries = (declared, recorded) => {
|
|
97
|
+
if (absent(declared))
|
|
98
|
+
return true;
|
|
99
|
+
if (absent(recorded))
|
|
100
|
+
return false;
|
|
101
|
+
if (Array.isArray(declared)) {
|
|
102
|
+
if (!Array.isArray(recorded))
|
|
103
|
+
return false;
|
|
104
|
+
return declared.every((each, index) => carries(each, recorded[index]));
|
|
105
|
+
}
|
|
106
|
+
if (isPlainObject(declared)) {
|
|
107
|
+
if (!isPlainObject(recorded))
|
|
108
|
+
return false;
|
|
109
|
+
return Object.entries(declared).every(([key, each]) => carries(each, recorded[key]));
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
};
|
|
113
|
+
var wholeDeclaration = (fqn, props) => Effect3.map(Effect3.serviceOption(Stack), (stack) => {
|
|
114
|
+
const declared = Option2.isSome(stack) ? stack.value.resources[fqn] : undefined;
|
|
115
|
+
return declared !== undefined && carries(declared.Props, props);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// src/ownership/rows.ts
|
|
119
|
+
var storeOf = Effect4.gen(function* () {
|
|
120
|
+
const stack = yield* Effect4.serviceOption(Stack2);
|
|
121
|
+
const state = yield* Effect4.serviceOption(State);
|
|
122
|
+
if (Option3.isNone(stack) || Option3.isNone(state))
|
|
123
|
+
return;
|
|
124
|
+
const store = yield* state.value;
|
|
125
|
+
const where = { stack: stack.value.name, stage: stack.value.stage };
|
|
126
|
+
return { resources: stack.value.resources, store, where };
|
|
127
|
+
});
|
|
128
|
+
var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect4.orElseSucceed(() => {
|
|
129
|
+
return;
|
|
130
|
+
}));
|
|
131
|
+
var generationOf = (row, instanceId) => {
|
|
132
|
+
let at = row;
|
|
133
|
+
while (typeof at === "object" && at !== null && !isActionState(at)) {
|
|
134
|
+
const generation = at;
|
|
135
|
+
if (generation.instanceId === instanceId)
|
|
136
|
+
return generation;
|
|
137
|
+
at = generation.old;
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
};
|
|
141
|
+
var recordedGeneration = (fqn, instanceId) => Effect4.gen(function* () {
|
|
142
|
+
const found = yield* storeOf;
|
|
143
|
+
if (found === undefined)
|
|
144
|
+
return;
|
|
145
|
+
for (const at of [fqn, ...found.resources[fqn]?.FormerFqns ?? []]) {
|
|
146
|
+
const generation = generationOf(yield* rowAt(found.store, found.where, at), instanceId);
|
|
147
|
+
if (generation !== undefined)
|
|
148
|
+
return generation;
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
});
|
|
152
|
+
var recordedInstance = (fqn, instanceId) => Effect4.gen(function* () {
|
|
153
|
+
const generation = yield* recordedGeneration(fqn, instanceId);
|
|
154
|
+
if (generation === undefined)
|
|
155
|
+
return "absent";
|
|
156
|
+
return (yield* wholeDeclaration(fqn, generation.props)) ? "whole" : "partial";
|
|
157
|
+
});
|
|
158
|
+
var isCreate = (fqn, instanceId) => Effect4.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
|
|
159
|
+
var forgetRefusedCreate = (fqn, instanceId) => Effect4.gen(function* () {
|
|
160
|
+
const found = yield* storeOf;
|
|
161
|
+
if (found === undefined)
|
|
162
|
+
return;
|
|
163
|
+
const row = yield* rowAt(found.store, found.where, fqn);
|
|
164
|
+
const mine = row;
|
|
165
|
+
if (mine?.status !== "creating" || mine.instanceId !== instanceId || mine.attr !== undefined) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
yield* found.store.delete({ ...found.where, fqn }).pipe(Effect4.ignore);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// src/ownership/adopt.ts
|
|
172
|
+
var adoptEnabled = (fqn) => Effect5.gen(function* () {
|
|
173
|
+
const stack = yield* Effect5.serviceOption(Stack3);
|
|
174
|
+
const scoped = Option4.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;
|
|
175
|
+
if (scoped !== undefined)
|
|
176
|
+
return scoped;
|
|
177
|
+
const policy = yield* Effect5.serviceOption(AdoptPolicy);
|
|
178
|
+
if (Option4.isSome(policy))
|
|
179
|
+
return policy.value;
|
|
180
|
+
const context = yield* Effect5.serviceOption(AlchemyContext);
|
|
181
|
+
return Option4.isSome(context) ? context.value.adopt : false;
|
|
182
|
+
});
|
|
183
|
+
var adoptable = (owner) => Effect5.gen(function* () {
|
|
184
|
+
if (yield* isCreate(owner.fqn, owner.instanceId))
|
|
185
|
+
return true;
|
|
186
|
+
return yield* unfinished(owner.instanceId);
|
|
187
|
+
});
|
|
188
|
+
var adoptsAtApply = (owner) => Effect5.gen(function* () {
|
|
189
|
+
if (!(yield* adoptEnabled(owner.fqn)))
|
|
190
|
+
return false;
|
|
191
|
+
return yield* adoptable(owner);
|
|
192
|
+
});
|
|
193
|
+
var refuseTakeover = (owner, what) => Effect5.gen(function* () {
|
|
194
|
+
if (owner.output !== undefined)
|
|
195
|
+
return;
|
|
196
|
+
if (yield* resumes(owner.instanceId))
|
|
197
|
+
return;
|
|
198
|
+
const may = yield* adoptable(owner);
|
|
199
|
+
if (may && (yield* adoptEnabled(owner.fqn)))
|
|
200
|
+
return;
|
|
201
|
+
yield* forgetRefusedCreate(owner.fqn, owner.instanceId);
|
|
202
|
+
const remedy = may ? "Deploy with --adopt (or wrap the resource in adopt(true)) to take it over, or remove it first." : "It is the new identity of a replace, which --adopt does not cover: remove it first.";
|
|
203
|
+
return yield* Effect5.die(new Error(`${what}: already exists, and this stack holds no state for it — the plan could not ask ` + "first (a prop was still an Output, a replace moved onto it, or it appeared after the " + `plan). Nothing was written. ${remedy}`));
|
|
204
|
+
});
|
|
205
|
+
var claimFor = (owner) => (what) => refuseTakeover(owner, what);
|
|
206
|
+
|
|
207
|
+
// src/launchd/secret-tripwire.ts
|
|
208
|
+
var ALTERNATIVE = "props are stored unencrypted in Alchemy state. Have a secret renderer (e.g. openbao-agent) " + "write it to a root-owned file and declare the file path instead (FOO_FILE=/path, --foo-file /path).";
|
|
209
|
+
var SECRET_NAME = /(?:^|_)(?:TOKEN|SECRET|PASSWORD|PASSWD|PASSPHRASE|API_?KEY|ACCESS_KEY|PRIVATE_KEY|SECRET_KEY|CREDENTIALS?)$/i;
|
|
210
|
+
var INLINE_FLAG = /^--?[a-z0-9-]*(?:token|password|passwd|passphrase|secret|api-?key)=./i;
|
|
211
|
+
var BARE_FLAG = /^--?(?:[a-z0-9]+-)*(?:token|password|passwd|passphrase|secret|api-?key)$/i;
|
|
212
|
+
var PRIVATE_KEY = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/;
|
|
213
|
+
var keyLeaves = (value, path) => {
|
|
214
|
+
if (typeof value === "string")
|
|
215
|
+
return PRIVATE_KEY.test(value) ? [path] : [];
|
|
216
|
+
if (Array.isArray(value)) {
|
|
217
|
+
return value.flatMap((item, index) => keyLeaves(item, `${path}[${String(index)}]`));
|
|
218
|
+
}
|
|
219
|
+
if (typeof value === "object" && value !== null) {
|
|
220
|
+
return Object.entries(value).flatMap(([key, item]) => keyLeaves(item, `${path}.${key}`));
|
|
221
|
+
}
|
|
222
|
+
return [];
|
|
223
|
+
};
|
|
224
|
+
var jobSecretProblems = (environment, argv, extraKeys) => {
|
|
225
|
+
const found = [];
|
|
226
|
+
for (const [name, value] of Object.entries(environment ?? {})) {
|
|
227
|
+
if (SECRET_NAME.test(name))
|
|
228
|
+
found.push(`environment ${name} looks like a secret: ${ALTERNATIVE}`);
|
|
229
|
+
else if (PRIVATE_KEY.test(value))
|
|
230
|
+
found.push(`environment ${name} holds a private key: ${ALTERNATIVE}`);
|
|
231
|
+
}
|
|
232
|
+
for (const [index, arg] of argv.entries()) {
|
|
233
|
+
if (INLINE_FLAG.test(arg)) {
|
|
234
|
+
found.push(`programArguments[${String(index)}] passes a secret inline: ${ALTERNATIVE}`);
|
|
235
|
+
} else if (BARE_FLAG.test(arg) && index + 1 < argv.length) {
|
|
236
|
+
found.push(`programArguments[${String(index + 1)}] follows a secret flag: ${ALTERNATIVE}`);
|
|
237
|
+
} else if (PRIVATE_KEY.test(arg)) {
|
|
238
|
+
found.push(`programArguments[${String(index)}] holds a private key: ${ALTERNATIVE}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
for (const path of keyLeaves(extraKeys ?? {}, "extraKeys")) {
|
|
242
|
+
found.push(`${path} holds a private key: ${ALTERNATIVE}`);
|
|
243
|
+
}
|
|
244
|
+
return found;
|
|
245
|
+
};
|
|
246
|
+
var fileSecretProblems = (content) => PRIVATE_KEY.test(content) ? [`content holds a private key: ${ALTERNATIVE}`] : [];
|
|
247
|
+
|
|
248
|
+
// src/launchd/host-file-form.ts
|
|
249
|
+
var DEFAULT_MODE = 420;
|
|
250
|
+
var identityProblems = (name, value) => {
|
|
251
|
+
if (value === undefined)
|
|
252
|
+
return [];
|
|
253
|
+
if (typeof value === "number") {
|
|
254
|
+
return Number.isSafeInteger(value) && value >= 0 ? [] : [`${name} must be a non-negative integer id`];
|
|
255
|
+
}
|
|
256
|
+
return /^[A-Za-z_][A-Za-z0-9_.-]*$/.test(value) || /^\d+$/.test(value) ? [] : [`${name} ${JSON.stringify(value)} is not a valid user or group name`];
|
|
257
|
+
};
|
|
258
|
+
var pathProblems = (path) => {
|
|
259
|
+
const found = [];
|
|
260
|
+
if (!path.startsWith("/"))
|
|
261
|
+
found.push("path must be absolute");
|
|
262
|
+
if (path.endsWith("/") || path.split("/").slice(1).some((part) => part === "" || part === "." || part === "..")) {
|
|
263
|
+
found.push('path must be normalised (no trailing slash, no empty, "." or ".." segments)');
|
|
264
|
+
}
|
|
265
|
+
if (path.includes("\x00"))
|
|
266
|
+
found.push("path contains NUL");
|
|
267
|
+
return found;
|
|
268
|
+
};
|
|
269
|
+
var fileProblems = (props) => {
|
|
270
|
+
const found = pathProblems(props.path);
|
|
271
|
+
const mode = props.mode ?? DEFAULT_MODE;
|
|
272
|
+
if (!Number.isSafeInteger(mode) || mode < 0 || mode > 4095)
|
|
273
|
+
found.push("mode must be 0–0o7777");
|
|
274
|
+
found.push(...identityProblems("owner", props.owner));
|
|
275
|
+
found.push(...identityProblems("group", props.group));
|
|
276
|
+
found.push(...fileSecretProblems(props.content));
|
|
277
|
+
return found;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/linux/directory-lifecycle.ts
|
|
281
|
+
var DEFAULT_DIR_MODE = 493;
|
|
282
|
+
var refuse = (path, message) => new Error(`Host.Directory ${path}: ${message}`);
|
|
283
|
+
var idProblems = (name, value) => {
|
|
284
|
+
if (value === undefined)
|
|
285
|
+
return [];
|
|
286
|
+
if (typeof value === "number") {
|
|
287
|
+
return Number.isSafeInteger(value) && value >= 0 ? [] : [`${name} must be a non-negative id`];
|
|
288
|
+
}
|
|
289
|
+
return /^[A-Za-z_][A-Za-z0-9_.-]*$/.test(value) || /^\d+$/.test(value) ? [] : [`${name} ${JSON.stringify(value)} is not a valid user or group name`];
|
|
290
|
+
};
|
|
291
|
+
var directoryProblems = (props) => {
|
|
292
|
+
const found = pathProblems(props.path);
|
|
293
|
+
const mode = props.mode ?? DEFAULT_DIR_MODE;
|
|
294
|
+
if (!Number.isSafeInteger(mode) || mode < 0 || mode > 4095)
|
|
295
|
+
found.push("mode must be 0–0o7777");
|
|
296
|
+
found.push(...idProblems("owner", props.owner), ...idProblems("group", props.group));
|
|
297
|
+
return found;
|
|
298
|
+
};
|
|
299
|
+
var numericId = (value) => typeof value === "number" ? value : /^\d+$/.test(value) ? Number(value) : undefined;
|
|
300
|
+
var desired = async (runner, props) => {
|
|
301
|
+
const found = directoryProblems(props);
|
|
302
|
+
if (found.length > 0)
|
|
303
|
+
throw refuse(props.path, found.join("; "));
|
|
304
|
+
let uid;
|
|
305
|
+
let gid;
|
|
306
|
+
if (props.owner !== undefined) {
|
|
307
|
+
uid = numericId(props.owner) ?? (await runner.lookupUser(String(props.owner)))?.uid;
|
|
308
|
+
if (uid === undefined)
|
|
309
|
+
throw refuse(props.path, `no user ${String(props.owner)} on this host`);
|
|
310
|
+
}
|
|
311
|
+
if (props.group !== undefined) {
|
|
312
|
+
gid = numericId(props.group) ?? await runner.lookupGroup(String(props.group));
|
|
313
|
+
if (gid === undefined)
|
|
314
|
+
throw refuse(props.path, `no group ${String(props.group)} on this host`);
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
mode: props.mode ?? DEFAULT_DIR_MODE,
|
|
318
|
+
...uid === undefined ? {} : { uid },
|
|
319
|
+
...gid === undefined ? {} : { gid }
|
|
320
|
+
};
|
|
321
|
+
};
|
|
322
|
+
var readDirectory = async (runner, path) => {
|
|
323
|
+
const stat = await runner.stat(path);
|
|
324
|
+
if (stat === undefined || stat.kind !== "directory")
|
|
325
|
+
return;
|
|
326
|
+
return { gid: stat.gid, mode: stat.mode, path, uid: stat.uid };
|
|
327
|
+
};
|
|
328
|
+
var octal = (mode) => mode.toString(8).padStart(3, "0");
|
|
329
|
+
var must = async (runner, path, argv) => {
|
|
330
|
+
const result = await runner.exec(argv);
|
|
331
|
+
if (result.exitCode !== 0) {
|
|
332
|
+
throw refuse(path, `${argv.join(" ")} -> ${String(result.exitCode)}: ${result.stderr.trim().slice(0, 300)}`);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
var reconcileDirectory = async (runner, props, output, adopt = false) => {
|
|
336
|
+
const want = await desired(runner, props);
|
|
337
|
+
if (want.uid !== undefined && want.uid !== runner.effectiveUid() && !canActAsRoot(runner)) {
|
|
338
|
+
throw refuse(props.path, `owner ${String(props.owner)} is not the deploying user; only root may chown. Deploy through ` + "a root destination or a privileged HostRunner. This provider never calls sudo itself.");
|
|
339
|
+
}
|
|
340
|
+
const stat = await runner.stat(props.path);
|
|
341
|
+
if (stat !== undefined && stat.kind !== "directory") {
|
|
342
|
+
throw refuse(props.path, `is a ${stat.kind}, not a directory`);
|
|
343
|
+
}
|
|
344
|
+
if (output === undefined && stat !== undefined && !adopt) {
|
|
345
|
+
const drifted = stat.mode !== want.mode || want.uid !== undefined && stat.uid !== want.uid;
|
|
346
|
+
if (drifted) {
|
|
347
|
+
throw refuse(props.path, "already exists with another mode or owner and is not this resource. Remove it, or " + "declare it as a new resource and deploy with --adopt.");
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (stat === undefined) {
|
|
351
|
+
const parent = props.path.slice(0, props.path.lastIndexOf("/")) || "/";
|
|
352
|
+
const above = await runner.stat(parent);
|
|
353
|
+
if (above === undefined || above.kind !== "directory") {
|
|
354
|
+
throw refuse(props.path, `its parent ${parent} does not exist; declare that directory too`);
|
|
355
|
+
}
|
|
356
|
+
await must(runner, props.path, ["mkdir", "-m", octal(want.mode), "--", props.path]);
|
|
357
|
+
} else if (stat.mode !== want.mode) {
|
|
358
|
+
await must(runner, props.path, ["chmod", octal(want.mode), "--", props.path]);
|
|
359
|
+
}
|
|
360
|
+
const owner = ownerMatches({ gid: stat?.gid ?? -1, mode: 0, path: props.path, uid: stat?.uid ?? -1 }, want) ? undefined : ownerArg(want);
|
|
361
|
+
if (owner !== undefined)
|
|
362
|
+
await must(runner, props.path, ["chown", owner, "--", props.path]);
|
|
363
|
+
const after = await readDirectory(runner, props.path);
|
|
364
|
+
if (after === undefined || after.mode !== want.mode || !ownerMatches(after, want)) {
|
|
365
|
+
throw refuse(props.path, "the directory on the host does not match the declaration");
|
|
366
|
+
}
|
|
367
|
+
return after;
|
|
368
|
+
};
|
|
369
|
+
var ownerArg = (want) => {
|
|
370
|
+
if (want.uid === undefined)
|
|
371
|
+
return want.gid === undefined ? undefined : `:${String(want.gid)}`;
|
|
372
|
+
return want.gid === undefined ? String(want.uid) : `${String(want.uid)}:${String(want.gid)}`;
|
|
373
|
+
};
|
|
374
|
+
var ownerMatches = (live, want) => (want.uid === undefined || live.uid === want.uid) && (want.gid === undefined || live.gid === want.gid);
|
|
375
|
+
var diffDirectory = async (runner, news, output) => {
|
|
376
|
+
const want = await desired(runner, news);
|
|
377
|
+
if (news.path !== output.path)
|
|
378
|
+
return { action: "replace" };
|
|
379
|
+
const live = await readDirectory(runner, news.path);
|
|
380
|
+
const converged = live !== undefined && live.mode === want.mode && ownerMatches(live, want) && output.mode === want.mode && ownerMatches(output, want);
|
|
381
|
+
if (converged)
|
|
382
|
+
return { action: "noop" };
|
|
383
|
+
await runner.checkWrite?.(news.path, want);
|
|
384
|
+
return { action: "update" };
|
|
385
|
+
};
|
|
386
|
+
var deleteDirectory = async (runner, output) => {
|
|
387
|
+
const stat = await runner.stat(output.path);
|
|
388
|
+
if (stat === undefined)
|
|
389
|
+
return;
|
|
390
|
+
if (stat.kind !== "directory")
|
|
391
|
+
throw refuse(output.path, `is now a ${stat.kind}; not removing it`);
|
|
392
|
+
await must(runner, output.path, ["rmdir", "--", output.path]);
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// src/linux/directory.ts
|
|
396
|
+
var HostDirectory = Resource("Host.Directory");
|
|
397
|
+
var HostDirectoryProvider = () => Provider.effect(HostDirectory, Effect6.gen(function* () {
|
|
398
|
+
const runner = yield* HostRunnerService;
|
|
399
|
+
return HostDirectory.Provider.of({
|
|
400
|
+
list: () => Effect6.succeed([]),
|
|
401
|
+
read: ({ olds, output }) => lift(async () => {
|
|
402
|
+
const found = await readDirectory(runner, olds.path);
|
|
403
|
+
if (found === undefined)
|
|
404
|
+
return;
|
|
405
|
+
return output === undefined ? Unowned2(found) : found;
|
|
406
|
+
}),
|
|
407
|
+
diff: ({ instanceId, news, output }) => {
|
|
408
|
+
if (output === undefined)
|
|
409
|
+
return noteUnfinished(instanceId);
|
|
410
|
+
if (isResolved(news))
|
|
411
|
+
return lift(() => diffDirectory(runner, news, output));
|
|
412
|
+
const path = resolvedString(news, "path");
|
|
413
|
+
return Effect6.succeed(path !== undefined && path !== output.path ? { action: "replace" } : undefined);
|
|
414
|
+
},
|
|
415
|
+
reconcile: ({ fqn, instanceId, news, output }) => Effect6.flatMap(adoptsAtApply({ fqn, instanceId, output }), (adopt) => lift(() => reconcileDirectory(runner, news, output, adopt))),
|
|
416
|
+
delete: ({ output }) => lift(() => deleteDirectory(runner, output))
|
|
417
|
+
});
|
|
418
|
+
}));
|
|
419
|
+
// src/linux/providers.ts
|
|
420
|
+
import * as Layer2 from "effect/Layer";
|
|
421
|
+
|
|
422
|
+
// src/linux/remote-file.ts
|
|
423
|
+
import { Resource as Resource2 } from "alchemy";
|
|
424
|
+
import { Unowned as Unowned3 } from "alchemy/AdoptPolicy";
|
|
425
|
+
import { isResolved as isResolved2 } from "alchemy/Diff";
|
|
426
|
+
import * as Provider2 from "alchemy/Provider";
|
|
427
|
+
import * as Effect7 from "effect/Effect";
|
|
428
|
+
|
|
429
|
+
// src/launchd/job-form.ts
|
|
430
|
+
import { createHash } from "node:crypto";
|
|
431
|
+
|
|
432
|
+
// src/launchd/plist.ts
|
|
433
|
+
class PlistError extends Error {
|
|
434
|
+
path;
|
|
435
|
+
constructor(path, detail) {
|
|
436
|
+
super(`plist ${path === "" ? "(root)" : path}: ${detail}`);
|
|
437
|
+
this.path = path;
|
|
438
|
+
this.name = "PlistError";
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
var HEADER = `<?xml version="1.0" encoding="UTF-8"?>
|
|
442
|
+
` + '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ' + `"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
443
|
+
` + `<plist version="1.0">
|
|
444
|
+
`;
|
|
445
|
+
var MAX_DEPTH = 32;
|
|
446
|
+
var FORBIDDEN = /[\x00-\x08\x0B\x0C\x0E-\x1F\uD800-\uDFFF\uFFFE\uFFFF]/u;
|
|
447
|
+
var escapeText = (text, path) => {
|
|
448
|
+
const bad = FORBIDDEN.exec(text);
|
|
449
|
+
if (bad !== null) {
|
|
450
|
+
const code = bad[0].codePointAt(0) ?? 0;
|
|
451
|
+
throw new PlistError(path, `U+${code.toString(16).toUpperCase().padStart(4, "0")} cannot appear in XML 1.0`);
|
|
452
|
+
}
|
|
453
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\r", " ");
|
|
454
|
+
};
|
|
455
|
+
var isPlainObject2 = (value) => {
|
|
456
|
+
const proto = Object.getPrototypeOf(value);
|
|
457
|
+
return proto === Object.prototype || proto === null;
|
|
458
|
+
};
|
|
459
|
+
var renderValue = (value, path, depth, out) => {
|
|
460
|
+
const pad = "\t".repeat(depth);
|
|
461
|
+
if (depth > MAX_DEPTH)
|
|
462
|
+
throw new PlistError(path, `nested deeper than ${String(MAX_DEPTH)}`);
|
|
463
|
+
if (typeof value === "string") {
|
|
464
|
+
out.push(`${pad}<string>${escapeText(value, path)}</string>`);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (typeof value === "boolean") {
|
|
468
|
+
out.push(`${pad}<${value ? "true" : "false"}/>`);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (typeof value === "number") {
|
|
472
|
+
if (!Number.isSafeInteger(value)) {
|
|
473
|
+
throw new PlistError(path, `${String(value)} is not a safe integer (no <real> support)`);
|
|
474
|
+
}
|
|
475
|
+
out.push(`${pad}<integer>${String(value === 0 ? 0 : value)}</integer>`);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (Array.isArray(value)) {
|
|
479
|
+
if (value.length === 0) {
|
|
480
|
+
out.push(`${pad}<array/>`);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
out.push(`${pad}<array>`);
|
|
484
|
+
for (const [index, item] of value.entries()) {
|
|
485
|
+
renderValue(item, `${path}[${String(index)}]`, depth + 1, out);
|
|
486
|
+
}
|
|
487
|
+
out.push(`${pad}</array>`);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (typeof value === "object" && value !== null && isPlainObject2(value)) {
|
|
491
|
+
renderDict(value, path, depth, out);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const kind = value === null ? "null" : typeof value;
|
|
495
|
+
throw new PlistError(path, `${kind} has no plist form — omit the key instead`);
|
|
496
|
+
};
|
|
497
|
+
var renderDict = (dict, path, depth, out) => {
|
|
498
|
+
const pad = "\t".repeat(depth);
|
|
499
|
+
const keys = Object.keys(dict).sort();
|
|
500
|
+
if (keys.length === 0) {
|
|
501
|
+
out.push(`${pad}<dict/>`);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
out.push(`${pad}<dict>`);
|
|
505
|
+
for (const key of keys) {
|
|
506
|
+
const child = path === "" ? key : `${path}.${key}`;
|
|
507
|
+
out.push(`${pad} <key>${escapeText(key, child)}</key>`);
|
|
508
|
+
renderValue(dict[key], child, depth + 1, out);
|
|
509
|
+
}
|
|
510
|
+
out.push(`${pad}</dict>`);
|
|
511
|
+
};
|
|
512
|
+
var renderPlist = (dict) => {
|
|
513
|
+
if (typeof dict !== "object" || dict === null || Array.isArray(dict) || !isPlainObject2(dict)) {
|
|
514
|
+
throw new PlistError("", "the top level of a launchd plist must be a dict");
|
|
515
|
+
}
|
|
516
|
+
const out = [];
|
|
517
|
+
renderDict(dict, "", 0, out);
|
|
518
|
+
return `${HEADER}${out.join(`
|
|
519
|
+
`)}
|
|
520
|
+
</plist>
|
|
521
|
+
`;
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
// src/launchd/job-form.ts
|
|
525
|
+
var parseDomain = (domain) => {
|
|
526
|
+
if (domain === "system")
|
|
527
|
+
return { kind: "system" };
|
|
528
|
+
const match = /^gui\/(\d+)$/.exec(domain);
|
|
529
|
+
return match?.[1] === undefined ? undefined : { kind: "gui", uid: Number(match[1]) };
|
|
530
|
+
};
|
|
531
|
+
var serviceTarget = (label, domain) => `${domain}/${label}`;
|
|
532
|
+
var plistPathFor = (label, domain, home) => {
|
|
533
|
+
if (domain.kind === "system")
|
|
534
|
+
return `/Library/LaunchDaemons/${label}.plist`;
|
|
535
|
+
if (home === undefined || !home.startsWith("/")) {
|
|
536
|
+
throw new Error(`gui/${String(domain.uid)}: no home directory for that uid on this host`);
|
|
537
|
+
}
|
|
538
|
+
return `${home}/Library/LaunchAgents/${label}.plist`;
|
|
539
|
+
};
|
|
540
|
+
var OWNED_KEYS = [
|
|
541
|
+
"EnvironmentVariables",
|
|
542
|
+
"GroupName",
|
|
543
|
+
"KeepAlive",
|
|
544
|
+
"Label",
|
|
545
|
+
"Program",
|
|
546
|
+
"ProgramArguments",
|
|
547
|
+
"RunAtLoad",
|
|
548
|
+
"StandardErrorPath",
|
|
549
|
+
"StandardOutPath",
|
|
550
|
+
"StartCalendarInterval",
|
|
551
|
+
"StartInterval",
|
|
552
|
+
"ThrottleInterval",
|
|
553
|
+
"UserName",
|
|
554
|
+
"WorkingDirectory"
|
|
555
|
+
];
|
|
556
|
+
var calendarDict = (entry) => {
|
|
557
|
+
const dict = {};
|
|
558
|
+
if (entry.minute !== undefined)
|
|
559
|
+
dict["Minute"] = entry.minute;
|
|
560
|
+
if (entry.hour !== undefined)
|
|
561
|
+
dict["Hour"] = entry.hour;
|
|
562
|
+
if (entry.day !== undefined)
|
|
563
|
+
dict["Day"] = entry.day;
|
|
564
|
+
if (entry.weekday !== undefined)
|
|
565
|
+
dict["Weekday"] = entry.weekday;
|
|
566
|
+
if (entry.month !== undefined)
|
|
567
|
+
dict["Month"] = entry.month;
|
|
568
|
+
return dict;
|
|
569
|
+
};
|
|
570
|
+
var keepAliveValue = (keepAlive) => {
|
|
571
|
+
if (typeof keepAlive === "boolean")
|
|
572
|
+
return keepAlive;
|
|
573
|
+
const dict = {};
|
|
574
|
+
if (keepAlive.successfulExit !== undefined)
|
|
575
|
+
dict["SuccessfulExit"] = keepAlive.successfulExit;
|
|
576
|
+
if (keepAlive.crashed !== undefined)
|
|
577
|
+
dict["Crashed"] = keepAlive.crashed;
|
|
578
|
+
return dict;
|
|
579
|
+
};
|
|
580
|
+
var jobPlistDict = (props) => {
|
|
581
|
+
const dict = { ...props.extraKeys };
|
|
582
|
+
dict["Label"] = props.label;
|
|
583
|
+
dict["ProgramArguments"] = [...props.programArguments];
|
|
584
|
+
const env = props.environment ?? {};
|
|
585
|
+
if (Object.keys(env).length > 0)
|
|
586
|
+
dict["EnvironmentVariables"] = { ...env };
|
|
587
|
+
if (props.runAtLoad !== undefined)
|
|
588
|
+
dict["RunAtLoad"] = props.runAtLoad;
|
|
589
|
+
if (props.keepAlive !== undefined)
|
|
590
|
+
dict["KeepAlive"] = keepAliveValue(props.keepAlive);
|
|
591
|
+
if (props.startInterval !== undefined)
|
|
592
|
+
dict["StartInterval"] = props.startInterval;
|
|
593
|
+
const calendar = props.startCalendarInterval;
|
|
594
|
+
if (calendar !== undefined) {
|
|
595
|
+
dict["StartCalendarInterval"] = Array.isArray(calendar) ? calendar.map(calendarDict) : calendarDict(calendar);
|
|
596
|
+
}
|
|
597
|
+
if (props.throttleInterval !== undefined)
|
|
598
|
+
dict["ThrottleInterval"] = props.throttleInterval;
|
|
599
|
+
if (props.standardOutPath !== undefined)
|
|
600
|
+
dict["StandardOutPath"] = props.standardOutPath;
|
|
601
|
+
if (props.standardErrorPath !== undefined)
|
|
602
|
+
dict["StandardErrorPath"] = props.standardErrorPath;
|
|
603
|
+
if (props.workingDirectory !== undefined)
|
|
604
|
+
dict["WorkingDirectory"] = props.workingDirectory;
|
|
605
|
+
if (props.userName !== undefined)
|
|
606
|
+
dict["UserName"] = props.userName;
|
|
607
|
+
if (props.groupName !== undefined)
|
|
608
|
+
dict["GroupName"] = props.groupName;
|
|
609
|
+
return dict;
|
|
610
|
+
};
|
|
611
|
+
var sha256Hex = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
612
|
+
var renderJob = (props) => {
|
|
613
|
+
const text = renderPlist(jobPlistDict(props));
|
|
614
|
+
const bytes = new TextEncoder().encode(text);
|
|
615
|
+
return { bytes, sha256: sha256Hex(bytes), text };
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
// src/linux/region.ts
|
|
619
|
+
var DEFAULT_COMMENT = "#";
|
|
620
|
+
var tokens = (spec) => {
|
|
621
|
+
const comment = spec.comment ?? DEFAULT_COMMENT;
|
|
622
|
+
return {
|
|
623
|
+
begin: `${comment} BEGIN ${spec.name}`,
|
|
624
|
+
end: `${comment} END ${spec.name}`
|
|
625
|
+
};
|
|
626
|
+
};
|
|
627
|
+
var regionProblems = (spec) => {
|
|
628
|
+
const found = [];
|
|
629
|
+
const comment = spec.comment ?? DEFAULT_COMMENT;
|
|
630
|
+
if (!/^\S{1,4}$/.test(comment) || comment.includes("\x00")) {
|
|
631
|
+
found.push("region comment must be 1–4 non-space characters");
|
|
632
|
+
}
|
|
633
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9 ._:@/-]{0,79}$/.test(spec.name)) {
|
|
634
|
+
found.push("region name must be 1–80 printable characters and start alphanumeric");
|
|
635
|
+
}
|
|
636
|
+
return found;
|
|
637
|
+
};
|
|
638
|
+
var refuse2 = (message) => new Error(`managed region: ${message}`);
|
|
639
|
+
var duplicate = (what, at, line) => refuse2(`${what} for ${JSON.stringify(line)} (lines ${at.map((index) => index + 1).join(", ")}). ` + "Remove the duplicate; this resource will not choose between them.");
|
|
640
|
+
var locate = (lines, spec) => {
|
|
641
|
+
const { begin, end } = tokens(spec);
|
|
642
|
+
const begins = lines.flatMap((line, index) => line === begin ? [index] : []);
|
|
643
|
+
const ends = lines.flatMap((line, index) => line === end ? [index] : []);
|
|
644
|
+
if (begins.length > 1)
|
|
645
|
+
throw duplicate("two BEGIN markers", begins, begin);
|
|
646
|
+
if (ends.length > 1)
|
|
647
|
+
throw duplicate("two END markers", ends, end);
|
|
648
|
+
const [from] = begins;
|
|
649
|
+
const [to] = ends;
|
|
650
|
+
if (from === undefined && to === undefined)
|
|
651
|
+
return;
|
|
652
|
+
if (from === undefined || to === undefined) {
|
|
653
|
+
throw refuse2(`${JSON.stringify(spec.name)} has a ${from === undefined ? "END" : "BEGIN"} marker and no ` + "match. Repair the file by hand — a half-marked block means an edit inside it.");
|
|
654
|
+
}
|
|
655
|
+
if (to < from)
|
|
656
|
+
throw refuse2(`${JSON.stringify(spec.name)} has its END marker before its BEGIN`);
|
|
657
|
+
return { from, to };
|
|
658
|
+
};
|
|
659
|
+
var spliceRegion = (file, spec, body) => {
|
|
660
|
+
const { begin, end } = tokens(spec);
|
|
661
|
+
const block = [begin, ...body === "" ? [] : body.replace(/\n$/, "").split(`
|
|
662
|
+
`), end];
|
|
663
|
+
const lines = file.split(`
|
|
664
|
+
`);
|
|
665
|
+
const found = locate(lines, spec);
|
|
666
|
+
if (found !== undefined) {
|
|
667
|
+
return [...lines.slice(0, found.from), ...block, ...lines.slice(found.to + 1)].join(`
|
|
668
|
+
`);
|
|
669
|
+
}
|
|
670
|
+
const prefix = file === "" || file.endsWith(`
|
|
671
|
+
`) ? file : `${file}
|
|
672
|
+
`;
|
|
673
|
+
return `${prefix}${block.join(`
|
|
674
|
+
`)}
|
|
675
|
+
`;
|
|
676
|
+
};
|
|
677
|
+
var readRegion = (file, spec) => {
|
|
678
|
+
const lines = file.split(`
|
|
679
|
+
`);
|
|
680
|
+
const found = locate(lines, spec);
|
|
681
|
+
if (found === undefined)
|
|
682
|
+
return;
|
|
683
|
+
const body = lines.slice(found.from + 1, found.to);
|
|
684
|
+
return body.length === 0 ? "" : `${body.join(`
|
|
685
|
+
`)}
|
|
686
|
+
`;
|
|
687
|
+
};
|
|
688
|
+
var removeRegion = (file, spec) => {
|
|
689
|
+
const lines = file.split(`
|
|
690
|
+
`);
|
|
691
|
+
const found = locate(lines, spec);
|
|
692
|
+
if (found === undefined)
|
|
693
|
+
return file;
|
|
694
|
+
return [...lines.slice(0, found.from), ...lines.slice(found.to + 1)].join(`
|
|
695
|
+
`);
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
// src/linux/remote-file-form.ts
|
|
699
|
+
var DEFAULT_MODE2 = 420;
|
|
700
|
+
var identityProblems2 = (name, value) => {
|
|
701
|
+
if (value === undefined)
|
|
702
|
+
return [];
|
|
703
|
+
if (typeof value === "number") {
|
|
704
|
+
return Number.isSafeInteger(value) && value >= 0 ? [] : [`${name} must be a non-negative integer id`];
|
|
705
|
+
}
|
|
706
|
+
return /^[A-Za-z_][A-Za-z0-9_.-]*$/.test(value) || /^\d+$/.test(value) ? [] : [`${name} ${JSON.stringify(value)} is not a valid user or group name`];
|
|
707
|
+
};
|
|
708
|
+
var fileProblems2 = (props) => {
|
|
709
|
+
const found = pathProblems(props.path);
|
|
710
|
+
const mode = props.mode ?? DEFAULT_MODE2;
|
|
711
|
+
if (!Number.isSafeInteger(mode) || mode < 0 || mode > 4095)
|
|
712
|
+
found.push("mode must be 0–0o7777");
|
|
713
|
+
found.push(...identityProblems2("owner", props.owner));
|
|
714
|
+
found.push(...identityProblems2("group", props.group));
|
|
715
|
+
found.push(...fileSecretProblems(props.content));
|
|
716
|
+
if (props.region !== undefined) {
|
|
717
|
+
found.push(...regionProblems(props.region));
|
|
718
|
+
if (props.create !== true && (props.mode !== undefined || props.owner !== undefined)) {
|
|
719
|
+
found.push("mode and owner apply only to a file this resource creates; drop them, or set create: true");
|
|
720
|
+
}
|
|
721
|
+
} else if (props.create === true) {
|
|
722
|
+
found.push("create is for region mode only; a whole-file declaration always creates the file");
|
|
723
|
+
}
|
|
724
|
+
return found;
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// src/linux/remote-file-plan.ts
|
|
728
|
+
var decoder = new TextDecoder;
|
|
729
|
+
var encoder = new TextEncoder;
|
|
730
|
+
var refuse3 = (path, message) => new Error(`Remote.File ${path}: ${message}`);
|
|
731
|
+
var numericId2 = (value) => typeof value === "number" ? value : /^\d+$/.test(value) ? Number(value) : undefined;
|
|
732
|
+
var identity = async (runner, props) => {
|
|
733
|
+
const found = fileProblems2(props);
|
|
734
|
+
if (found.length > 0)
|
|
735
|
+
throw refuse3(props.path, found.join("; "));
|
|
736
|
+
let uid;
|
|
737
|
+
let gid;
|
|
738
|
+
if (props.owner !== undefined) {
|
|
739
|
+
uid = numericId2(props.owner) ?? (await runner.lookupUser(String(props.owner)))?.uid;
|
|
740
|
+
if (uid === undefined)
|
|
741
|
+
throw refuse3(props.path, `no user ${String(props.owner)} on this host`);
|
|
742
|
+
}
|
|
743
|
+
if (props.group !== undefined) {
|
|
744
|
+
gid = numericId2(props.group) ?? await runner.lookupGroup(String(props.group));
|
|
745
|
+
if (gid === undefined)
|
|
746
|
+
throw refuse3(props.path, `no group ${String(props.group)} on this host`);
|
|
747
|
+
}
|
|
748
|
+
return { ...uid === undefined ? {} : { uid }, ...gid === undefined ? {} : { gid } };
|
|
749
|
+
};
|
|
750
|
+
var digestOf = (text) => sha256Hex(encoder.encode(text));
|
|
751
|
+
var ownedDigest = (file, region) => {
|
|
752
|
+
if (file === undefined)
|
|
753
|
+
return;
|
|
754
|
+
if (region === undefined)
|
|
755
|
+
return digestOf(file);
|
|
756
|
+
const body = readRegion(file, region);
|
|
757
|
+
return body === undefined ? undefined : digestOf(body);
|
|
758
|
+
};
|
|
759
|
+
var readFileAttributes = async (runner, path, region) => {
|
|
760
|
+
const stat = await runner.stat(path);
|
|
761
|
+
if (stat === undefined)
|
|
762
|
+
return;
|
|
763
|
+
const bytes = stat.kind === "file" ? await runner.readFile(path) : undefined;
|
|
764
|
+
const text = bytes === undefined ? undefined : decoder.decode(bytes);
|
|
765
|
+
return {
|
|
766
|
+
contentSha256: ownedDigest(text, region) ?? "",
|
|
767
|
+
gid: stat.gid,
|
|
768
|
+
mode: stat.mode,
|
|
769
|
+
path,
|
|
770
|
+
...region === undefined ? {} : { region },
|
|
771
|
+
sha256: bytes === undefined ? "" : sha256Hex(bytes),
|
|
772
|
+
size: stat.size,
|
|
773
|
+
uid: stat.uid
|
|
774
|
+
};
|
|
775
|
+
};
|
|
776
|
+
var matches = (stat, write) => stat !== undefined && stat.mode === write.mode && (write.uid === undefined || stat.uid === write.uid) && (write.gid === undefined || stat.gid === write.gid);
|
|
777
|
+
var planWrite = async (runner, props, want) => {
|
|
778
|
+
const stat = await runner.stat(props.path);
|
|
779
|
+
if (stat !== undefined && stat.kind !== "file") {
|
|
780
|
+
throw refuse3(props.path, `is a ${stat.kind}; remove it from whatever owns it before declaring it here`);
|
|
781
|
+
}
|
|
782
|
+
const current = stat === undefined ? undefined : await runner.readFile(props.path);
|
|
783
|
+
const before = current === undefined ? undefined : decoder.decode(current);
|
|
784
|
+
const declaredMode = props.mode ?? DEFAULT_MODE2;
|
|
785
|
+
if (props.region === undefined) {
|
|
786
|
+
const write = { mode: declaredMode, ...want };
|
|
787
|
+
return {
|
|
788
|
+
before,
|
|
789
|
+
bytes: encoder.encode(props.content),
|
|
790
|
+
converged: before === props.content && matches(stat, write),
|
|
791
|
+
write
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
if (before === undefined) {
|
|
795
|
+
if (props.create !== true) {
|
|
796
|
+
throw refuse3(props.path, "does not exist, and a managed region does not create the file it lives in. Check the " + "path, declare the file itself, or pass create: true.");
|
|
797
|
+
}
|
|
798
|
+
return {
|
|
799
|
+
before,
|
|
800
|
+
bytes: encoder.encode(spliceRegion("", props.region, props.content)),
|
|
801
|
+
converged: false,
|
|
802
|
+
write: { mode: declaredMode, ...want }
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
const next = spliceRegion(before, props.region, props.content);
|
|
806
|
+
return {
|
|
807
|
+
before,
|
|
808
|
+
bytes: encoder.encode(next),
|
|
809
|
+
converged: next === before,
|
|
810
|
+
write: { gid: stat?.gid ?? 0, mode: stat?.mode ?? declaredMode, uid: stat?.uid ?? 0 }
|
|
811
|
+
};
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
// src/linux/remote-file-lifecycle.ts
|
|
815
|
+
var diffFile = async (runner, news, output) => {
|
|
816
|
+
const want = await identity(runner, news);
|
|
817
|
+
if (news.path !== output.path) {
|
|
818
|
+
await runner.checkWrite?.(news.path, { mode: news.mode ?? DEFAULT_MODE2, ...want });
|
|
819
|
+
return { action: "replace" };
|
|
820
|
+
}
|
|
821
|
+
const planned = await planWrite(runner, news, want);
|
|
822
|
+
if (planned.converged && output.contentSha256 === digestOf(news.content)) {
|
|
823
|
+
return { action: "noop" };
|
|
824
|
+
}
|
|
825
|
+
await runner.checkWrite?.(news.path, planned.write);
|
|
826
|
+
return { action: "update" };
|
|
827
|
+
};
|
|
828
|
+
var reconcileFile = async (runner, props, output, adopt = false) => {
|
|
829
|
+
const want = await identity(runner, props);
|
|
830
|
+
if (want.uid !== undefined && want.uid !== runner.effectiveUid() && !canActAsRoot(runner)) {
|
|
831
|
+
throw refuse3(props.path, `owner ${String(props.owner)} is not the deploying user; only root may chown. Deploy through ` + "a root destination or a privileged HostRunner. This provider never calls sudo itself.");
|
|
832
|
+
}
|
|
833
|
+
const moved = output !== undefined && output.path !== props.path;
|
|
834
|
+
const prior = moved ? undefined : output;
|
|
835
|
+
const planned = await planWrite(runner, props, want);
|
|
836
|
+
const live = ownedDigest(planned.before, props.region);
|
|
837
|
+
const declared = digestOf(props.content);
|
|
838
|
+
if (prior === undefined && live !== undefined && live !== declared && !(adopt && output === undefined)) {
|
|
839
|
+
throw refuse3(props.path, `${props.region === undefined ? "already exists" : "already carries that managed region"}` + " and is not this resource. Remove it, or declare it as a new resource and deploy --adopt.");
|
|
840
|
+
}
|
|
841
|
+
if (!planned.converged)
|
|
842
|
+
await runner.writeFileAtomic(props.path, planned.bytes, planned.write);
|
|
843
|
+
const after = await readFileAttributes(runner, props.path, props.region);
|
|
844
|
+
if (after === undefined || after.sha256 !== sha256Hex(planned.bytes) || after.contentSha256 !== declared) {
|
|
845
|
+
if (planned.before === undefined && props.region === undefined) {
|
|
846
|
+
await runner.removeFile(props.path).catch(() => {
|
|
847
|
+
return;
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
throw refuse3(props.path, "the write returned but the file on disk does not match the declaration");
|
|
851
|
+
}
|
|
852
|
+
if (moved && output !== undefined)
|
|
853
|
+
await deleteFile(runner, output);
|
|
854
|
+
return after;
|
|
855
|
+
};
|
|
856
|
+
var deleteFile = async (runner, output) => {
|
|
857
|
+
const stat = await runner.stat(output.path);
|
|
858
|
+
if (stat === undefined)
|
|
859
|
+
return;
|
|
860
|
+
if (stat.kind !== "file")
|
|
861
|
+
throw refuse3(output.path, `is now a ${stat.kind}; not touching it`);
|
|
862
|
+
if (output.region === undefined) {
|
|
863
|
+
await runner.removeFile(output.path);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const bytes = await runner.readFile(output.path);
|
|
867
|
+
if (bytes === undefined)
|
|
868
|
+
return;
|
|
869
|
+
const before = decoder.decode(bytes);
|
|
870
|
+
const next = removeRegion(before, output.region);
|
|
871
|
+
if (next === before)
|
|
872
|
+
return;
|
|
873
|
+
await runner.writeFileAtomic(output.path, encoder.encode(next), {
|
|
874
|
+
gid: stat.gid,
|
|
875
|
+
mode: stat.mode,
|
|
876
|
+
uid: stat.uid
|
|
877
|
+
});
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
// src/linux/remote-file.ts
|
|
881
|
+
var RemoteFile = Resource2("Remote.File");
|
|
882
|
+
var RemoteFileProvider = () => Provider2.effect(RemoteFile, Effect7.gen(function* () {
|
|
883
|
+
const runner = yield* HostRunnerService;
|
|
884
|
+
return RemoteFile.Provider.of({
|
|
885
|
+
list: () => Effect7.succeed([]),
|
|
886
|
+
read: ({ olds, output }) => lift(async () => {
|
|
887
|
+
const found = await readFileAttributes(runner, olds.path, olds.region);
|
|
888
|
+
if (found === undefined)
|
|
889
|
+
return;
|
|
890
|
+
if (olds.region !== undefined && found.contentSha256 === "")
|
|
891
|
+
return;
|
|
892
|
+
return output === undefined ? Unowned3(found) : found;
|
|
893
|
+
}),
|
|
894
|
+
diff: ({ instanceId, news, output }) => {
|
|
895
|
+
if (output === undefined)
|
|
896
|
+
return noteUnfinished(instanceId);
|
|
897
|
+
if (isResolved2(news))
|
|
898
|
+
return lift(() => diffFile(runner, news, output));
|
|
899
|
+
const path = resolvedString(news, "path");
|
|
900
|
+
return Effect7.succeed(path !== undefined && path !== output.path ? { action: "replace" } : undefined);
|
|
901
|
+
},
|
|
902
|
+
reconcile: ({ fqn, instanceId, news, output }) => Effect7.flatMap(adoptsAtApply({ fqn, instanceId, output }), (adopt) => lift(() => reconcileFile(runner, news, output, adopt))),
|
|
903
|
+
delete: ({ output }) => lift(() => deleteFile(runner, output))
|
|
904
|
+
});
|
|
905
|
+
}));
|
|
906
|
+
|
|
907
|
+
// src/linux/unit.ts
|
|
908
|
+
import { Resource as Resource3 } from "alchemy";
|
|
909
|
+
import * as Provider3 from "alchemy/Provider";
|
|
910
|
+
import * as Effect9 from "effect/Effect";
|
|
911
|
+
|
|
912
|
+
// src/linux/unit-handlers.ts
|
|
913
|
+
import { Unowned as Unowned4 } from "alchemy/AdoptPolicy";
|
|
914
|
+
import { isResolved as isResolved3 } from "alchemy/Diff";
|
|
915
|
+
import * as Effect8 from "effect/Effect";
|
|
916
|
+
|
|
917
|
+
// src/linux/unit-form.ts
|
|
918
|
+
var DEFAULT_UNIT_DIRECTORY = "/etc/systemd/system";
|
|
919
|
+
var UNIT_SEARCH_DIRECTORIES = [
|
|
920
|
+
"/etc/systemd/system",
|
|
921
|
+
"/run/systemd/system",
|
|
922
|
+
"/usr/local/lib/systemd/system",
|
|
923
|
+
"/usr/lib/systemd/system",
|
|
924
|
+
"/lib/systemd/system"
|
|
925
|
+
];
|
|
926
|
+
var UNIT_WRITE = { gid: 0, mode: 420, uid: 0 };
|
|
927
|
+
var digestOf2 = (text) => sha256Hex(new TextEncoder().encode(text));
|
|
928
|
+
var configDigest = (restartOn) => digestOf2((restartOn ?? []).join(`
|
|
929
|
+
`));
|
|
930
|
+
var renderUnit = (sections) => `${sections.map((section) => [`[${section.name}]`, ...section.lines.map(([key, value]) => `${key}=${value}`)].join(`
|
|
931
|
+
`)).join(`
|
|
932
|
+
|
|
933
|
+
`)}
|
|
934
|
+
`;
|
|
935
|
+
var unitText = (props) => props.content ?? renderUnit(props.sections ?? []);
|
|
936
|
+
var unitPathFor = (props) => `${props.directory ?? DEFAULT_UNIT_DIRECTORY}/${props.name}`;
|
|
937
|
+
var NAME = /^[A-Za-z0-9][A-Za-z0-9_.@:-]*\.([a-z]+)$/;
|
|
938
|
+
var TYPES = new Set([
|
|
939
|
+
"automount",
|
|
940
|
+
"device",
|
|
941
|
+
"mount",
|
|
942
|
+
"path",
|
|
943
|
+
"scope",
|
|
944
|
+
"service",
|
|
945
|
+
"slice",
|
|
946
|
+
"socket",
|
|
947
|
+
"swap",
|
|
948
|
+
"target",
|
|
949
|
+
"timer"
|
|
950
|
+
]);
|
|
951
|
+
var INSTALL_KEYS = new Set(["WantedBy", "RequiredBy", "UpsertedBy", "Also", "Alias"]);
|
|
952
|
+
var unitProblems = (props, expect) => {
|
|
953
|
+
const found = [];
|
|
954
|
+
const type = NAME.exec(props.name)?.[1];
|
|
955
|
+
if (type === undefined || !TYPES.has(type)) {
|
|
956
|
+
found.push(`name must be <unit>.<type> with a systemd unit type, got ${JSON.stringify(props.name)}`);
|
|
957
|
+
} else if (expect !== undefined && type !== expect) {
|
|
958
|
+
found.push(`this resource declares a .${expect} unit; ${props.name} is a .${type}`);
|
|
959
|
+
}
|
|
960
|
+
if (props.content === undefined === (props.sections === undefined)) {
|
|
961
|
+
found.push("declare exactly one of content or sections");
|
|
962
|
+
}
|
|
963
|
+
const directory = props.directory ?? DEFAULT_UNIT_DIRECTORY;
|
|
964
|
+
if (!directory.startsWith("/") || directory.endsWith("/") || directory.includes("/..")) {
|
|
965
|
+
found.push("directory must be an absolute, normalised path with no trailing slash");
|
|
966
|
+
}
|
|
967
|
+
found.push(...textProblems(unitText(props)));
|
|
968
|
+
if (props.enabled !== false && !UNIT_SEARCH_DIRECTORIES.includes(directory)) {
|
|
969
|
+
found.push(`an enabled unit must live where systemd looks for it by name (${UNIT_SEARCH_DIRECTORIES.join(", ")}); ` + `${directory} is not one of them. Move it, or declare enabled: false.`);
|
|
970
|
+
}
|
|
971
|
+
if (props.enabled !== false && !hasInstall(props)) {
|
|
972
|
+
found.push("enabled units need an [Install] section with WantedBy/RequiredBy/Also/Alias; without one " + 'systemd calls the unit "static" and `systemctl enable` has nothing to link. Add one, or ' + "declare enabled: false and let something else pull it in.");
|
|
973
|
+
}
|
|
974
|
+
if (type === "timer" && !unitText(props).includes("[Timer]")) {
|
|
975
|
+
found.push("a .timer needs a [Timer] section");
|
|
976
|
+
}
|
|
977
|
+
return found;
|
|
978
|
+
};
|
|
979
|
+
var hasInstall = (props) => {
|
|
980
|
+
const text = unitText(props);
|
|
981
|
+
if (!text.includes("[Install]"))
|
|
982
|
+
return false;
|
|
983
|
+
const after = text.slice(text.indexOf("[Install]"));
|
|
984
|
+
return [...INSTALL_KEYS].some((key) => new RegExp(`^${key}=`, "m").test(after));
|
|
985
|
+
};
|
|
986
|
+
var textProblems = (text) => {
|
|
987
|
+
const found = [];
|
|
988
|
+
if (text.trim() === "")
|
|
989
|
+
found.push("the unit file is empty");
|
|
990
|
+
if (text.includes("\x00"))
|
|
991
|
+
found.push("the unit file contains NUL");
|
|
992
|
+
if (!/^\s*\[[A-Za-z]+]/.test(text))
|
|
993
|
+
found.push("a unit file must begin with a [Section]");
|
|
994
|
+
return found;
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
// src/linux/systemctl.ts
|
|
998
|
+
var SYSTEMCTL = "systemctl";
|
|
999
|
+
var SHOW_PROPERTIES = [
|
|
1000
|
+
"Id",
|
|
1001
|
+
"LoadState",
|
|
1002
|
+
"ActiveState",
|
|
1003
|
+
"SubState",
|
|
1004
|
+
"UnitFileState",
|
|
1005
|
+
"FragmentPath",
|
|
1006
|
+
"NeedDaemonReload"
|
|
1007
|
+
];
|
|
1008
|
+
|
|
1009
|
+
class SystemctlError extends Error {
|
|
1010
|
+
constructor(command, exitCode, stderr) {
|
|
1011
|
+
super(`systemctl ${command} -> ${String(exitCode)}: ${stderr.trim().slice(0, 300)}`);
|
|
1012
|
+
this.name = "SystemctlError";
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
var parseShow = (stdout) => {
|
|
1016
|
+
const fields = new Map;
|
|
1017
|
+
for (const line of stdout.split(`
|
|
1018
|
+
`)) {
|
|
1019
|
+
const at = line.indexOf("=");
|
|
1020
|
+
if (at <= 0)
|
|
1021
|
+
continue;
|
|
1022
|
+
const key = line.slice(0, at);
|
|
1023
|
+
if (!fields.has(key))
|
|
1024
|
+
fields.set(key, line.slice(at + 1));
|
|
1025
|
+
}
|
|
1026
|
+
return fields;
|
|
1027
|
+
};
|
|
1028
|
+
var statusOf = (stdout) => {
|
|
1029
|
+
const fields = parseShow(stdout);
|
|
1030
|
+
const loadState = fields.get("LoadState") ?? "not-found";
|
|
1031
|
+
const unitFileState = fields.get("UnitFileState") ?? "";
|
|
1032
|
+
const fragmentPath = fields.get("FragmentPath") ?? "";
|
|
1033
|
+
const subState = fields.get("SubState") ?? "";
|
|
1034
|
+
return {
|
|
1035
|
+
activeState: fields.get("ActiveState") ?? "inactive",
|
|
1036
|
+
known: loadState !== "not-found" || unitFileState !== "",
|
|
1037
|
+
loadState,
|
|
1038
|
+
needDaemonReload: fields.get("NeedDaemonReload") === "yes",
|
|
1039
|
+
...subState === "" ? {} : { subState },
|
|
1040
|
+
...unitFileState === "" ? {} : { unitFileState },
|
|
1041
|
+
...fragmentPath === "" ? {} : { fragmentPath }
|
|
1042
|
+
};
|
|
1043
|
+
};
|
|
1044
|
+
var showUnit = async (runner, name) => {
|
|
1045
|
+
const result = await runner.exec([
|
|
1046
|
+
SYSTEMCTL,
|
|
1047
|
+
"show",
|
|
1048
|
+
"--no-pager",
|
|
1049
|
+
"-p",
|
|
1050
|
+
SHOW_PROPERTIES.join(","),
|
|
1051
|
+
"--",
|
|
1052
|
+
name
|
|
1053
|
+
]);
|
|
1054
|
+
if (result.exitCode !== 0)
|
|
1055
|
+
throw new SystemctlError(`show ${name}`, result.exitCode, result.stderr);
|
|
1056
|
+
return statusOf(result.stdout);
|
|
1057
|
+
};
|
|
1058
|
+
var run = async (runner, args) => {
|
|
1059
|
+
const result = await runner.exec([SYSTEMCTL, ...args]);
|
|
1060
|
+
if (result.exitCode !== 0) {
|
|
1061
|
+
throw new SystemctlError(args.join(" "), result.exitCode, result.stderr);
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
var daemonReload = (runner) => run(runner, ["daemon-reload"]);
|
|
1065
|
+
var enableUnit = (runner, name) => run(runner, ["enable", "--", name]);
|
|
1066
|
+
var disableUnit = (runner, name) => run(runner, ["disable", "--", name]);
|
|
1067
|
+
var startUnit = (runner, name) => run(runner, ["start", "--", name]);
|
|
1068
|
+
var stopUnit = (runner, name) => run(runner, ["stop", "--", name]);
|
|
1069
|
+
var restartUnit = (runner, name) => run(runner, ["restart", "--", name]);
|
|
1070
|
+
|
|
1071
|
+
// src/linux/unit-settle.ts
|
|
1072
|
+
var refuse4 = (name, message) => new Error(`Systemd.Unit ${name}: ${message}`);
|
|
1073
|
+
var attributesOf = (props, unitSha256, status) => ({
|
|
1074
|
+
active: status.activeState === "active" || status.activeState === "activating",
|
|
1075
|
+
activeState: status.activeState,
|
|
1076
|
+
configSha256: configDigest(props.restartOn),
|
|
1077
|
+
enabled: status.unitFileState === "enabled" || status.unitFileState === "enabled-runtime",
|
|
1078
|
+
loadState: status.loadState,
|
|
1079
|
+
name: props.name,
|
|
1080
|
+
unitPath: unitPathFor(props),
|
|
1081
|
+
unitSha256,
|
|
1082
|
+
...status.unitFileState === undefined ? {} : { unitFileState: status.unitFileState }
|
|
1083
|
+
});
|
|
1084
|
+
var settle = async (runner, props, step) => {
|
|
1085
|
+
const wantEnabled = props.enabled !== false;
|
|
1086
|
+
const wantStarted = props.started !== false;
|
|
1087
|
+
const isEnabled = step.status.unitFileState === "enabled" || step.status.unitFileState === "enabled-runtime";
|
|
1088
|
+
const undo = async (detail) => {
|
|
1089
|
+
if (step.before === undefined) {
|
|
1090
|
+
await runner.removeFile(step.path).catch(() => {
|
|
1091
|
+
return;
|
|
1092
|
+
});
|
|
1093
|
+
await daemonReload(runner).catch(() => {
|
|
1094
|
+
return;
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
throw refuse4(props.name, `${detail} The unit is NOT running.`);
|
|
1098
|
+
};
|
|
1099
|
+
try {
|
|
1100
|
+
if (wantEnabled && (!isEnabled || step.changed))
|
|
1101
|
+
await enableUnit(runner, props.name);
|
|
1102
|
+
if (!wantEnabled && isEnabled)
|
|
1103
|
+
await disableUnit(runner, props.name);
|
|
1104
|
+
const active = step.status.activeState === "active" || step.status.activeState === "activating";
|
|
1105
|
+
if (!wantStarted) {
|
|
1106
|
+
if (active)
|
|
1107
|
+
await stopUnit(runner, props.name);
|
|
1108
|
+
} else if (!active)
|
|
1109
|
+
await startUnit(runner, props.name);
|
|
1110
|
+
else if (step.changed || step.configChanged)
|
|
1111
|
+
await restartUnit(runner, props.name);
|
|
1112
|
+
} catch (cause) {
|
|
1113
|
+
return undo(cause instanceof Error ? cause.message : String(cause));
|
|
1114
|
+
}
|
|
1115
|
+
const after = await showUnit(runner, props.name);
|
|
1116
|
+
if (wantStarted && after.activeState !== "active" && after.activeState !== "activating") {
|
|
1117
|
+
return undo(`systemctl reported success but the unit is ${after.activeState}.`);
|
|
1118
|
+
}
|
|
1119
|
+
const settled = attributesOf(props, step.desired, after);
|
|
1120
|
+
if (wantEnabled && !settled.enabled) {
|
|
1121
|
+
return undo(`systemctl enable reported success but systemd calls the unit ` + `${JSON.stringify(after.unitFileState ?? "")}, not enabled.`);
|
|
1122
|
+
}
|
|
1123
|
+
return settled;
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
// src/linux/unit-lifecycle.ts
|
|
1127
|
+
var encoder2 = new TextEncoder;
|
|
1128
|
+
var decoder2 = new TextDecoder;
|
|
1129
|
+
var assertValid = (props, expect) => {
|
|
1130
|
+
const found = unitProblems(props, expect);
|
|
1131
|
+
if (found.length > 0)
|
|
1132
|
+
throw refuse4(props.name, found.join("; "));
|
|
1133
|
+
};
|
|
1134
|
+
var readUnit = async (runner, props) => {
|
|
1135
|
+
const bytes = await runner.readFile(unitPathFor(props));
|
|
1136
|
+
const status = await showUnit(runner, props.name);
|
|
1137
|
+
if (bytes === undefined && !status.known)
|
|
1138
|
+
return;
|
|
1139
|
+
return attributesOf(props, bytes === undefined ? "" : digestOf2(decoder2.decode(bytes)), status);
|
|
1140
|
+
};
|
|
1141
|
+
var assertMayWrite = (runner, name) => {
|
|
1142
|
+
if (canActAsRoot(runner))
|
|
1143
|
+
return;
|
|
1144
|
+
throw refuse4(name, "a systemd unit is root-owned and systemctl needs root. Point the runner at a destination " + "whose ssh user is root, or provide a privileged HostRunner. This provider never calls sudo.");
|
|
1145
|
+
};
|
|
1146
|
+
var assertUsable = (props, status) => {
|
|
1147
|
+
if (status.loadState === "masked" || status.unitFileState === "masked") {
|
|
1148
|
+
throw refuse4(props.name, "is masked. If that is stale, `systemctl unmask` it deliberately, then redeploy. Nothing " + "was written.");
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
var diffUnit = async (runner, news, output, expect) => {
|
|
1152
|
+
assertValid(news, expect);
|
|
1153
|
+
if (news.name !== output.name || unitPathFor(news) !== output.unitPath) {
|
|
1154
|
+
assertMayWrite(runner, news.name);
|
|
1155
|
+
return { action: "replace", deleteFirst: true };
|
|
1156
|
+
}
|
|
1157
|
+
const desired2 = digestOf2(unitText(news));
|
|
1158
|
+
const update = async () => {
|
|
1159
|
+
assertMayWrite(runner, news.name);
|
|
1160
|
+
await runner.checkWrite?.(unitPathFor(news), UNIT_WRITE);
|
|
1161
|
+
return { action: "update" };
|
|
1162
|
+
};
|
|
1163
|
+
if (desired2 !== output.unitSha256)
|
|
1164
|
+
return update();
|
|
1165
|
+
if (configDigest(news.restartOn) !== output.configSha256)
|
|
1166
|
+
return update();
|
|
1167
|
+
const live = await readUnit(runner, news);
|
|
1168
|
+
if (live === undefined || live.unitSha256 !== desired2)
|
|
1169
|
+
return update();
|
|
1170
|
+
if (live.enabled !== (news.enabled !== false))
|
|
1171
|
+
return update();
|
|
1172
|
+
if (live.active !== (news.started !== false))
|
|
1173
|
+
return update();
|
|
1174
|
+
const status = await showUnit(runner, news.name);
|
|
1175
|
+
return status.needDaemonReload ? update() : { action: "noop" };
|
|
1176
|
+
};
|
|
1177
|
+
var reconcileUnit = async (runner, props, output, adopt = false, expect) => {
|
|
1178
|
+
assertValid(props, expect);
|
|
1179
|
+
assertMayWrite(runner, props.name);
|
|
1180
|
+
const path = unitPathFor(props);
|
|
1181
|
+
const status = await showUnit(runner, props.name);
|
|
1182
|
+
assertUsable(props, status);
|
|
1183
|
+
const text = unitText(props);
|
|
1184
|
+
const desired2 = digestOf2(text);
|
|
1185
|
+
const before = await runner.readFile(path);
|
|
1186
|
+
const liveSha = before === undefined ? undefined : digestOf2(decoder2.decode(before));
|
|
1187
|
+
const moved = output !== undefined && (output.name !== props.name || output.unitPath !== path);
|
|
1188
|
+
const prior = moved ? undefined : output;
|
|
1189
|
+
if (prior === undefined && !(adopt && output === undefined) && liveSha !== undefined && liveSha !== desired2) {
|
|
1190
|
+
throw refuse4(props.name, `${path} is already on this host and is not this resource. Remove it, or declare it as a ` + "new resource and deploy with --adopt.");
|
|
1191
|
+
}
|
|
1192
|
+
if (moved && output !== undefined)
|
|
1193
|
+
await deleteUnit(runner, output);
|
|
1194
|
+
const wrote = liveSha !== desired2;
|
|
1195
|
+
const stale = prior !== undefined && prior.unitSha256 !== desired2;
|
|
1196
|
+
const configChanged = prior !== undefined && prior.configSha256 !== configDigest(props.restartOn);
|
|
1197
|
+
if (wrote)
|
|
1198
|
+
await runner.writeFileAtomic(path, encoder2.encode(text), UNIT_WRITE);
|
|
1199
|
+
if (wrote || status.needDaemonReload)
|
|
1200
|
+
await daemonReload(runner);
|
|
1201
|
+
const changed = wrote || stale || status.needDaemonReload;
|
|
1202
|
+
return settle(runner, props, { before, changed, configChanged, desired: desired2, path, status });
|
|
1203
|
+
};
|
|
1204
|
+
var deleteUnit = async (runner, output) => {
|
|
1205
|
+
assertMayWrite(runner, output.name);
|
|
1206
|
+
const status = await showUnit(runner, output.name);
|
|
1207
|
+
if (status.activeState === "active" || status.activeState === "activating") {
|
|
1208
|
+
await stopUnit(runner, output.name);
|
|
1209
|
+
}
|
|
1210
|
+
if (status.unitFileState === "enabled" || status.unitFileState === "enabled-runtime") {
|
|
1211
|
+
await disableUnit(runner, output.name);
|
|
1212
|
+
}
|
|
1213
|
+
const stat = await runner.stat(output.unitPath);
|
|
1214
|
+
if (stat !== undefined) {
|
|
1215
|
+
if (stat.kind !== "file")
|
|
1216
|
+
throw refuse4(output.name, `${output.unitPath} is a ${stat.kind}`);
|
|
1217
|
+
await runner.removeFile(output.unitPath);
|
|
1218
|
+
}
|
|
1219
|
+
await daemonReload(runner);
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1222
|
+
// src/linux/unit-handlers.ts
|
|
1223
|
+
var readHandler = (runner, olds, output) => lift(async () => {
|
|
1224
|
+
const found = await readUnit(runner, olds);
|
|
1225
|
+
if (found === undefined)
|
|
1226
|
+
return;
|
|
1227
|
+
return output === undefined ? Unowned4(found) : found;
|
|
1228
|
+
});
|
|
1229
|
+
var diffHandler = (runner, instanceId, news, output, expect) => {
|
|
1230
|
+
if (output === undefined)
|
|
1231
|
+
return noteUnfinished(instanceId);
|
|
1232
|
+
if (isResolved3(news))
|
|
1233
|
+
return lift(() => diffUnit(runner, news, output, expect));
|
|
1234
|
+
const name = resolvedString(news, "name");
|
|
1235
|
+
return Effect8.succeed(name !== undefined && name !== output.name ? { action: "replace", deleteFirst: true } : undefined);
|
|
1236
|
+
};
|
|
1237
|
+
var reconcileHandler = (runner, args, expect) => Effect8.flatMap(adoptsAtApply(args), (adopt) => lift(() => reconcileUnit(runner, args.news, args.output, adopt, expect)));
|
|
1238
|
+
var deleteHandler = (runner, output) => lift(() => deleteUnit(runner, output));
|
|
1239
|
+
|
|
1240
|
+
// src/linux/unit.ts
|
|
1241
|
+
var SystemdUnit = Resource3("Systemd.Unit");
|
|
1242
|
+
var SystemdTimer = Resource3("Systemd.Timer");
|
|
1243
|
+
var SystemdUnitProvider = () => Provider3.effect(SystemdUnit, Effect9.gen(function* () {
|
|
1244
|
+
const runner = yield* HostRunnerService;
|
|
1245
|
+
return SystemdUnit.Provider.of({
|
|
1246
|
+
list: () => Effect9.succeed([]),
|
|
1247
|
+
read: ({ olds, output }) => readHandler(runner, olds, output),
|
|
1248
|
+
diff: ({ instanceId, news, output }) => diffHandler(runner, instanceId, news, output),
|
|
1249
|
+
reconcile: (args) => reconcileHandler(runner, args),
|
|
1250
|
+
delete: ({ output }) => deleteHandler(runner, output)
|
|
1251
|
+
});
|
|
1252
|
+
}));
|
|
1253
|
+
var SystemdTimerProvider = () => Provider3.effect(SystemdTimer, Effect9.gen(function* () {
|
|
1254
|
+
const runner = yield* HostRunnerService;
|
|
1255
|
+
return SystemdTimer.Provider.of({
|
|
1256
|
+
list: () => Effect9.succeed([]),
|
|
1257
|
+
read: ({ olds, output }) => readHandler(runner, olds, output),
|
|
1258
|
+
diff: ({ instanceId, news, output }) => diffHandler(runner, instanceId, news, output, "timer"),
|
|
1259
|
+
reconcile: (args) => reconcileHandler(runner, args, "timer"),
|
|
1260
|
+
delete: ({ output }) => deleteHandler(runner, output)
|
|
1261
|
+
});
|
|
1262
|
+
}));
|
|
1263
|
+
|
|
1264
|
+
// src/linux/providers.ts
|
|
1265
|
+
var linuxProviders = (runner) => Layer2.mergeAll(HostDirectoryProvider(), RemoteFileProvider(), SystemdTimerProvider(), SystemdUnitProvider()).pipe(Layer2.provide(hostRunnerLayer(runner)));
|
|
1266
|
+
// src/linux/ssh-runner.ts
|
|
1267
|
+
import { spawn } from "node:child_process";
|
|
1268
|
+
import { randomBytes } from "node:crypto";
|
|
1269
|
+
|
|
1270
|
+
// src/launchd/host-lookup.ts
|
|
1271
|
+
var NUMERIC = /^\d+$/;
|
|
1272
|
+
var userQuery = (platform, nameOrId) => platform === "darwin" ? [
|
|
1273
|
+
"/usr/bin/dscacheutil",
|
|
1274
|
+
"-q",
|
|
1275
|
+
"user",
|
|
1276
|
+
"-a",
|
|
1277
|
+
NUMERIC.test(nameOrId) ? "uid" : "name",
|
|
1278
|
+
nameOrId
|
|
1279
|
+
] : ["getent", "passwd", nameOrId];
|
|
1280
|
+
var groupQuery = (platform, nameOrId) => platform === "darwin" ? [
|
|
1281
|
+
"/usr/bin/dscacheutil",
|
|
1282
|
+
"-q",
|
|
1283
|
+
"group",
|
|
1284
|
+
"-a",
|
|
1285
|
+
NUMERIC.test(nameOrId) ? "gid" : "name",
|
|
1286
|
+
nameOrId
|
|
1287
|
+
] : ["getent", "group", nameOrId];
|
|
1288
|
+
var firstRecord = (stdout) => {
|
|
1289
|
+
const fields = new Map;
|
|
1290
|
+
for (const line of stdout.split(`
|
|
1291
|
+
`)) {
|
|
1292
|
+
if (line.trim() === "") {
|
|
1293
|
+
if (fields.size > 0)
|
|
1294
|
+
break;
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
const at = line.indexOf(": ");
|
|
1298
|
+
if (at <= 0)
|
|
1299
|
+
continue;
|
|
1300
|
+
const key = line.slice(0, at);
|
|
1301
|
+
if (key === "password")
|
|
1302
|
+
continue;
|
|
1303
|
+
fields.set(key, line.slice(at + 2).trim());
|
|
1304
|
+
}
|
|
1305
|
+
return fields;
|
|
1306
|
+
};
|
|
1307
|
+
var toId = (text) => text !== undefined && NUMERIC.test(text) ? Number(text) : undefined;
|
|
1308
|
+
var parseUser = (platform, stdout) => {
|
|
1309
|
+
if (platform === "darwin") {
|
|
1310
|
+
const record = firstRecord(stdout);
|
|
1311
|
+
const uid2 = toId(record.get("uid"));
|
|
1312
|
+
const gid2 = toId(record.get("gid"));
|
|
1313
|
+
const home2 = record.get("dir");
|
|
1314
|
+
return uid2 === undefined || gid2 === undefined || home2 === undefined ? undefined : { gid: gid2, home: home2, uid: uid2 };
|
|
1315
|
+
}
|
|
1316
|
+
const fields = (stdout.split(`
|
|
1317
|
+
`)[0] ?? "").split(":");
|
|
1318
|
+
const uid = toId(fields[2]);
|
|
1319
|
+
const gid = toId(fields[3]);
|
|
1320
|
+
const home = fields[5];
|
|
1321
|
+
return uid === undefined || gid === undefined || home === undefined || home === "" ? undefined : { gid, home, uid };
|
|
1322
|
+
};
|
|
1323
|
+
var parseGroup = (platform, stdout) => {
|
|
1324
|
+
if (platform === "darwin")
|
|
1325
|
+
return toId(firstRecord(stdout).get("gid"));
|
|
1326
|
+
return toId((stdout.split(`
|
|
1327
|
+
`)[0] ?? "").split(":")[2]);
|
|
1328
|
+
};
|
|
1329
|
+
|
|
1330
|
+
// src/linux/ssh-command.ts
|
|
1331
|
+
var ABSENT = 66;
|
|
1332
|
+
var shellQuote = (arg) => {
|
|
1333
|
+
if (arg.includes("\x00"))
|
|
1334
|
+
throw new Error("ssh: an argument contains NUL");
|
|
1335
|
+
return `'${arg.replaceAll("'", `'\\''`)}'`;
|
|
1336
|
+
};
|
|
1337
|
+
var quoteArgv = (argv) => argv.map(shellQuote).join(" ");
|
|
1338
|
+
var marker = (nonce) => `__HF_RC_${nonce}=`;
|
|
1339
|
+
var frameScript = (script, nonce) => `(
|
|
1340
|
+
${script}
|
|
1341
|
+
)
|
|
1342
|
+
__hf=$?
|
|
1343
|
+
printf '\\n${marker(nonce)}%s\\n' "$__hf" >&2
|
|
1344
|
+
exit "$__hf"`;
|
|
1345
|
+
var parseFramed = (stderr, nonce) => {
|
|
1346
|
+
const at = stderr.lastIndexOf(`
|
|
1347
|
+
${marker(nonce)}`);
|
|
1348
|
+
if (at < 0)
|
|
1349
|
+
return;
|
|
1350
|
+
const digits = /^(\d+)\s*$/.exec(stderr.slice(at + marker(nonce).length + 1));
|
|
1351
|
+
if (digits?.[1] === undefined)
|
|
1352
|
+
return;
|
|
1353
|
+
return { exitCode: Number(digits[1]), stderr: stderr.slice(0, at) };
|
|
1354
|
+
};
|
|
1355
|
+
var sshArgv = (host, script, options) => {
|
|
1356
|
+
if (host === "" || host.startsWith("-")) {
|
|
1357
|
+
throw new Error(`ssh: ${JSON.stringify(host)} is not a usable destination`);
|
|
1358
|
+
}
|
|
1359
|
+
return [
|
|
1360
|
+
"ssh",
|
|
1361
|
+
"-T",
|
|
1362
|
+
"-o",
|
|
1363
|
+
"BatchMode=yes",
|
|
1364
|
+
"-o",
|
|
1365
|
+
`ConnectTimeout=${String(options.connectTimeoutSec)}`,
|
|
1366
|
+
...options.sshArgs ?? [],
|
|
1367
|
+
host,
|
|
1368
|
+
`/bin/sh -c ${shellQuote(script)}`
|
|
1369
|
+
];
|
|
1370
|
+
};
|
|
1371
|
+
|
|
1372
|
+
// src/linux/ssh-scripts.ts
|
|
1373
|
+
var IF_PRESENT = (path) => `if [ -e ${path} ] || [ -L ${path} ]; then`;
|
|
1374
|
+
var statScript = (path) => {
|
|
1375
|
+
const quoted = shellQuote(path);
|
|
1376
|
+
return `${IF_PRESENT(quoted)} stat -c '%f %a %u %g %s' -- ${quoted}; else exit ${String(ABSENT)}; fi`;
|
|
1377
|
+
};
|
|
1378
|
+
var readScript = (path) => {
|
|
1379
|
+
const quoted = shellQuote(path);
|
|
1380
|
+
return `${IF_PRESENT(quoted)} base64 -- ${quoted}; else exit ${String(ABSENT)}; fi`;
|
|
1381
|
+
};
|
|
1382
|
+
var removeScript = (path) => `rm -f -- ${shellQuote(path)}`;
|
|
1383
|
+
var execScript = (argv) => quoteArgv(argv);
|
|
1384
|
+
var writeScript = (path, temp, options) => {
|
|
1385
|
+
const target = shellQuote(path);
|
|
1386
|
+
const staged = shellQuote(temp);
|
|
1387
|
+
const bail = `{ rm -f -- ${staged}; exit`;
|
|
1388
|
+
const owner = options.uid === undefined ? options.gid === undefined ? undefined : `:${String(options.gid)}` : options.gid === undefined ? String(options.uid) : `${String(options.uid)}:${String(options.gid)}`;
|
|
1389
|
+
const chown = owner === undefined ? [] : [`chown ${owner} -- ${staged} || ${bail} 72; }`];
|
|
1390
|
+
return [
|
|
1391
|
+
"set -C",
|
|
1392
|
+
"umask 077",
|
|
1393
|
+
`cat > ${staged} || ${bail} 71; }`,
|
|
1394
|
+
...chown,
|
|
1395
|
+
`chmod ${options.mode.toString(8).padStart(3, "0")} -- ${staged} || ${bail} 73; }`,
|
|
1396
|
+
`mv -f -- ${staged} ${target} || ${bail} 74; }`
|
|
1397
|
+
].join(`
|
|
1398
|
+
`);
|
|
1399
|
+
};
|
|
1400
|
+
var TYPE_BITS = 61440;
|
|
1401
|
+
var KINDS = new Map([
|
|
1402
|
+
[32768, "file"],
|
|
1403
|
+
[16384, "directory"],
|
|
1404
|
+
[40960, "symlink"]
|
|
1405
|
+
]);
|
|
1406
|
+
var parseStat = (stdout) => {
|
|
1407
|
+
const fields = stdout.trim().split(/\s+/);
|
|
1408
|
+
if (fields.length !== 5)
|
|
1409
|
+
return;
|
|
1410
|
+
const [rawMode, perms, uid, gid, size] = fields;
|
|
1411
|
+
const raw = Number.parseInt(rawMode, 16);
|
|
1412
|
+
const mode = Number.parseInt(perms, 8);
|
|
1413
|
+
const numbers = [raw, mode, Number(uid), Number(gid), Number(size)];
|
|
1414
|
+
if (numbers.some((value) => !Number.isSafeInteger(value) || value < 0))
|
|
1415
|
+
return;
|
|
1416
|
+
return {
|
|
1417
|
+
gid: Number(gid),
|
|
1418
|
+
kind: KINDS.get(raw & TYPE_BITS) ?? "other",
|
|
1419
|
+
mode,
|
|
1420
|
+
size: Number(size),
|
|
1421
|
+
uid: Number(uid)
|
|
1422
|
+
};
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
// src/linux/ssh-runner.ts
|
|
1426
|
+
var spawnSsh = (argv, timeoutMs, stdin) => new Promise((resolve, reject) => {
|
|
1427
|
+
const [program, ...args] = argv;
|
|
1428
|
+
const child = spawn(program, args, {
|
|
1429
|
+
shell: false,
|
|
1430
|
+
stdio: [stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
|
|
1431
|
+
timeout: timeoutMs
|
|
1432
|
+
});
|
|
1433
|
+
const out = [];
|
|
1434
|
+
const err = [];
|
|
1435
|
+
child.stdout?.on("data", (chunk) => out.push(chunk));
|
|
1436
|
+
child.stderr?.on("data", (chunk) => err.push(chunk));
|
|
1437
|
+
child.on("error", (cause) => {
|
|
1438
|
+
reject(new Error(`ssh: ${cause.message}`));
|
|
1439
|
+
});
|
|
1440
|
+
if (stdin !== undefined && child.stdin !== null) {
|
|
1441
|
+
child.stdin.on("error", () => {
|
|
1442
|
+
return;
|
|
1443
|
+
});
|
|
1444
|
+
child.stdin.end(stdin);
|
|
1445
|
+
}
|
|
1446
|
+
child.on("close", (exitCode, signal) => {
|
|
1447
|
+
resolve({
|
|
1448
|
+
exitCode: exitCode ?? -1,
|
|
1449
|
+
signal,
|
|
1450
|
+
stderr: Buffer.concat(err).toString("utf8"),
|
|
1451
|
+
stdout: Buffer.concat(out).toString("utf8")
|
|
1452
|
+
});
|
|
1453
|
+
});
|
|
1454
|
+
});
|
|
1455
|
+
var runRemote = async (options, nonce, script, stdin) => {
|
|
1456
|
+
const argv = sshArgv(options.host, frameScript(script, nonce), {
|
|
1457
|
+
connectTimeoutSec: options.connectTimeoutSec,
|
|
1458
|
+
...options.sshArgs === undefined ? {} : { sshArgs: options.sshArgs }
|
|
1459
|
+
});
|
|
1460
|
+
const raw = await spawnSsh(argv, options.execTimeoutMs, stdin);
|
|
1461
|
+
const framed = parseFramed(raw.stderr, nonce);
|
|
1462
|
+
if (framed === undefined) {
|
|
1463
|
+
throw new Error(`ssh ${options.host}: the remote command did not report a status (ssh exit ` + `${String(raw.exitCode)}${raw.signal === null ? "" : `, killed by ${raw.signal}`}): ` + `${raw.stderr.trim().slice(0, 300)}`);
|
|
1464
|
+
}
|
|
1465
|
+
return { exitCode: framed.exitCode, stderr: framed.stderr, stdout: raw.stdout };
|
|
1466
|
+
};
|
|
1467
|
+
var decodeBase64 = (text) => new Uint8Array(Buffer.from(text.replaceAll(`
|
|
1468
|
+
`, ""), "base64"));
|
|
1469
|
+
var failed = (host, what, result) => new Error(`ssh ${host}: ${what} -> ${String(result.exitCode)}: ${result.stderr.trim().slice(0, 300)}`);
|
|
1470
|
+
var sshRunner = async (options) => {
|
|
1471
|
+
const settings = {
|
|
1472
|
+
connectTimeoutSec: options.connectTimeoutSec ?? 10,
|
|
1473
|
+
execTimeoutMs: options.execTimeoutMs ?? 120000,
|
|
1474
|
+
host: options.host,
|
|
1475
|
+
...options.sshArgs === undefined ? {} : { sshArgs: options.sshArgs }
|
|
1476
|
+
};
|
|
1477
|
+
const nonce = randomBytes(8).toString("hex");
|
|
1478
|
+
const run2 = (script, stdin) => runRemote(settings, nonce, script, stdin);
|
|
1479
|
+
const probe = await run2("uname -s; id -u");
|
|
1480
|
+
if (probe.exitCode !== 0)
|
|
1481
|
+
throw failed(options.host, "uname -s; id -u", probe);
|
|
1482
|
+
const [system, uid] = probe.stdout.trim().split(`
|
|
1483
|
+
`);
|
|
1484
|
+
if (system !== "Linux" || uid === undefined || !/^\d+$/.test(uid)) {
|
|
1485
|
+
throw new Error(`ssh ${options.host}: expected a Linux host, got ${JSON.stringify(probe.stdout.trim())}. ` + "sshRunner() sends GNU/BusyBox argv (stat -c, base64, mv -f); for a Mac use localRunner().");
|
|
1486
|
+
}
|
|
1487
|
+
const euid = Number(uid);
|
|
1488
|
+
return {
|
|
1489
|
+
effectiveUid: () => euid,
|
|
1490
|
+
exec: (argv) => run2(execScript(argv)),
|
|
1491
|
+
lookupGroup: async (nameOrId) => {
|
|
1492
|
+
const result = await run2(execScript(groupQuery("linux", nameOrId)));
|
|
1493
|
+
return result.exitCode === 0 ? parseGroup("linux", result.stdout) : undefined;
|
|
1494
|
+
},
|
|
1495
|
+
lookupUser: async (nameOrId) => {
|
|
1496
|
+
const result = await run2(execScript(userQuery("linux", nameOrId)));
|
|
1497
|
+
return result.exitCode === 0 ? parseUser("linux", result.stdout) : undefined;
|
|
1498
|
+
},
|
|
1499
|
+
privileged: false,
|
|
1500
|
+
readFile: async (path) => {
|
|
1501
|
+
const result = await run2(readScript(path));
|
|
1502
|
+
if (result.exitCode === ABSENT)
|
|
1503
|
+
return;
|
|
1504
|
+
if (result.exitCode !== 0)
|
|
1505
|
+
throw failed(options.host, `read ${path}`, result);
|
|
1506
|
+
return decodeBase64(result.stdout);
|
|
1507
|
+
},
|
|
1508
|
+
removeFile: async (path) => {
|
|
1509
|
+
const result = await run2(removeScript(path));
|
|
1510
|
+
if (result.exitCode !== 0)
|
|
1511
|
+
throw failed(options.host, `remove ${path}`, result);
|
|
1512
|
+
},
|
|
1513
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
1514
|
+
stat: async (path) => {
|
|
1515
|
+
const result = await run2(statScript(path));
|
|
1516
|
+
if (result.exitCode === ABSENT)
|
|
1517
|
+
return;
|
|
1518
|
+
if (result.exitCode !== 0)
|
|
1519
|
+
throw failed(options.host, `stat ${path}`, result);
|
|
1520
|
+
const parsed = parseStat(result.stdout);
|
|
1521
|
+
if (parsed === undefined) {
|
|
1522
|
+
throw new Error(`ssh ${options.host}: stat ${path} printed an unexpected shape`);
|
|
1523
|
+
}
|
|
1524
|
+
return parsed;
|
|
1525
|
+
},
|
|
1526
|
+
writeFileAtomic: async (path, bytes, write) => {
|
|
1527
|
+
const at = path.lastIndexOf("/");
|
|
1528
|
+
const temp = `${path.slice(0, at)}/.${path.slice(at + 1)}.${randomBytes(6).toString("hex")}.tmp`;
|
|
1529
|
+
const result = await run2(writeScript(path, temp, write), bytes);
|
|
1530
|
+
if (result.exitCode !== 0)
|
|
1531
|
+
throw failed(options.host, `write ${path}`, result);
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
};
|
|
1535
|
+
export {
|
|
1536
|
+
DEFAULT_UNIT_DIRECTORY,
|
|
1537
|
+
HostDirectory,
|
|
1538
|
+
HostDirectoryProvider,
|
|
1539
|
+
HostRunnerService,
|
|
1540
|
+
RemoteFile,
|
|
1541
|
+
RemoteFileProvider,
|
|
1542
|
+
SystemdTimer,
|
|
1543
|
+
SystemdTimerProvider,
|
|
1544
|
+
SystemdUnit,
|
|
1545
|
+
SystemdUnitProvider,
|
|
1546
|
+
canActAsRoot,
|
|
1547
|
+
hostRunnerLayer,
|
|
1548
|
+
linuxProviders,
|
|
1549
|
+
renderUnit,
|
|
1550
|
+
sshRunner
|
|
1551
|
+
};
|
|
1552
|
+
|
|
1553
|
+
//# debugId=36C2172DF713B75564756E2164756E21
|
|
1554
|
+
//# sourceMappingURL=index.js.map
|