@homeflare/alchemy 0.39.0 → 0.41.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 +10 -8
- package/dist/caddy/index.js.map +2 -2
- package/dist/ceph/auth-entity.d.ts +34 -0
- package/dist/ceph/auth-entity.d.ts.map +1 -0
- package/dist/ceph/ceph-argv-config.d.ts +10 -0
- package/dist/ceph/ceph-argv-config.d.ts.map +1 -0
- package/dist/ceph/ceph-argv.d.ts +19 -0
- package/dist/ceph/ceph-argv.d.ts.map +1 -0
- package/dist/ceph/ceph-auth-form.d.ts +54 -0
- package/dist/ceph/ceph-auth-form.d.ts.map +1 -0
- package/dist/ceph/ceph-auth-parse.d.ts +56 -0
- package/dist/ceph/ceph-auth-parse.d.ts.map +1 -0
- package/dist/ceph/ceph-auth-reconcile.d.ts +35 -0
- package/dist/ceph/ceph-auth-reconcile.d.ts.map +1 -0
- package/dist/ceph/ceph-config-options.d.ts +23 -0
- package/dist/ceph/ceph-config-options.d.ts.map +1 -0
- package/dist/ceph/ceph-key-leak-assert.d.ts +25 -0
- package/dist/ceph/ceph-key-leak-assert.d.ts.map +1 -0
- package/dist/ceph/ceph-transport.d.ts +37 -0
- package/dist/ceph/ceph-transport.d.ts.map +1 -0
- package/dist/ceph/fake-ceph-dial.d.ts +35 -0
- package/dist/ceph/fake-ceph-dial.d.ts.map +1 -0
- package/dist/ceph/index.d.ts +13 -0
- package/dist/ceph/index.d.ts.map +1 -0
- package/dist/ceph/index.js +1610 -0
- package/dist/ceph/index.js.map +38 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/launchd/fake-runner.d.ts.map +1 -1
- package/dist/launchd/index.d.ts +1 -1
- package/dist/launchd/index.d.ts.map +1 -1
- package/dist/launchd/index.js +7 -1
- package/dist/launchd/index.js.map +5 -5
- package/dist/launchd/local-runner.d.ts.map +1 -1
- package/dist/launchd/runner.d.ts +21 -0
- package/dist/launchd/runner.d.ts.map +1 -1
- package/dist/launchd/sudo-runner.d.ts.map +1 -1
- package/dist/linux/directory-lifecycle.d.ts.map +1 -1
- package/dist/linux/fake-linux-host.d.ts.map +1 -1
- package/dist/linux/index.d.ts +1 -1
- package/dist/linux/index.d.ts.map +1 -1
- package/dist/linux/index.js +16 -4
- package/dist/linux/index.js.map +7 -7
- package/dist/linux/ssh-runner.d.ts.map +1 -1
- package/dist/linux/sudo-runner.d.ts.map +1 -1
- package/dist/opnsense/alias-form.d.ts +14 -1
- package/dist/opnsense/alias-form.d.ts.map +1 -1
- package/dist/opnsense/alias.d.ts +1 -1
- package/dist/opnsense/alias.d.ts.map +1 -1
- package/dist/opnsense/group-form.d.ts +8 -2
- package/dist/opnsense/group-form.d.ts.map +1 -1
- package/dist/opnsense/group.d.ts +1 -1
- package/dist/opnsense/group.d.ts.map +1 -1
- package/dist/opnsense/index.js +15 -7
- package/dist/opnsense/index.js.map +7 -7
- package/dist/opnsense/wire.d.ts +42 -12
- package/dist/opnsense/wire.d.ts.map +1 -1
- package/dist/proxmox/index.js +62 -39
- package/dist/proxmox/index.js.map +8 -7
- package/dist/proxmox/qemu-volume.d.ts +12 -2
- package/dist/proxmox/qemu-volume.d.ts.map +1 -1
- package/dist/proxmox/storage-form.d.ts +28 -3
- package/dist/proxmox/storage-form.d.ts.map +1 -1
- package/dist/proxmox/storage-plugin-options.d.ts +2 -0
- package/dist/proxmox/storage-plugin-options.d.ts.map +1 -0
- package/dist/proxmox/storage-wire.d.ts +28 -1
- package/dist/proxmox/storage-wire.d.ts.map +1 -1
- package/dist/proxmox/storage.d.ts +6 -1
- package/dist/proxmox/storage.d.ts.map +1 -1
- package/dist/release/index.d.ts +1 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js.map +2 -2
- package/dist/talos/credentials-write.d.ts +46 -0
- package/dist/talos/credentials-write.d.ts.map +1 -0
- package/dist/talos/credentials.d.ts +51 -9
- package/dist/talos/credentials.d.ts.map +1 -1
- package/dist/talos/fake-process.d.ts +56 -0
- package/dist/talos/fake-process.d.ts.map +1 -0
- package/dist/talos/index.d.ts +19 -4
- package/dist/talos/index.d.ts.map +1 -1
- package/dist/talos/index.js +357 -173
- package/dist/talos/index.js.map +16 -10
- package/dist/talos/kubeconfig-attrs.d.ts +4 -0
- package/dist/talos/kubeconfig-attrs.d.ts.map +1 -0
- package/dist/talos/kubeconfig.d.ts +41 -23
- package/dist/talos/kubeconfig.d.ts.map +1 -1
- package/dist/talos/machine-config-fixtures.d.ts +60 -0
- package/dist/talos/machine-config-fixtures.d.ts.map +1 -0
- package/dist/talos/machine-config-poll.d.ts +43 -0
- package/dist/talos/machine-config-poll.d.ts.map +1 -0
- package/dist/talos/machine-config-read.d.ts +49 -0
- package/dist/talos/machine-config-read.d.ts.map +1 -0
- package/dist/talos/resource.d.ts +18 -0
- package/dist/talos/resource.d.ts.map +1 -1
- package/dist/talos/talos-bootstrap-etcd.d.ts +38 -0
- package/dist/talos/talos-bootstrap-etcd.d.ts.map +1 -0
- package/dist/talos/talos-bootstrap.d.ts +87 -0
- package/dist/talos/talos-bootstrap.d.ts.map +1 -1
- package/dist/talos/talos-cluster-health.d.ts +43 -1
- package/dist/talos/talos-cluster-health.d.ts.map +1 -1
- package/dist/talos/talos-errors.d.ts +68 -0
- package/dist/talos/talos-errors.d.ts.map +1 -0
- package/dist/talos/talos-machine-config.d.ts +51 -15
- package/dist/talos/talos-machine-config.d.ts.map +1 -1
- package/dist/talos/talosctl.d.ts +7 -0
- package/dist/talos/talosctl.d.ts.map +1 -1
- package/dist/talos/values.d.ts +23 -3
- package/dist/talos/values.d.ts.map +1 -1
- package/dist/unifi/acl-rule-drift.d.ts +22 -0
- package/dist/unifi/acl-rule-drift.d.ts.map +1 -0
- package/dist/unifi/acl-rule-form.d.ts +90 -0
- package/dist/unifi/acl-rule-form.d.ts.map +1 -0
- package/dist/unifi/acl-rule-ordering-form.d.ts +49 -0
- package/dist/unifi/acl-rule-ordering-form.d.ts.map +1 -0
- package/dist/unifi/acl-rule-ordering.d.ts +54 -0
- package/dist/unifi/acl-rule-ordering.d.ts.map +1 -0
- package/dist/unifi/acl-rule.d.ts +49 -0
- package/dist/unifi/acl-rule.d.ts.map +1 -0
- package/dist/unifi/dns-policy-drift.d.ts +21 -0
- package/dist/unifi/dns-policy-drift.d.ts.map +1 -0
- package/dist/unifi/dns-policy-form.d.ts +97 -0
- package/dist/unifi/dns-policy-form.d.ts.map +1 -0
- package/dist/unifi/dns-policy.d.ts +55 -0
- package/dist/unifi/dns-policy.d.ts.map +1 -0
- package/dist/unifi/firewall-policy-drift.d.ts +26 -0
- package/dist/unifi/firewall-policy-drift.d.ts.map +1 -0
- package/dist/unifi/firewall-policy-form.d.ts +102 -0
- package/dist/unifi/firewall-policy-form.d.ts.map +1 -0
- package/dist/unifi/firewall-policy-ordering-form.d.ts +66 -0
- package/dist/unifi/firewall-policy-ordering-form.d.ts.map +1 -0
- package/dist/unifi/firewall-policy-ordering.d.ts +57 -0
- package/dist/unifi/firewall-policy-ordering.d.ts.map +1 -0
- package/dist/unifi/firewall-policy.d.ts +57 -0
- package/dist/unifi/firewall-policy.d.ts.map +1 -0
- package/dist/unifi/index.d.ts +7 -0
- package/dist/unifi/index.d.ts.map +1 -1
- package/dist/unifi/index.js +588 -3
- package/dist/unifi/index.js.map +24 -4
- package/dist/unifi/paginate.d.ts +0 -12
- package/dist/unifi/paginate.d.ts.map +1 -1
- package/dist/unifi/providers.d.ts +1 -1
- package/dist/unifi/providers.d.ts.map +1 -1
- package/dist/unifi/scan-source.d.ts +11 -0
- package/dist/unifi/scan-source.d.ts.map +1 -0
- package/dist/unifi/traffic-matching-list-drift.d.ts +22 -0
- package/dist/unifi/traffic-matching-list-drift.d.ts.map +1 -0
- package/dist/unifi/traffic-matching-list-form.d.ts +56 -0
- package/dist/unifi/traffic-matching-list-form.d.ts.map +1 -0
- package/dist/unifi/traffic-matching-list.d.ts +51 -0
- package/dist/unifi/traffic-matching-list.d.ts.map +1 -0
- package/dist/unifi/wifi-broadcast-drift.d.ts +22 -0
- package/dist/unifi/wifi-broadcast-drift.d.ts.map +1 -0
- package/dist/unifi/wifi-broadcast-form.d.ts +127 -0
- package/dist/unifi/wifi-broadcast-form.d.ts.map +1 -0
- package/dist/unifi/wifi-broadcast.d.ts +73 -0
- package/dist/unifi/wifi-broadcast.d.ts.map +1 -0
- package/docs/distilled-interim.md +7 -0
- package/docs/launchd.md +5 -3
- package/docs/provider-standard.md +5 -1
- package/docs/talos-argocd-dogfood.md +160 -0
- package/docs/unifi-acl-rule.md +122 -0
- package/docs/unifi-dns-policy.md +75 -0
- package/docs/unifi-firewall-policy.md +109 -0
- package/docs/unifi-traffic-matching-list.md +82 -0
- package/docs/unifi-wifi.md +120 -0
- package/docs/unifi.md +10 -5
- package/package.json +7 -3
- package/src/ceph/auth-entity.ts +74 -0
- package/src/ceph/ceph-argv-config.ts +78 -0
- package/src/ceph/ceph-argv.test.ts +226 -0
- package/src/ceph/ceph-argv.ts +189 -0
- package/src/ceph/ceph-auth-form.test.ts +103 -0
- package/src/ceph/ceph-auth-form.ts +107 -0
- package/src/ceph/ceph-auth-parse.test.ts +155 -0
- package/src/ceph/ceph-auth-parse.ts +121 -0
- package/src/ceph/ceph-auth-reconcile-secrecy.test.ts +82 -0
- package/src/ceph/ceph-auth-reconcile.test.ts +233 -0
- package/src/ceph/ceph-auth-reconcile.ts +119 -0
- package/src/ceph/ceph-config-options.test.ts +44 -0
- package/src/ceph/ceph-config-options.ts +36 -0
- package/src/ceph/ceph-key-leak-assert.ts +35 -0
- package/src/ceph/ceph-no-output-logging.test.ts +52 -0
- package/src/ceph/ceph-transport.test.ts +125 -0
- package/src/ceph/ceph-transport.ts +117 -0
- package/src/ceph/fake-ceph-dial.ts +55 -0
- package/src/ceph/index.ts +12 -0
- package/src/index.ts +1 -0
- package/src/launchd/fake-runner.ts +2 -0
- package/src/launchd/index.ts +8 -1
- package/src/launchd/local-runner.ts +16 -1
- package/src/launchd/runner.ts +22 -0
- package/src/launchd/sudo-runner.ts +3 -0
- package/src/linux/directory-lifecycle.ts +18 -3
- package/src/linux/directory-sudo.test.ts +59 -0
- package/src/linux/directory.test.ts +34 -8
- package/src/linux/fake-linux-host.ts +2 -0
- package/src/linux/index.ts +1 -0
- package/src/linux/ssh-runner.ts +4 -0
- package/src/linux/sudo-runner.ts +3 -0
- package/src/opnsense/alias-form.test.ts +24 -12
- package/src/opnsense/alias-form.ts +20 -7
- package/src/opnsense/alias.test.ts +13 -6
- package/src/opnsense/alias.ts +16 -10
- package/src/opnsense/group-form.test.ts +12 -8
- package/src/opnsense/group-form.ts +13 -4
- package/src/opnsense/group.test.ts +18 -7
- package/src/opnsense/group.ts +10 -3
- package/src/opnsense/wire.ts +57 -12
- package/src/proxmox/qemu-volume.test.ts +153 -4
- package/src/proxmox/qemu-volume.ts +71 -14
- package/src/proxmox/storage-form.ts +48 -8
- package/src/proxmox/storage-plugin-options.ts +45 -0
- package/src/proxmox/storage-update.test.ts +183 -0
- package/src/proxmox/storage-wire.ts +54 -7
- package/src/proxmox/storage.ts +18 -6
- package/src/release/index.ts +1 -0
- package/src/talos/credentials-write.test.ts +81 -0
- package/src/talos/credentials-write.ts +95 -0
- package/src/talos/credentials.test.ts +157 -0
- package/src/talos/credentials.ts +160 -90
- package/src/talos/fake-process.ts +94 -0
- package/src/talos/index.ts +39 -4
- package/src/talos/kubeconfig-attrs.ts +55 -0
- package/src/talos/kubeconfig.test.ts +256 -0
- package/src/talos/kubeconfig.ts +170 -90
- package/src/talos/machine-config-fixtures.ts +86 -0
- package/src/talos/machine-config-poll.test.ts +57 -0
- package/src/talos/machine-config-poll.ts +82 -0
- package/src/talos/machine-config-read.ts +118 -0
- package/src/talos/resource.ts +19 -0
- package/src/talos/secrets-gate.test.ts +63 -0
- package/src/talos/talos-bootstrap-etcd.ts +71 -0
- package/src/talos/talos-bootstrap.test.ts +218 -0
- package/src/talos/talos-bootstrap.ts +151 -45
- package/src/talos/talos-cluster-health.test.ts +143 -0
- package/src/talos/talos-cluster-health.ts +91 -28
- package/src/talos/talos-errors.ts +85 -0
- package/src/talos/talos-machine-config.test.ts +189 -0
- package/src/talos/talos-machine-config.ts +102 -88
- package/src/talos/talosctl.ts +8 -0
- package/src/talos/values.test.ts +34 -6
- package/src/talos/values.ts +41 -7
- package/src/unifi/acl-rule-drift.ts +57 -0
- package/src/unifi/acl-rule-form.ts +135 -0
- package/src/unifi/acl-rule-ordering-form.ts +79 -0
- package/src/unifi/acl-rule-ordering.test.ts +141 -0
- package/src/unifi/acl-rule-ordering.ts +70 -0
- package/src/unifi/acl-rule.test.ts +183 -0
- package/src/unifi/acl-rule.ts +61 -0
- package/src/unifi/discriminated-filter-decode.test.ts +134 -0
- package/src/unifi/dns-policy-drift.ts +48 -0
- package/src/unifi/dns-policy-form.ts +142 -0
- package/src/unifi/dns-policy.test.ts +154 -0
- package/src/unifi/dns-policy.ts +67 -0
- package/src/unifi/firewall-policy-drift.test.ts +151 -0
- package/src/unifi/firewall-policy-drift.ts +55 -0
- package/src/unifi/firewall-policy-filter-roundtrip.test.ts +143 -0
- package/src/unifi/firewall-policy-form.ts +146 -0
- package/src/unifi/firewall-policy-ordering-form.ts +111 -0
- package/src/unifi/firewall-policy-ordering.test.ts +185 -0
- package/src/unifi/firewall-policy-ordering.ts +87 -0
- package/src/unifi/firewall-policy.test.ts +183 -0
- package/src/unifi/firewall-policy.ts +73 -0
- package/src/unifi/index.ts +63 -0
- package/src/unifi/paginate.test.ts +24 -0
- package/src/unifi/paginate.ts +15 -3
- package/src/unifi/providers.ts +19 -1
- package/src/unifi/scan-source.ts +64 -0
- package/src/unifi/traffic-matching-list-drift.ts +40 -0
- package/src/unifi/traffic-matching-list-form.ts +77 -0
- package/src/unifi/traffic-matching-list.test.ts +202 -0
- package/src/unifi/traffic-matching-list.ts +78 -0
- package/src/unifi/wifi-broadcast-details-call.test.ts +92 -0
- package/src/unifi/wifi-broadcast-drift.ts +69 -0
- package/src/unifi/wifi-broadcast-form.ts +211 -0
- package/src/unifi/wifi-broadcast-secrets.test.ts +145 -0
- package/src/unifi/wifi-broadcast.test.ts +155 -0
- package/src/unifi/wifi-broadcast.ts +90 -0
- package/src/unifi/write-op-reference.test.ts +8 -54
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssh-runner.d.ts","sourceRoot":"","sources":["../../src/linux/ssh-runner.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAWnE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qGAAqG;IACrG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,0DAA0D;IAC1D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,8CAA8C;IAC9C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC,CAAC;AAmCF,gGAAgG;AAChG,eAAO,MAAM,SAAS,YACX,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,mBAAmB,GAAG,eAAe,CAAC,CAAC,GACvF,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,SAC5B,MAAM,UACL,MAAM,UACN,UAAU,KACjB,OAAO,CAAC,UAAU,CAiBpB,CAAC;AAUF;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,YAAmB,gBAAgB,KAAG,OAAO,CAAC,UAAU,
|
|
1
|
+
{"version":3,"file":"ssh-runner.d.ts","sourceRoot":"","sources":["../../src/linux/ssh-runner.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAWnE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qGAAqG;IACrG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,0DAA0D;IAC1D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,8CAA8C;IAC9C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC,CAAC;AAmCF,gGAAgG;AAChG,eAAO,MAAM,SAAS,YACX,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,mBAAmB,GAAG,eAAe,CAAC,CAAC,GACvF,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,SAC5B,MAAM,UACL,MAAM,UACN,UAAU,KACjB,OAAO,CAAC,UAAU,CAiBpB,CAAC;AAUF;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,YAAmB,gBAAgB,KAAG,OAAO,CAAC,UAAU,CAiE7E,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sudo-runner.d.ts","sourceRoot":"","sources":["../../src/linux/sudo-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,KAAK,gBAAgB,EAAa,MAAM,iBAAiB,CAAC;AAEnE,OAAO,EAUL,gBAAgB,EAKjB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,KAAK,MAAM,EAAe,MAAM,iBAAiB,CAAC;AAG3D,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG;IACpD,mFAAmF;IACnF,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC,CAAC;AAEF,iGAAiG;AACjG,MAAM,MAAM,WAAW,GAAG;IACxB,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,CAAC;AAeF,eAAO,MAAM,iBAAiB,eAAgB,SAAS,MAAM,EAAE,QAAQ,WAAW,KAAG,
|
|
1
|
+
{"version":3,"file":"sudo-runner.d.ts","sourceRoot":"","sources":["../../src/linux/sudo-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,KAAK,gBAAgB,EAAa,MAAM,iBAAiB,CAAC;AAEnE,OAAO,EAUL,gBAAgB,EAKjB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,KAAK,MAAM,EAAe,MAAM,iBAAiB,CAAC;AAG3D,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG;IACpD,mFAAmF;IACnF,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC,CAAC;AAEF,iGAAiG;AACjG,MAAM,MAAM,WAAW,GAAG;IACxB,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,CAAC;AAeF,eAAO,MAAM,iBAAiB,eAAgB,SAAS,MAAM,EAAE,QAAQ,WAAW,KAAG,UA8DpF,CAAC;AAsBF,gGAAgG;AAChG,eAAO,MAAM,aAAa,YAAmB,oBAAoB,KAAG,OAAO,CAAC,UAAU,CAYrF,CAAC"}
|
|
@@ -23,7 +23,20 @@ import type * as alias from '@distilled.cloud/opnsense/firewall_alias';
|
|
|
23
23
|
* noop declaration needs — see the exported `propsFromLive` alias in alias.ts's barrel and
|
|
24
24
|
* `alias-form.test.ts`'s round-trip test (`matches(attributesOf(uuid, live), attributesOf(uuid,
|
|
25
25
|
* live))` is always true by construction, and so is the general case).
|
|
26
|
+
*
|
|
27
|
+
* `live` is `ModelAliasReadItem` (the WHOLE-MODEL `get()`'s read-shaped item), not `AliasItem` —
|
|
28
|
+
* `type`/`interface`/`proto`/`categories` decode as option maps, not strings (OPNSENSE-2; see
|
|
29
|
+
* wire.ts's module doc). `selectedOf`/`selectedOneOf` extract the selected key(s) back to the
|
|
30
|
+
* same plain-string/string[] shape `AliasAttributes` already declared.
|
|
31
|
+
*
|
|
32
|
+
* `content` is `.\AliasContentField`, which overrides `getNodeData()` independently of
|
|
33
|
+
* `BaseListField` (`Firewall/FieldTypes/AliasContentField.php:69-76`) — it still decodes as a map,
|
|
34
|
+
* but every entry is unconditionally `selected: 1` (it is a raw newline-separated list, not a
|
|
35
|
+
* real option set), and ITS wire separator is `\n`, not `,`. `Object.keys` preserves the
|
|
36
|
+
* insertion order OPNsense's own `explode()` built the map in, which matters for a host/URL list
|
|
37
|
+
* in a way `selectedOf`'s alphabetical sort does not preserve — so this one field is reconstructed
|
|
38
|
+
* directly rather than through `selectedOf`.
|
|
26
39
|
*/
|
|
27
|
-
export declare const attributesOf: (uuid: string, live: alias.
|
|
40
|
+
export declare const attributesOf: (uuid: string, live: alias.ModelAliasReadItem) => AliasAttributes;
|
|
28
41
|
export declare const matches: (attributes: AliasAttributes, props: AliasProps) => boolean;
|
|
29
42
|
//# sourceMappingURL=alias-form.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alias-form.d.ts","sourceRoot":"","sources":["../../src/opnsense/alias-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,KAAK,KAAK,MAAM,0CAA0C,CAAC;AAGvE
|
|
1
|
+
{"version":3,"file":"alias-form.d.ts","sourceRoot":"","sources":["../../src/opnsense/alias-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,KAAK,KAAK,MAAM,0CAA0C,CAAC;AAGvE;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,YAAY,SAAU,MAAM,QAAQ,KAAK,CAAC,kBAAkB,KAAG,eAY1E,CAAC;AAEH,eAAO,MAAM,OAAO,eAAgB,eAAe,SAAS,UAAU,KAAG,OAUC,CAAC"}
|
package/dist/opnsense/alias.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export interface OpnsenseFirewallAlias extends Resource<'Opnsense.Firewall.Alias
|
|
|
42
42
|
export declare const OpnsenseFirewallAlias: import("alchemy").ResourceClass<OpnsenseFirewallAlias>;
|
|
43
43
|
export declare const isOpnsenseFirewallAlias: (value: unknown) => value is OpnsenseFirewallAlias;
|
|
44
44
|
/** Exported for direct testing against `fake-opnsense.ts` — the same seam `../discord/*.ts` uses. */
|
|
45
|
-
export declare const spec: OpnsenseSpec<AliasProps, Alias.
|
|
45
|
+
export declare const spec: OpnsenseSpec<AliasProps, Alias.ModelAliasReadItem, AliasAttributes, OpnsenseOpError>;
|
|
46
46
|
/**
|
|
47
47
|
* The pure declaration renderer: given the uuid `get()`'s map key names and the `AliasItem`
|
|
48
48
|
* decoded at that key, return the props a declaration needs so `firewallAlias.spec.matches`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alias.d.ts","sourceRoot":"","sources":["../../src/opnsense/alias.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"alias.d.ts","sourceRoot":"","sources":["../../src/opnsense/alias.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,KAAK,MAAM,0CAA0C,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAW,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGxE,MAAM,WAAW,UAAU;IACzB,mGAAmG;IACnG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mGAAmG;IACnG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,gGAAgG;IAChG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,qBAAsB,SAAQ,QAAQ,CACrD,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,KAAK,EACL,oBAAoB,CACrB;CAAG;AAEJ,eAAO,MAAM,qBAAqB,wDAGhC,CAAC;AAEH,eAAO,MAAM,uBAAuB,UAAW,OAAO,KAAG,KAAK,IAAI,qBAGA,CAAC;AAEnE,qGAAqG;AACrG,eAAO,MAAM,IAAI,EAAE,YAAY,CAC7B,UAAU,EACV,KAAK,CAAC,kBAAkB,EACxB,eAAe,EACf,eAAe,CAOhB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,qBAAe,CAAC;AAE1C,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;CAAyB,CAAC;AAE/C,eAAO,MAAM,6BAA6B,2IAIvC,CAAC;AAEJ;uGACuG;AACvG,eAAO,MAAM,aAAa,OAAQ,MAAM,SAAS,UAAU,sGACP,CAAC"}
|
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { GroupAttributes, GroupProps } from './group.ts';
|
|
7
7
|
import type * as group from '@distilled.cloud/opnsense/firewall_group';
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* ★ Attributes and the declaration renderer are one function — see alias-form.ts's header note.
|
|
10
|
+
*
|
|
11
|
+
* `live` is `ModelIfgroupentryReadItem` (the WHOLE-MODEL `get()`'s read-shaped item), not
|
|
12
|
+
* `GroupItem` — `members` (`.\InterfaceField`, `Multiple="Y"`) decodes as an option map, not a
|
|
13
|
+
* comma string (OPNSENSE-2; see wire.ts's module doc).
|
|
14
|
+
*/
|
|
15
|
+
export declare const attributesOf: (uuid: string, live: group.ModelIfgroupentryReadItem) => GroupAttributes;
|
|
10
16
|
export declare const matches: (attributes: GroupAttributes, props: GroupProps) => boolean;
|
|
11
17
|
//# sourceMappingURL=group-form.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"group-form.d.ts","sourceRoot":"","sources":["../../src/opnsense/group-form.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,KAAK,KAAK,MAAM,0CAA0C,CAAC;AAGvE
|
|
1
|
+
{"version":3,"file":"group-form.d.ts","sourceRoot":"","sources":["../../src/opnsense/group-form.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,KAAK,KAAK,MAAM,0CAA0C,CAAC;AAGvE;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,SACjB,MAAM,QACN,KAAK,CAAC,yBAAyB,KACpC,eAOD,CAAC;AAEH,eAAO,MAAM,OAAO,eAAgB,eAAe,SAAS,UAAU,KAAG,OAKL,CAAC"}
|
package/dist/opnsense/group.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface OpnsenseFirewallGroup extends Resource<'Opnsense.Firewall.Group
|
|
|
29
29
|
}
|
|
30
30
|
export declare const OpnsenseFirewallGroup: import("alchemy").ResourceClass<OpnsenseFirewallGroup>;
|
|
31
31
|
export declare const isOpnsenseFirewallGroup: (value: unknown) => value is OpnsenseFirewallGroup;
|
|
32
|
-
export declare const spec: OpnsenseSpec<GroupProps, Group.
|
|
32
|
+
export declare const spec: OpnsenseSpec<GroupProps, Group.ModelIfgroupentryReadItem, GroupAttributes, OpnsenseOpError>;
|
|
33
33
|
/** The pure declaration renderer — see alias.ts's `propsFromLive` for the full contract note. */
|
|
34
34
|
export declare const propsFromLive: typeof attributesOf;
|
|
35
35
|
export declare const handlers: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../../src/opnsense/group.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../../src/opnsense/group.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,KAAK,MAAM,0CAA0C,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAW,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGxE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,kGAAkG;IAClG,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,+FAA+F;IAC/F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,qBAAsB,SAAQ,QAAQ,CACrD,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,KAAK,EACL,oBAAoB,CACrB;CAAG;AAEJ,eAAO,MAAM,qBAAqB,wDAEhC,CAAC;AAEH,eAAO,MAAM,uBAAuB,UAAW,OAAO,KAAG,KAAK,IAAI,qBAGA,CAAC;AAEnE,eAAO,MAAM,IAAI,EAAE,YAAY,CAC7B,UAAU,EACV,KAAK,CAAC,yBAAyB,EAC/B,eAAe,EACf,eAAe,CAOhB,CAAC;AAEF,iGAAiG;AACjG,eAAO,MAAM,aAAa,qBAAe,CAAC;AAE1C,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;CAAyB,CAAC;AAE/C,eAAO,MAAM,6BAA6B,2IAIvC,CAAC;AAEJ,gGAAgG;AAChG,eAAO,MAAM,aAAa,OAAQ,MAAM,SAAS,UAAU,sGACP,CAAC"}
|
package/dist/opnsense/index.js
CHANGED
|
@@ -20,18 +20,26 @@ var numField = (value, fallback) => {
|
|
|
20
20
|
const parsed = Number(value);
|
|
21
21
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
22
22
|
};
|
|
23
|
+
var selectedOf = (map) => {
|
|
24
|
+
if (map === undefined)
|
|
25
|
+
return [];
|
|
26
|
+
const keys = Object.entries(map).filter(([key, entry]) => key !== "" && entry?.selected === 1).map(([key]) => key);
|
|
27
|
+
return [...new Set(keys)].sort();
|
|
28
|
+
};
|
|
29
|
+
var selectedOneOf = (map) => selectedOf(map)[0] ?? "";
|
|
23
30
|
|
|
24
31
|
// src/opnsense/alias-form.ts
|
|
25
32
|
var attributesOf = (uuid, live) => ({
|
|
26
|
-
categories:
|
|
27
|
-
content: live.content ??
|
|
33
|
+
categories: selectedOf(live.categories),
|
|
34
|
+
content: Object.keys(live.content ?? {}).join(`
|
|
35
|
+
`),
|
|
28
36
|
counters: bool01(live.counters, false),
|
|
29
37
|
description: live.description ?? "",
|
|
30
38
|
enabled: bool01(live.enabled, true),
|
|
31
|
-
interface: live.interface
|
|
39
|
+
interface: selectedOneOf(live.interface),
|
|
32
40
|
name: live.name,
|
|
33
|
-
proto: live.proto
|
|
34
|
-
type: live.type,
|
|
41
|
+
proto: selectedOf(live.proto).join(","),
|
|
42
|
+
type: selectedOneOf(live.type),
|
|
35
43
|
updatefreq: numField(live.updatefreq, 0),
|
|
36
44
|
uuid
|
|
37
45
|
});
|
|
@@ -158,7 +166,7 @@ import * as Effect5 from "effect/Effect";
|
|
|
158
166
|
var attributesOf3 = (uuid, live) => ({
|
|
159
167
|
description: live.descr ?? "",
|
|
160
168
|
ifname: live.ifname,
|
|
161
|
-
members:
|
|
169
|
+
members: selectedOf(live.members),
|
|
162
170
|
nogroup: bool01(live.nogroup, false),
|
|
163
171
|
sequence: numField(live.sequence, 0),
|
|
164
172
|
uuid
|
|
@@ -207,5 +215,5 @@ export {
|
|
|
207
215
|
providers
|
|
208
216
|
};
|
|
209
217
|
|
|
210
|
-
//# debugId=
|
|
218
|
+
//# debugId=681DFCB41A03047B64756E2164756E21
|
|
211
219
|
//# sourceMappingURL=index.js.map
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/opnsense/alias.ts", "../src/opnsense/wire.ts", "../src/opnsense/alias-form.ts", "../src/opnsense/resource.ts", "../src/opnsense/credentials.ts", "../src/opnsense/policy.ts", "../src/opnsense/category.ts", "../src/opnsense/category-form.ts", "../src/opnsense/group.ts", "../src/opnsense/group-form.ts", "../src/opnsense/providers.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * `Opnsense.Firewall.Alias` — a read-only declaration of one `firewall/alias` item on the edge.\n * READ-ONLY BY DESIGN — see policy.ts's header. `reconcile`/`delete` always refuse.\n *\n * ★ READ GOES THROUGH `get()`, NEVER `getItem`/`searchItem`. `get()` (`firewall_alias.ts#get`)\n * returns the WHOLE model tree — `{ alias: { aliases: { alias: { <uuid>:
|
|
6
|
-
"/**\n * How OPNsense's MVC model spells a value on the wire, and how to get it back to something\n * comparable — the `../proxmox/values.ts` idea, scoped to this vendor's own spellings.\n *\n * ★ OPNSENSE HAS EXACTLY TWO WIRE SHAPES THIS FAMILY NEEDS. Every `BooleanField` is the
|
|
7
|
-
"/**\n * `Opnsense.Firewall.Alias`'s wire shape: how a live `AliasItem` becomes attributes, and the\n * comparison that decides whether a declaration already matches it. Split out of alias.ts to\n * keep that file under the house's 250-line cap, mirroring `../proxmox/user-wire.ts`.\n *\n * ⛔ SCOPE, STATED ONCE. `AliasItem` (firewall_alias.ts) carries far more fields than this props\n * type declares — three groups are left out on purpose:\n * 1. Runtime counters (`current_items`, `last_updated`, `eval_nomatch`, `eval_match`,\n * `in_block_p`/`_b`, `in_pass_p`/`_b`, `out_block_p`/`_b`, `out_pass_p`/`_b`) — these are\n * the alias's hit/block statistics, not configuration, exactly the \"stats\" the task that\n * opened this family says to exclude.\n * 2. `path_expression` — only meaningful for the `urljson` alias type; a niche field this\n * first import pass leaves for a later one rather than modelling one type's one field.\n * 3. `password`/`username`/`authtype`/`expire` — the URL-fetch credential fields for the\n * `url`/`urltable`/`urljson` alias types. S25 (secrets stay out of props/attributes):\n * `password` is a literal stored credential and Alchemy persists Attributes unencrypted,\n * the same reasoning `../proxmox/user.ts` gives for shipping no `password` prop at all.\n */\nimport type { AliasAttributes, AliasProps } from './alias.ts';\nimport type * as alias from '@distilled.cloud/opnsense/firewall_alias';\nimport { bool01,
|
|
5
|
+
"/**\n * `Opnsense.Firewall.Alias` — a read-only declaration of one `firewall/alias` item on the edge.\n * READ-ONLY BY DESIGN — see policy.ts's header. `reconcile`/`delete` always refuse.\n *\n * ★ READ GOES THROUGH `get()`, NEVER `getItem`/`searchItem`. `get()` (`firewall_alias.ts#get`)\n * returns the WHOLE model tree — `{ alias: { aliases: { alias: { <uuid>: ModelAliasReadItem,\n * ... } } } }` — so finding one item by uuid is an object-key lookup, not a request the vendor\n * could answer ambiguously. `Alias` has no per-item `getAlias` operation at all (`getItemAction`\n * decorates its result — see the distilled clone's package README) — only `get` (whole model)\n * and `searchAlias` (paginated, see below) exist, so this family's whole-model read is the only\n * option here (unlike Category/Group, whose `getCategory`/`getGroup` per-item operations now\n * exist correctly — OPNSENSE-1 — but this file still uses `get()` uniformly across the three\n * resources; switching Category/Group to their per-item operation is a separate, later PR).\n * `ModelAliasReadItem`'s list-shaped fields (`type`/`interface`/`proto`/`categories`) decode as\n * option maps, not strings (OPNSENSE-2) — see `alias-form.ts`'s `attributesOf`.\n *\n * ⛔ NOT `searchAlias`. It is POST-shaped (a read, per OPNsense's own MVC convention — see the\n * family's docs page) and paginated with no typed limit/offset the generated request schema\n * exposes, so a large alias set could silently page-truncate through it. `get()` has neither\n * problem: GET, and the WHOLE model in one call, no pagination to fall short of.\n *\n * ⚠️ `uuid` IS THE ONLY STABLE IDENTITY THIS FAMILY DECLARES BY. A name is close to stable\n * (`AliasNameField` is unique) but is not what `get()`'s map is keyed on — obtain the live\n * uuid once (a future import script does this from a live read, via `propsFromLive` below)\n * and declare it from then on, the same \"adoption is explicit, one identity at a time\" posture\n * `../proxmox/user.ts` and `../netbox/resource.ts` document for their own families.\n */\nimport { adopt } from 'alchemy/AdoptPolicy';\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as Alias from '@distilled.cloud/opnsense/firewall_alias';\nimport type { OpnsenseOpError } from '@distilled.cloud/opnsense/Protocol';\nimport * as Effect from 'effect/Effect';\nimport { attributesOf, matches } from './alias-form.ts';\nimport type { OpnsenseRequirements, OpnsenseSpec } from './resource.ts';\nimport { opnsenseHandlers } from './resource.ts';\n\nexport interface AliasProps {\n /** OPNsense-assigned uuid — see the file header on why this is the only identity to declare by. */\n readonly uuid: string;\n readonly name: string;\n /** e.g. `host`, `network`, `port`, `url`, `geoip`, `networkgroup` — OPNsense's own alias types. */\n readonly type: string;\n /** Default true, matching OPNsense's own `enabled=1`. */\n readonly enabled?: boolean;\n /** The alias's members — a host list, a CIDR, a URL, depending on `type`. */\n readonly content?: string;\n readonly interface?: string;\n /** `IPv4` or `IPv6`, for a `geoip`/`networkgroup` alias; comma-joined on the wire (wire.ts). */\n readonly proto?: string;\n readonly counters?: boolean;\n /** Minutes between refreshes, for a `url`/`urltable` alias. */\n readonly updatefreq?: number;\n readonly categories?: readonly string[];\n readonly description?: string;\n}\n\nexport interface AliasAttributes {\n readonly uuid: string;\n readonly name: string;\n readonly type: string;\n readonly enabled: boolean;\n readonly content: string;\n readonly interface: string;\n readonly proto: string;\n readonly counters: boolean;\n readonly updatefreq: number;\n readonly categories: readonly string[];\n readonly description: string;\n}\n\nexport interface OpnsenseFirewallAlias extends Resource<\n 'Opnsense.Firewall.Alias',\n AliasProps,\n AliasAttributes,\n never,\n OpnsenseRequirements\n> {}\n\nexport const OpnsenseFirewallAlias = Resource<OpnsenseFirewallAlias>('Opnsense.Firewall.Alias', {\n // The edge's own config, not this stack's to reclaim — see policy.ts.\n defaultRemovalPolicy: 'retain',\n});\n\nexport const isOpnsenseFirewallAlias = (value: unknown): value is OpnsenseFirewallAlias =>\n typeof value === 'object' &&\n value !== null &&\n (value as { Type?: unknown }).Type === 'Opnsense.Firewall.Alias';\n\n/** Exported for direct testing against `fake-opnsense.ts` — the same seam `../discord/*.ts` uses. */\nexport const spec: OpnsenseSpec<\n AliasProps,\n Alias.ModelAliasReadItem,\n AliasAttributes,\n OpnsenseOpError\n> = {\n attributes: attributesOf,\n fetchLive: (props) =>\n Alias.get({}).pipe(Effect.map((res) => res.alias?.aliases?.alias?.[props.uuid])),\n matches,\n resourceType: 'Opnsense.Firewall.Alias',\n};\n\n/**\n * The pure declaration renderer: given the uuid `get()`'s map key names and the `AliasItem`\n * decoded at that key, return the props a declaration needs so `firewallAlias.spec.matches`\n * reports noop against it. A later import script iterates `Object.entries(map)` and calls this\n * once per row to generate an `alchemy.run.ts` row — see `alias-form.test.ts`'s round-trip test.\n */\nexport const propsFromLive = attributesOf;\n\nexport const handlers = opnsenseHandlers(spec);\n\nexport const OpnsenseFirewallAliasProvider = () =>\n Provider.effect(\n OpnsenseFirewallAlias,\n Effect.succeed(OpnsenseFirewallAlias.Provider.of(handlers)),\n );\n\n/** `firewallAlias('name-alias', { uuid, name, type })` — `adopt(true)` piped by default (H5), so\n * declaring a live alias's own uuid takes it over silently instead of failing `OwnedBySomeoneElse`. */\nexport const firewallAlias = (id: string, props: AliasProps) =>\n OpnsenseFirewallAlias(id, props).pipe(adopt(true));\n",
|
|
6
|
+
"/**\n * How OPNsense's MVC model spells a value on the wire, and how to get it back to something\n * comparable — the `../proxmox/values.ts` idea, scoped to this vendor's own spellings.\n *\n * ★ OPNSENSE HAS EXACTLY TWO WIRE SHAPES THIS FAMILY NEEDS ON WRITE (props stay plain values;\n * this family never writes, but `matches` compares against them). Every `BooleanField` is the\n * string `\"1\"` or `\"0\"` (never a JSON boolean). Every multi-select `OptionField`/\n * `ModelRelationField`'s WRITE-side spelling is a SINGLE comma-joined string, not a JSON array.\n *\n * ⚠️ READ IS A THIRD SHAPE (OPNSENSE-2, distilled `homeflare/opnsense`'s `read-shape.ts`): the\n * SAME list-shaped fields decode on GET as an option map `{key: {value, selected}}`, not a\n * string — `AliasItem`/`GroupItem` (this family's OLD, pre-fix type references) were the\n * WRITE-shaped structs; `fetchLive` actually decodes `ModelAliasReadItem`/\n * `ModelIfgroupentryReadItem` (the WHOLE-MODEL GET's read-shaped item), whose `type`/\n * `interface`/`proto`/`categories`/`members` are option maps. `selectedOf`/`selectedOneOf`\n * below extract the selected key(s) back to the SAME plain-string/string[] shape `matches`\n * already compares, so the rest of this family's logic (and `AliasAttributes`/\n * `GroupAttributes`'s own field types) is unchanged.\n *\n * ⚠️ COMMA-LIST ORDER ROUND-TRIP IS NOT ASSUMED STABLE. OPNsense builds these from a PHP\n * associative array, and nothing in the model XML promises the join order survives a\n * save-then-read unchanged. Sorting and deduplicating both sides before comparing is what makes\n * `matches` (alias-form.ts, group-form.ts) actually settle instead of reporting drift on a\n * re-ordered list that changed nothing — the same reasoning `../proxmox/user-wire.ts`'s\n * `groupSet` documents for PVE's `groups` field.\n */\n\n/** OPNsense's own boolean spelling. Absent takes the caller's stated default — OPNsense's own\n * default for a `BooleanField` is form-specific, so there is no house-wide fallback here. */\nexport const bool01 = (value: string | undefined, fallback: boolean): boolean =>\n value === undefined ? fallback : value === '1';\n\n/** The write direction, for symmetry — unused today (this family never writes), kept so a form\n * helper never has to guess the spelling if lifting the read-only policy adds one later. */\nexport const toBool01 = (value: boolean): string => (value ? '1' : '0');\n\n/**\n * A comma-joined multi-select column, normalised to a sorted, deduplicated set — see the module\n * header's ⚠️ for why normalising (not just splitting) is what makes a diff settle.\n */\nexport const csvSet = (value: string | undefined): readonly string[] => {\n if (value === undefined || value.trim() === '') return [];\n const items = value\n .split(',')\n .map((item) => item.trim())\n .filter((item) => item !== '');\n return [...new Set(items)].sort();\n};\n\n/** A declared array, normalised the SAME way `csvSet` normalises the wire, so `matches` compares\n * two sets built by one function rather than trusting the caller's array to already be sorted. */\nexport const csvSetOf = (value: readonly string[] | undefined): readonly string[] =>\n csvSet(value === undefined ? undefined : value.join(','));\n\n/**\n * OPNsense's numeric-as-string fields (`\"60\"`, `\"0\"`), tolerant of an already-numeric input the\n * same way `../proxmox/user-wire.ts`'s `int` is tolerant of PVE's mixed number/string releases.\n */\nexport const numField = (value: string | undefined, fallback: number): number => {\n if (value === undefined) return fallback;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : fallback;\n};\n\n/** One entry of a GET-decoded option map — `{value: <label>, selected: 0|1}`, see this file's\n * module doc. Structural, not imported from the SDK: every generated `*Map` field shares this\n * exact shape (`OptionEntry` in each service module), and staying structural here avoids a\n * per-resource-file type import just for this. */\nexport interface OpnsenseOptionEntry {\n readonly value: string;\n readonly selected: number;\n}\n\n/**\n * The selected key(s) of a GET-decoded option map, normalised the same way `csvSet` normalises\n * the write-side comma string — sorted, deduplicated, empty-key filtered. Use for a\n * `Multiple=\"Y\"` field (`AliasItem.proto`, `AliasItem.categories`, `GroupItem.members`); for a\n * single-select field use `selectedOneOf`.\n */\nexport const selectedOf = (\n map: Readonly<Record<string, OpnsenseOptionEntry | undefined>> | undefined,\n): readonly string[] => {\n if (map === undefined) return [];\n const keys = Object.entries(map)\n .filter(([key, entry]) => key !== '' && entry?.selected === 1)\n .map(([key]) => key);\n return [...new Set(keys)].sort();\n};\n\n/** Single-select convenience over `selectedOf` — the one selected key, or `''` if none (the same\n * \"absent\" spelling a plain string field uses, e.g. `AliasItem.type`/`GroupItem`'s own\n * `.\\InterfaceField` members when not `Multiple=\"Y\"`). */\nexport const selectedOneOf = (\n map: Readonly<Record<string, OpnsenseOptionEntry | undefined>> | undefined,\n): string => selectedOf(map)[0] ?? '';\n\n/** The encode direction, for test fixtures: every listed key marked `selected: 1`, mirroring a\n * real OPNsense GET response (`{[key]: {value, selected: 1}}`) — so a fixture exercises the exact\n * decode path `selectedOf`/`selectedOneOf` handle, not the pre-fix plain-string shape. */\nexport const optionMap = (...keys: readonly string[]): Record<string, OpnsenseOptionEntry> =>\n Object.fromEntries(keys.map((key) => [key, { value: key, selected: 1 }]));\n",
|
|
7
|
+
"/**\n * `Opnsense.Firewall.Alias`'s wire shape: how a live `AliasItem` becomes attributes, and the\n * comparison that decides whether a declaration already matches it. Split out of alias.ts to\n * keep that file under the house's 250-line cap, mirroring `../proxmox/user-wire.ts`.\n *\n * ⛔ SCOPE, STATED ONCE. `AliasItem` (firewall_alias.ts) carries far more fields than this props\n * type declares — three groups are left out on purpose:\n * 1. Runtime counters (`current_items`, `last_updated`, `eval_nomatch`, `eval_match`,\n * `in_block_p`/`_b`, `in_pass_p`/`_b`, `out_block_p`/`_b`, `out_pass_p`/`_b`) — these are\n * the alias's hit/block statistics, not configuration, exactly the \"stats\" the task that\n * opened this family says to exclude.\n * 2. `path_expression` — only meaningful for the `urljson` alias type; a niche field this\n * first import pass leaves for a later one rather than modelling one type's one field.\n * 3. `password`/`username`/`authtype`/`expire` — the URL-fetch credential fields for the\n * `url`/`urltable`/`urljson` alias types. S25 (secrets stay out of props/attributes):\n * `password` is a literal stored credential and Alchemy persists Attributes unencrypted,\n * the same reasoning `../proxmox/user.ts` gives for shipping no `password` prop at all.\n */\nimport type { AliasAttributes, AliasProps } from './alias.ts';\nimport type * as alias from '@distilled.cloud/opnsense/firewall_alias';\nimport { bool01, csvSetOf, numField, selectedOf, selectedOneOf } from './wire.ts';\n\n/**\n * ★ ATTRIBUTES AND THE DECLARATION RENDERER ARE ONE FUNCTION. Attributes ARE exactly the props a\n * noop declaration needs — see the exported `propsFromLive` alias in alias.ts's barrel and\n * `alias-form.test.ts`'s round-trip test (`matches(attributesOf(uuid, live), attributesOf(uuid,\n * live))` is always true by construction, and so is the general case).\n *\n * `live` is `ModelAliasReadItem` (the WHOLE-MODEL `get()`'s read-shaped item), not `AliasItem` —\n * `type`/`interface`/`proto`/`categories` decode as option maps, not strings (OPNSENSE-2; see\n * wire.ts's module doc). `selectedOf`/`selectedOneOf` extract the selected key(s) back to the\n * same plain-string/string[] shape `AliasAttributes` already declared.\n *\n * `content` is `.\\AliasContentField`, which overrides `getNodeData()` independently of\n * `BaseListField` (`Firewall/FieldTypes/AliasContentField.php:69-76`) — it still decodes as a map,\n * but every entry is unconditionally `selected: 1` (it is a raw newline-separated list, not a\n * real option set), and ITS wire separator is `\\n`, not `,`. `Object.keys` preserves the\n * insertion order OPNsense's own `explode()` built the map in, which matters for a host/URL list\n * in a way `selectedOf`'s alphabetical sort does not preserve — so this one field is reconstructed\n * directly rather than through `selectedOf`.\n */\nexport const attributesOf = (uuid: string, live: alias.ModelAliasReadItem): AliasAttributes => ({\n categories: selectedOf(live.categories),\n content: Object.keys(live.content ?? {}).join('\\n'),\n counters: bool01(live.counters, false),\n description: live.description ?? '',\n enabled: bool01(live.enabled, true),\n interface: selectedOneOf(live.interface),\n name: live.name,\n proto: selectedOf(live.proto).join(','),\n type: selectedOneOf(live.type),\n updatefreq: numField(live.updatefreq, 0),\n uuid,\n});\n\nexport const matches = (attributes: AliasAttributes, props: AliasProps): boolean =>\n attributes.name === props.name &&\n attributes.type === props.type &&\n attributes.enabled === (props.enabled ?? true) &&\n attributes.content === (props.content ?? '') &&\n attributes.interface === (props.interface ?? '') &&\n attributes.proto === (props.proto ?? '') &&\n attributes.counters === (props.counters ?? false) &&\n attributes.updatefreq === (props.updatefreq ?? 0) &&\n attributes.description === (props.description ?? '') &&\n attributes.categories.join(',') === csvSetOf(props.categories).join(',');\n",
|
|
8
8
|
"/**\n * One shape for every OPNsense object this family declares — `read`/`diff` mirroring\n * `../discord/resource.ts`'s marker-less-API posture exactly (S7–S9, H1), and `reconcile`/\n * `delete` replaced end to end with the read-only refusal `policy.ts` states.\n *\n * ★ WHY ONE ENGINE FOR THREE RESOURCES. `alias.ts`, `category.ts` and `group.ts` differ only in\n * which `firewall_*` module they call and how a live item maps to attributes — the read/diff/\n * reconcile/delete flow is identical, so it lives here once, the way `netbox/resource.ts` and\n * `discord/resource.ts` already do for their own families.\n *\n * ★ `Unowned(attrs)` ON A COLD READ, PLAIN ATTRIBUTES ON A WARM ONE (S7, S8). None of Alias,\n * Category or Group carries an ownership marker OPNsense itself understands — no tag, no\n * metadata field a stack could stamp — so this is the same \"marker-less API\" S8 describes, and\n * the same posture `../discord/resource.ts` documents against `Snippet.ts`'s reference read.\n * `adopt(true)` (H5), piped on by each resource file's convenience constructor, is what turns\n * an `Unowned` first read into a silent one-time takeover instead of an `OwnedBySomeoneElse`\n * refusal — exactly what an import script generating `alchemy.run.ts` rows from live reads\n * needs: declare what is live, adopt it, change nothing.\n *\n * ⛔ `reconcile` AND `delete` NEVER CALL AN SDK OPERATION. `reconcile` still OBSERVES first (one\n * GET, via `read`) so the refusal it raises can say whether it would have created or updated —\n * S9's \"observe before you write\" doctrine, kept for the error message even though there is no\n * write to guard. `delete` observes nothing: it refuses unconditionally, because under this\n * policy a delete never succeeds regardless of whether the object is still live. See policy.ts.\n */\nimport { Unowned } from 'alchemy/AdoptPolicy';\nimport { isResolved } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport type { OpnsenseOpContext } from '@distilled.cloud/opnsense/Protocol';\nimport * as Effect from 'effect/Effect';\nimport type * as HttpClient from 'effect/unstable/http/HttpClient';\nimport { CredentialsFromEnv } from './credentials.ts';\nimport { refuseWrite } from './policy.ts';\n\n/** What every stack must still provide after `opnsenseHandlers` bakes in `CredentialsFromEnv`. */\nexport type OpnsenseRequirements = HttpClient.HttpClient;\n\n/** Every resource in this family identifies its live object by OPNsense's own assigned uuid. */\nexport interface OpnsenseIdentity {\n readonly uuid: string;\n}\n\n/**\n * One OPNsense object's read path. `Live` is whatever the generated `get()` response decodes to\n * for one item (an `AliasItem`, `CategoryItem`, `GroupItem`); `E` is left to each resource file\n * to declare — in practice always `OpnsenseOpError`, since every generated operation shares one\n * error channel (protocol.ts).\n */\nexport type OpnsenseSpec<Props extends OpnsenseIdentity, Live, Attributes extends object, E> = {\n readonly resourceType: string;\n /** Absent when `props.uuid` is not a key in the live model's map — see each resource file. */\n readonly fetchLive: (props: Props) => Effect.Effect<Live | undefined, E, OpnsenseOpContext>;\n /** Also the declaration renderer (S.T. propsFromLive re-export) — see each `*-form.ts`. */\n readonly attributes: (uuid: string, live: Live) => Attributes;\n readonly matches: (attributes: Attributes, props: Props) => boolean;\n};\n\nexport const opnsenseOperations = <\n Props extends OpnsenseIdentity,\n Live,\n Attributes extends object,\n E,\n>(\n spec: OpnsenseSpec<Props, Live, Attributes, E>,\n) => {\n const read = (props: Props) =>\n spec\n .fetchLive(props)\n .pipe(\n Effect.map((live) => (live === undefined ? undefined : spec.attributes(props.uuid, live))),\n );\n\n return {\n read: ({ olds, output }: { olds: Props; output: Attributes | undefined }) =>\n Effect.gen(function* () {\n const attrs = yield* read(olds);\n if (attrs === undefined) return undefined;\n // Warm read: a prior apply already adopted this exact uuid — ours.\n if (output !== undefined) return attrs;\n // Cold read (fresh store, or state lost): no ownership marker exists on this vendor.\n return Unowned(attrs);\n }),\n\n diff: (news: Input<Props>, output: Attributes | undefined) =>\n Effect.gen(function* () {\n if (output === undefined || !isResolved(news)) return undefined;\n const live = yield* read(news);\n if (live === undefined) return { action: 'update' } as const;\n return spec.matches(live, news)\n ? ({ action: 'noop' } as const)\n : ({ action: 'update' } as const);\n }),\n\n reconcile: (news: Props) =>\n Effect.gen(function* () {\n const live = yield* read(news);\n return yield* refuseWrite(\n spec.resourceType,\n news.uuid,\n live === undefined ? 'create' : 'update',\n );\n }),\n\n destroy: (olds: Props) => refuseWrite(spec.resourceType, olds.uuid, 'delete'),\n };\n};\n\n/**\n * The provider handlers for a spec'd OPNsense object, wired once. `list` answers empty — every\n * `get()` in this family returns the WHOLE model tree with no per-account/per-scope narrowing,\n * so a `list` that enumerated it would sweep the estate's entire alias/category/group set into\n * one plan; adoption stays explicit, one uuid at a time, the same reasoning netbox's and\n * discord's own `list` give for a shared or marker-less object space (S12).\n *\n * `nuke: { skip: true }` — `delete` refuses unconditionally (policy.ts), so `alchemy unsafe\n * nuke` could never do anything here but fail loudly on every object it found; skipping keeps\n * that refusal out of a nuke census entirely, matching `postgres/database.ts` and\n * `github/repository-ruleset.ts`'s own `nuke: { skip: true }` for the same reason.\n */\nexport const opnsenseHandlers = <\n Props extends OpnsenseIdentity,\n Live,\n Attributes extends object,\n E,\n>(\n spec: OpnsenseSpec<Props, Live, Attributes, E>,\n) => {\n const ops = opnsenseOperations(spec);\n // ⚠️ GENERIC OVER ITS OWN ERROR AND REQUIREMENT CHANNELS, NOT PINNED TO `E`/`OpnsenseOpContext`.\n // `read`/`diff` fail with `E` and need `OpnsenseOpContext`; `reconcile` fails with\n // `E | OpnsenseWriteRefused` (it observes, then refuses) and still needs `OpnsenseOpContext`\n // for that observe; `delete` fails with `OpnsenseWriteRefused` alone and needs NOTHING (it\n // never observes) — three different signatures sharing one credentials-providing wrapper.\n const withCredentials = <A, Err, R>(effect: Effect.Effect<A, Err, R>) =>\n Effect.provide(effect, CredentialsFromEnv);\n return {\n list: () => Effect.succeed([]),\n nuke: { skip: true },\n read: ({ olds, output }: { olds: Props; output: Attributes | undefined }) =>\n withCredentials(ops.read({ olds, output })),\n diff: ({ news, output }: { news: Input<Props>; output: Attributes | undefined }) =>\n withCredentials(ops.diff(news, output)),\n reconcile: ({ news }: { news: Props }) => withCredentials(ops.reconcile(news)),\n delete: ({ olds }: { olds: Props }) => withCredentials(ops.destroy(olds)),\n };\n};\n",
|
|
9
9
|
"/**\n * OPNsense credentials — a key/secret pair REFERENCE, resolved lazily (S24), never a prop.\n *\n * ⛔ NO KEY OR SECRET IS EVER A PROP OR AN ATTRIBUTE. Alchemy persists Attributes unencrypted\n * (`State/StateEncoding.ts@v2.0.0-beta.79#encodeState`), so nothing a stack file holds may be a\n * credential. `@distilled.cloud/opnsense/Credentials` already ships exactly the shape S24 asks\n * for: `Credentials` is a lazy `Context.Service<Credentials, Effect.Effect<Config>>`, resolved\n * on the calling fiber inside each operation (`OpnsenseProtocol`'s `encode`), never at layer\n * build. This file re-exports it rather than re-implementing it — the same choice\n * `../discord/credentials.ts` made for its own SDK's `CredentialsFromEnv`.\n *\n * ★ `CredentialsFromEnv` READS `OPNSENSE_URL` / `OPNSENSE_API_KEY` / `OPNSENSE_API_SECRET` via\n * Effect's `Config` (the SDK's own `credentials.ts` — no vendor-standard env var names exist\n * for OPNsense's API, unlike Forgejo's `FORGEJO_URL`). The resource never sees the values,\n * only the deploy process's environment does, and the secret is `Redacted` the moment it is\n * read — exactly the \"reference, never a value\" shape S25 asks for.\n *\n * ⚠️ NO OpenBao MINT YET, THE SAME GAP `../discord/credentials.ts` DOCUMENTS. H8's target\n * (`Alchemy.Stack({ secrets })`) is unreleased (PR 1728, `main` only). Until the estate is on a\n * release that carries it, the deploy process's own environment is the credential seam — a\n * human or CI job exports the three `OPNSENSE_*` variables from wherever it mints them. This\n * package never mints, reads or logs the key/secret itself; it only names the variables.\n */\nimport type { Config } from '@distilled.cloud/opnsense/Credentials';\nimport {\n Credentials,\n CredentialsFromEnv,\n credentials,\n} from '@distilled.cloud/opnsense/Credentials';\n\n/** Re-exported so a consumer never has to reach into `@distilled.cloud/opnsense` by hand. */\nexport { Credentials, CredentialsFromEnv, credentials };\nexport type { Config as OpnsenseCredentialsConfig };\n\n/**\n * `OpnsenseCredentials.fromEnv()` — the layer `opnsenseHandlers` (resource.ts) provides by\n * default. A thin, named alias over `CredentialsFromEnv` so a stack's import reads `Opnsense.*`\n * throughout, the way `../discord/credentials.ts`'s `fromEnv()` keeps `Discord.*` names at the\n * front door.\n */\nexport const fromEnv = () => CredentialsFromEnv;\n",
|
|
10
10
|
"/**\n * `OpnsenseWriteRefused` — the one error every resource in this family can raise for a write,\n * and the only thing standing between \"read-only for now\" and an actual change against the edge.\n *\n * ⛔ TIM, 2026-09-24: UniFi AND OPNSENSE ARE READ-ONLY FOR NOW. `Opnsense.Firewall.Alias`,\n * `.Category` and `.Group` (alias.ts, category.ts, group.ts) exist to `read`, `diff` and adopt\n * against the live edge — never to change it. This is the estate's EDGE firewall: a failed\n * probe gets the caller's IP CrowdSec-banned (`edge-crowdsec-bans-lan-ssh-probes` in the shared\n * estate memory), and nothing about that risk is worth a resource that can also reconfigure it.\n * Every `reconcile` and `delete` in this directory calls `refuseWrite` below and NOTHING ELSE —\n * none of them imports an `add`/`set`/`del`/`toggle` operation from `@distilled.cloud/opnsense`\n * at all, and `write-refusal.test.ts` proves it with a fake HTTP client that fails the test on\n * any request that is not a GET.\n *\n * ★ LIFTING THIS IS A KIT CHANGE, NEVER A PROP. There is no `allowWrite` flag on any resource\n * here — a flag is one bad `true` (or one bad default) away from a live edit to the box that\n * fronts the whole estate. Lifting the policy means editing this file, and writing the\n * create/update/delete bodies these resources do not have, a decision Tim makes explicitly by\n * changing code and review, not a runtime toggle a stack can set.\n */\nimport * as Data from 'effect/Data';\nimport * as Effect from 'effect/Effect';\n\nexport type OpnsenseWriteAction = 'create' | 'update' | 'delete';\n\nexport class OpnsenseWriteRefused extends Data.TaggedError('OpnsenseWriteRefused')<{\n readonly resourceType: string;\n readonly id: string;\n readonly action: OpnsenseWriteAction;\n}> {\n override get message(): string {\n return (\n `${this.resourceType} ${this.id}: refusing to ${this.action} — read-only by Tim's rule, ` +\n '2026-09-24; lifting it is a kit change, not a prop.'\n );\n }\n}\n\n/**\n * Every `reconcile`/`delete` handler in this family funnels through this one call — see\n * `resource.ts`'s `opnsenseOperations`. Nothing downstream of this function can reach a write:\n * it never calls an SDK operation, it only builds and fails with the typed refusal above.\n */\nexport const refuseWrite = (\n resourceType: string,\n id: string,\n action: OpnsenseWriteAction,\n): Effect.Effect<never, OpnsenseWriteRefused> =>\n Effect.fail(new OpnsenseWriteRefused({ action, id, resourceType }));\n",
|
|
11
11
|
"/**\n * `Opnsense.Firewall.Category` — a read-only declaration of one `firewall/category` item (the\n * tag OPNsense's UI groups aliases, rules etc. by) on the edge. READ-ONLY BY DESIGN — see\n * policy.ts's header. `reconcile`/`delete` always refuse. Mirrors alias.ts exactly; see that\n * file's header for why `get()` (whole model) is the read source rather than `getCategory` —\n * the same generation gap (no `uuid` in `GetCategoryRequest`'s schema) applies here too.\n */\nimport { adopt } from 'alchemy/AdoptPolicy';\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as Category from '@distilled.cloud/opnsense/firewall_category';\nimport type { OpnsenseOpError } from '@distilled.cloud/opnsense/Protocol';\nimport * as Effect from 'effect/Effect';\nimport { attributesOf, matches } from './category-form.ts';\nimport type { OpnsenseRequirements, OpnsenseSpec } from './resource.ts';\nimport { opnsenseHandlers } from './resource.ts';\n\nexport interface CategoryProps {\n readonly uuid: string;\n /** Pattern `/[^,]+/` on the wire — a comma in the name would collide with the multi-select join. */\n readonly name: string;\n /** Whether OPNsense itself created this category (its own subsystems tag things this way); a\n * category a person made is `false`/absent. */\n readonly auto?: boolean;\n /** Six hex digits, no `#`, e.g. `0000FF`. */\n readonly color?: string;\n}\n\nexport interface CategoryAttributes {\n readonly uuid: string;\n readonly name: string;\n readonly auto: boolean;\n readonly color: string;\n}\n\nexport interface OpnsenseFirewallCategory extends Resource<\n 'Opnsense.Firewall.Category',\n CategoryProps,\n CategoryAttributes,\n never,\n OpnsenseRequirements\n> {}\n\nexport const OpnsenseFirewallCategory = Resource<OpnsenseFirewallCategory>(\n 'Opnsense.Firewall.Category',\n {\n defaultRemovalPolicy: 'retain',\n },\n);\n\nexport const isOpnsenseFirewallCategory = (value: unknown): value is OpnsenseFirewallCategory =>\n typeof value === 'object' &&\n value !== null &&\n (value as { Type?: unknown }).Type === 'Opnsense.Firewall.Category';\n\nexport const spec: OpnsenseSpec<\n CategoryProps,\n Category.CategoryItem,\n CategoryAttributes,\n OpnsenseOpError\n> = {\n attributes: attributesOf,\n fetchLive: (props) =>\n Category.get({}).pipe(Effect.map((res) => res.category?.categories?.category?.[props.uuid])),\n matches,\n resourceType: 'Opnsense.Firewall.Category',\n};\n\n/** The pure declaration renderer — see alias.ts's `propsFromLive` for the full contract note. */\nexport const propsFromLive = attributesOf;\n\nexport const handlers = opnsenseHandlers(spec);\n\nexport const OpnsenseFirewallCategoryProvider = () =>\n Provider.effect(\n OpnsenseFirewallCategory,\n Effect.succeed(OpnsenseFirewallCategory.Provider.of(handlers)),\n );\n\n/** `firewallCategory('name', { uuid, name })` — `adopt(true)` piped by default (H5). */\nexport const firewallCategory = (id: string, props: CategoryProps) =>\n OpnsenseFirewallCategory(id, props).pipe(adopt(true));\n",
|
|
12
12
|
"/**\n * `Opnsense.Firewall.Category`'s wire shape: how a live `CategoryItem` becomes attributes, and\n * the comparison that decides whether a declaration already matches it. Mirrors alias-form.ts.\n */\nimport type { CategoryAttributes, CategoryProps } from './category.ts';\nimport type * as category from '@distilled.cloud/opnsense/firewall_category';\nimport { bool01 } from './wire.ts';\n\n/** ★ Attributes and the declaration renderer are one function — see alias-form.ts's header note. */\nexport const attributesOf = (uuid: string, live: category.CategoryItem): CategoryAttributes => ({\n auto: bool01(live.auto, false),\n color: live.color ?? '',\n name: live.name,\n uuid,\n});\n\nexport const matches = (attributes: CategoryAttributes, props: CategoryProps): boolean =>\n attributes.name === props.name &&\n attributes.auto === (props.auto ?? false) &&\n attributes.color === (props.color ?? '');\n",
|
|
13
|
-
"/**\n * `Opnsense.Firewall.Group` — a read-only declaration of one `firewall/group` item (an\n * \"Interface Group\", not a permission group) on the edge. READ-ONLY BY DESIGN — see policy.ts's\n * header. `reconcile`/`delete` always refuse. Mirrors alias.ts exactly; see that file's header\n * for why `get()` (whole model) is the read source rather than `getGroup`
|
|
14
|
-
"/**\n * `Opnsense.Firewall.Group`'s wire shape: how a live `GroupItem` (an interface group) becomes\n * attributes, and the comparison that decides whether a declaration already matches it. Mirrors\n * alias-form.ts.\n */\nimport type { GroupAttributes, GroupProps } from './group.ts';\nimport type * as group from '@distilled.cloud/opnsense/firewall_group';\nimport { bool01,
|
|
13
|
+
"/**\n * `Opnsense.Firewall.Group` — a read-only declaration of one `firewall/group` item (an\n * \"Interface Group\", not a permission group) on the edge. READ-ONLY BY DESIGN — see policy.ts's\n * header. `reconcile`/`delete` always refuse. Mirrors alias.ts exactly; see that file's header\n * for why `get()` (whole model) is the read source uniformly across this family rather than the\n * now-correct `getGroup` per-item operation (OPNSENSE-1 fixed `GetGroupRequest`'s missing `uuid`,\n * but switching to it is a separate, later PR). `members` decodes as an option map, not a comma\n * string — see `group-form.ts`'s `attributesOf` (OPNSENSE-2).\n */\nimport { adopt } from 'alchemy/AdoptPolicy';\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as Group from '@distilled.cloud/opnsense/firewall_group';\nimport type { OpnsenseOpError } from '@distilled.cloud/opnsense/Protocol';\nimport * as Effect from 'effect/Effect';\nimport { attributesOf, matches } from './group-form.ts';\nimport type { OpnsenseRequirements, OpnsenseSpec } from './resource.ts';\nimport { opnsenseHandlers } from './resource.ts';\n\nexport interface GroupProps {\n readonly uuid: string;\n /** OPNsense's own `GroupNameField` — the interface group's name. */\n readonly ifname: string;\n /** Member interface names (`lan`, `opt1`, …), not their descriptions. */\n readonly members: readonly string[];\n /** OPNsense's own \"no group\" flag — excludes it from certain interface pickers. Default false. */\n readonly nogroup?: boolean;\n /** UI list position, `0`..`9999`. Carries no evaluation-order meaning for this object type. */\n readonly sequence?: number;\n readonly description?: string;\n}\n\nexport interface GroupAttributes {\n readonly uuid: string;\n readonly ifname: string;\n readonly members: readonly string[];\n readonly nogroup: boolean;\n readonly sequence: number;\n readonly description: string;\n}\n\nexport interface OpnsenseFirewallGroup extends Resource<\n 'Opnsense.Firewall.Group',\n GroupProps,\n GroupAttributes,\n never,\n OpnsenseRequirements\n> {}\n\nexport const OpnsenseFirewallGroup = Resource<OpnsenseFirewallGroup>('Opnsense.Firewall.Group', {\n defaultRemovalPolicy: 'retain',\n});\n\nexport const isOpnsenseFirewallGroup = (value: unknown): value is OpnsenseFirewallGroup =>\n typeof value === 'object' &&\n value !== null &&\n (value as { Type?: unknown }).Type === 'Opnsense.Firewall.Group';\n\nexport const spec: OpnsenseSpec<\n GroupProps,\n Group.ModelIfgroupentryReadItem,\n GroupAttributes,\n OpnsenseOpError\n> = {\n attributes: attributesOf,\n fetchLive: (props) =>\n Group.get({}).pipe(Effect.map((res) => res.group?.ifgroupentry?.[props.uuid])),\n matches,\n resourceType: 'Opnsense.Firewall.Group',\n};\n\n/** The pure declaration renderer — see alias.ts's `propsFromLive` for the full contract note. */\nexport const propsFromLive = attributesOf;\n\nexport const handlers = opnsenseHandlers(spec);\n\nexport const OpnsenseFirewallGroupProvider = () =>\n Provider.effect(\n OpnsenseFirewallGroup,\n Effect.succeed(OpnsenseFirewallGroup.Provider.of(handlers)),\n );\n\n/** `firewallGroup('name', { uuid, ifname, members })` — `adopt(true)` piped by default (H5). */\nexport const firewallGroup = (id: string, props: GroupProps) =>\n OpnsenseFirewallGroup(id, props).pipe(adopt(true));\n",
|
|
14
|
+
"/**\n * `Opnsense.Firewall.Group`'s wire shape: how a live `GroupItem` (an interface group) becomes\n * attributes, and the comparison that decides whether a declaration already matches it. Mirrors\n * alias-form.ts.\n */\nimport type { GroupAttributes, GroupProps } from './group.ts';\nimport type * as group from '@distilled.cloud/opnsense/firewall_group';\nimport { bool01, csvSetOf, numField, selectedOf } from './wire.ts';\n\n/**\n * ★ Attributes and the declaration renderer are one function — see alias-form.ts's header note.\n *\n * `live` is `ModelIfgroupentryReadItem` (the WHOLE-MODEL `get()`'s read-shaped item), not\n * `GroupItem` — `members` (`.\\InterfaceField`, `Multiple=\"Y\"`) decodes as an option map, not a\n * comma string (OPNSENSE-2; see wire.ts's module doc).\n */\nexport const attributesOf = (\n uuid: string,\n live: group.ModelIfgroupentryReadItem,\n): GroupAttributes => ({\n description: live.descr ?? '',\n ifname: live.ifname,\n members: selectedOf(live.members),\n nogroup: bool01(live.nogroup, false),\n sequence: numField(live.sequence, 0),\n uuid,\n});\n\nexport const matches = (attributes: GroupAttributes, props: GroupProps): boolean =>\n attributes.ifname === props.ifname &&\n attributes.nogroup === (props.nogroup ?? false) &&\n attributes.sequence === (props.sequence ?? 0) &&\n attributes.description === (props.description ?? '') &&\n attributes.members.join(',') === csvSetOf(props.members).join(',');\n",
|
|
15
15
|
"/**\n * All three OPNsense firewall providers as one layer for a stack.\n *\n * Layer.mergeAll(opnsenseProviders(), …the stack's other providers)\n * .pipe(Layer.provide(FetchHttpClient.layer))\n *\n * S16: a subpath's `providers()` plays the role upstream's own `Providers.ts` does — one minimal\n * insertion per family, requirements that never contain `unknown`. `FetchHttpClient.layer` is\n * the stack's own responsibility to provide, mirroring `../discord/providers.ts`.\n */\nimport * as Layer from 'effect/Layer';\nimport { OpnsenseFirewallAliasProvider } from './alias.ts';\nimport { OpnsenseFirewallCategoryProvider } from './category.ts';\nimport { OpnsenseFirewallGroupProvider } from './group.ts';\n\nexport const providers = () =>\n Layer.mergeAll(\n OpnsenseFirewallAliasProvider(),\n OpnsenseFirewallCategoryProvider(),\n OpnsenseFirewallGroupProvider(),\n );\n"
|
|
16
16
|
],
|
|
17
|
-
"mappings": ";
|
|
18
|
-
"debugId": "
|
|
17
|
+
"mappings": "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",
|
|
18
|
+
"debugId": "681DFCB41A03047B64756E2164756E21",
|
|
19
19
|
"names": []
|
|
20
20
|
}
|
package/dist/opnsense/wire.d.ts
CHANGED
|
@@ -2,20 +2,27 @@
|
|
|
2
2
|
* How OPNsense's MVC model spells a value on the wire, and how to get it back to something
|
|
3
3
|
* comparable — the `../proxmox/values.ts` idea, scoped to this vendor's own spellings.
|
|
4
4
|
*
|
|
5
|
-
* ★ OPNSENSE HAS EXACTLY TWO WIRE SHAPES THIS FAMILY NEEDS
|
|
6
|
-
*
|
|
7
|
-
* string
|
|
8
|
-
* `ModelRelationField` is a SINGLE comma-joined string, not a JSON array
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* ★ OPNSENSE HAS EXACTLY TWO WIRE SHAPES THIS FAMILY NEEDS ON WRITE (props stay plain values;
|
|
6
|
+
* this family never writes, but `matches` compares against them). Every `BooleanField` is the
|
|
7
|
+
* string `"1"` or `"0"` (never a JSON boolean). Every multi-select `OptionField`/
|
|
8
|
+
* `ModelRelationField`'s WRITE-side spelling is a SINGLE comma-joined string, not a JSON array.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ READ IS A THIRD SHAPE (OPNSENSE-2, distilled `homeflare/opnsense`'s `read-shape.ts`): the
|
|
11
|
+
* SAME list-shaped fields decode on GET as an option map `{key: {value, selected}}`, not a
|
|
12
|
+
* string — `AliasItem`/`GroupItem` (this family's OLD, pre-fix type references) were the
|
|
13
|
+
* WRITE-shaped structs; `fetchLive` actually decodes `ModelAliasReadItem`/
|
|
14
|
+
* `ModelIfgroupentryReadItem` (the WHOLE-MODEL GET's read-shaped item), whose `type`/
|
|
15
|
+
* `interface`/`proto`/`categories`/`members` are option maps. `selectedOf`/`selectedOneOf`
|
|
16
|
+
* below extract the selected key(s) back to the SAME plain-string/string[] shape `matches`
|
|
17
|
+
* already compares, so the rest of this family's logic (and `AliasAttributes`/
|
|
18
|
+
* `GroupAttributes`'s own field types) is unchanged.
|
|
11
19
|
*
|
|
12
20
|
* ⚠️ COMMA-LIST ORDER ROUND-TRIP IS NOT ASSUMED STABLE. OPNsense builds these from a PHP
|
|
13
|
-
* associative array
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* `groups` field.
|
|
21
|
+
* associative array, and nothing in the model XML promises the join order survives a
|
|
22
|
+
* save-then-read unchanged. Sorting and deduplicating both sides before comparing is what makes
|
|
23
|
+
* `matches` (alias-form.ts, group-form.ts) actually settle instead of reporting drift on a
|
|
24
|
+
* re-ordered list that changed nothing — the same reasoning `../proxmox/user-wire.ts`'s
|
|
25
|
+
* `groupSet` documents for PVE's `groups` field.
|
|
19
26
|
*/
|
|
20
27
|
/** OPNsense's own boolean spelling. Absent takes the caller's stated default — OPNsense's own
|
|
21
28
|
* default for a `BooleanField` is form-specific, so there is no house-wide fallback here. */
|
|
@@ -36,4 +43,27 @@ export declare const csvSetOf: (value: readonly string[] | undefined) => readonl
|
|
|
36
43
|
* same way `../proxmox/user-wire.ts`'s `int` is tolerant of PVE's mixed number/string releases.
|
|
37
44
|
*/
|
|
38
45
|
export declare const numField: (value: string | undefined, fallback: number) => number;
|
|
46
|
+
/** One entry of a GET-decoded option map — `{value: <label>, selected: 0|1}`, see this file's
|
|
47
|
+
* module doc. Structural, not imported from the SDK: every generated `*Map` field shares this
|
|
48
|
+
* exact shape (`OptionEntry` in each service module), and staying structural here avoids a
|
|
49
|
+
* per-resource-file type import just for this. */
|
|
50
|
+
export interface OpnsenseOptionEntry {
|
|
51
|
+
readonly value: string;
|
|
52
|
+
readonly selected: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The selected key(s) of a GET-decoded option map, normalised the same way `csvSet` normalises
|
|
56
|
+
* the write-side comma string — sorted, deduplicated, empty-key filtered. Use for a
|
|
57
|
+
* `Multiple="Y"` field (`AliasItem.proto`, `AliasItem.categories`, `GroupItem.members`); for a
|
|
58
|
+
* single-select field use `selectedOneOf`.
|
|
59
|
+
*/
|
|
60
|
+
export declare const selectedOf: (map: Readonly<Record<string, OpnsenseOptionEntry | undefined>> | undefined) => readonly string[];
|
|
61
|
+
/** Single-select convenience over `selectedOf` — the one selected key, or `''` if none (the same
|
|
62
|
+
* "absent" spelling a plain string field uses, e.g. `AliasItem.type`/`GroupItem`'s own
|
|
63
|
+
* `.\InterfaceField` members when not `Multiple="Y"`). */
|
|
64
|
+
export declare const selectedOneOf: (map: Readonly<Record<string, OpnsenseOptionEntry | undefined>> | undefined) => string;
|
|
65
|
+
/** The encode direction, for test fixtures: every listed key marked `selected: 1`, mirroring a
|
|
66
|
+
* real OPNsense GET response (`{[key]: {value, selected: 1}}`) — so a fixture exercises the exact
|
|
67
|
+
* decode path `selectedOf`/`selectedOneOf` handle, not the pre-fix plain-string shape. */
|
|
68
|
+
export declare const optionMap: (...keys: readonly string[]) => Record<string, OpnsenseOptionEntry>;
|
|
39
69
|
//# sourceMappingURL=wire.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../../src/opnsense/wire.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../../src/opnsense/wire.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;6FAC6F;AAC7F,eAAO,MAAM,MAAM,UAAW,MAAM,GAAG,SAAS,YAAY,OAAO,KAAG,OACtB,CAAC;AAEjD;4FAC4F;AAC5F,eAAO,MAAM,QAAQ,UAAW,OAAO,KAAG,MAA6B,CAAC;AAExE;;;GAGG;AACH,eAAO,MAAM,MAAM,UAAW,MAAM,GAAG,SAAS,KAAG,SAAS,MAAM,EAOjE,CAAC;AAEF;kGACkG;AAClG,eAAO,MAAM,QAAQ,UAAW,SAAS,MAAM,EAAE,GAAG,SAAS,KAAG,SAAS,MAAM,EACpB,CAAC;AAE5D;;;GAGG;AACH,eAAO,MAAM,QAAQ,UAAW,MAAM,GAAG,SAAS,YAAY,MAAM,KAAG,MAItE,CAAC;AAEF;;;kDAGkD;AAClD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,QAChB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,KACzE,SAAS,MAAM,EAMjB,CAAC;AAEF;;0DAE0D;AAC1D,eAAO,MAAM,aAAa,QACnB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,KACzE,MAAkC,CAAC;AAEtC;;0FAE0F;AAC1F,eAAO,MAAM,SAAS,YAAa,SAAS,MAAM,EAAE,KAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CACd,CAAC"}
|
package/dist/proxmox/index.js
CHANGED
|
@@ -6045,13 +6045,13 @@ class QemuRefusedError extends Error {
|
|
|
6045
6045
|
var NEW_DISK2 = /^([^:\s]+):(\d+(?:\.\d+)?)$/;
|
|
6046
6046
|
var NEW_CLOUDINIT = /^([^:\s]+):cloudinit$/;
|
|
6047
6047
|
var parseDisk = (text3) => {
|
|
6048
|
-
const
|
|
6049
|
-
if (
|
|
6048
|
+
const bare = NEW_DISK2.exec(text3) ?? NEW_CLOUDINIT.exec(text3);
|
|
6049
|
+
if (bare !== null) {
|
|
6050
6050
|
return {
|
|
6051
6051
|
kind: "fresh",
|
|
6052
6052
|
options: new Map,
|
|
6053
6053
|
size: undefined,
|
|
6054
|
-
source:
|
|
6054
|
+
source: bare[1] ?? "",
|
|
6055
6055
|
volname: ""
|
|
6056
6056
|
};
|
|
6057
6057
|
}
|
|
@@ -6065,6 +6065,10 @@ var parseDisk = (text3) => {
|
|
|
6065
6065
|
}));
|
|
6066
6066
|
const size = options.get("size");
|
|
6067
6067
|
options.delete("size");
|
|
6068
|
+
if (volname === "0" && options.has("import-from")) {
|
|
6069
|
+
options.delete("import-from");
|
|
6070
|
+
return { kind: "fresh", options, size: undefined, source, volname: "" };
|
|
6071
|
+
}
|
|
6068
6072
|
return { kind: "volume", options, size, source, volname };
|
|
6069
6073
|
};
|
|
6070
6074
|
var printOptions = (source, volname, options, size) => [
|
|
@@ -6080,8 +6084,13 @@ var judgeDisk = (key, declared2, live) => {
|
|
|
6080
6084
|
refuse: `${key}: the volume is on ${have.source}, declared on ${want.source}. Moving a disk ` + "between storages is not an update this resource makes -- move it by hand, then declare it."
|
|
6081
6085
|
};
|
|
6082
6086
|
}
|
|
6083
|
-
if (want.kind === "fresh")
|
|
6084
|
-
|
|
6087
|
+
if (want.kind === "fresh") {
|
|
6088
|
+
if (want.options.size === 0)
|
|
6089
|
+
return {};
|
|
6090
|
+
if ([...want.options].every(([k, v]) => have.options.get(k) === v))
|
|
6091
|
+
return {};
|
|
6092
|
+
return { put: printOptions(have.source, have.volname, want.options, have.size) };
|
|
6093
|
+
}
|
|
6085
6094
|
if (want.volname !== have.volname) {
|
|
6086
6095
|
return {
|
|
6087
6096
|
refuse: `${key}: declared volume ${want.volname} is not the live ${have.volname}. Writing it ` + "would detach the live volume to unusedN; attach or swap volumes by hand."
|
|
@@ -6895,33 +6904,9 @@ import * as Provider35 from "alchemy/Provider";
|
|
|
6895
6904
|
import * as storage2 from "@distilled.cloud/proxmox/storage";
|
|
6896
6905
|
import * as Effect97 from "effect/Effect";
|
|
6897
6906
|
|
|
6898
|
-
// src/proxmox/storage-
|
|
6899
|
-
var
|
|
6900
|
-
var
|
|
6901
|
-
var field6 = (name, value) => value === undefined ? {} : { [name]: value };
|
|
6902
|
-
var locatorForm = (locator) => ({ ...locator });
|
|
6903
|
-
var underscored = (form2) => Object.fromEntries(Object.entries(form2).map(([key, value]) => [key.replaceAll("-", "_"), value]));
|
|
6904
|
-
var mutable2 = (props) => ({
|
|
6905
|
-
...field6("comment", props.comment),
|
|
6906
|
-
...field6("content", props.content),
|
|
6907
|
-
...field6("disable", flag(props.disable)),
|
|
6908
|
-
...field6("nodes", props.nodes),
|
|
6909
|
-
...field6("preallocation", props.preallocation),
|
|
6910
|
-
...field6("prune-backups", props["prune-backups"]),
|
|
6911
|
-
...field6("shared", flag(props.shared))
|
|
6912
|
-
});
|
|
6913
|
-
var createForm11 = (props) => ({
|
|
6914
|
-
...locatorForm(props.locator),
|
|
6915
|
-
...mutable2(props),
|
|
6916
|
-
storage: props.storage,
|
|
6917
|
-
type: props.type
|
|
6918
|
-
});
|
|
6919
|
-
var updateForm9 = (props) => ({
|
|
6920
|
-
...mutable2(props),
|
|
6921
|
-
storage: props.storage
|
|
6922
|
-
});
|
|
6923
|
-
var toDistilledCreate4 = (props) => underscored(createForm11(props));
|
|
6924
|
-
var toDistilledUpdate3 = (props) => underscored(updateForm9(props));
|
|
6907
|
+
// src/proxmox/storage-plugin-options.ts
|
|
6908
|
+
var SHARED_ACCEPTED_TYPES = new Set(["dir", "lvm", "btrfs", "esxi"]);
|
|
6909
|
+
var sharedAccepted = (type) => SHARED_ACCEPTED_TYPES.has(type);
|
|
6925
6910
|
|
|
6926
6911
|
// src/proxmox/storage-wire.ts
|
|
6927
6912
|
import * as storage from "@distilled.cloud/proxmox/storage";
|
|
@@ -6947,7 +6932,44 @@ var attributesOf10 = (live, props) => {
|
|
|
6947
6932
|
var readStorageOrFail = (props) => readOrUnreadable(runPve(props.target, "provision", false, storage.getStorage({ storage: props.storage }))).pipe(Effect96.map((live) => live === UNREADABLE ? UNREADABLE : attributesOf10(live, props)));
|
|
6948
6933
|
var readStorage = (props) => readStorageOrFail(props).pipe(Effect96.catchTag("StorageNotFound", () => Effect96.succeed(undefined)));
|
|
6949
6934
|
var dropUnreadable7 = (live) => live === UNREADABLE ? undefined : live;
|
|
6950
|
-
var
|
|
6935
|
+
var changedProps = (live, props) => ({
|
|
6936
|
+
...sameList2(props.content, live.content) ? {} : { content: props.content },
|
|
6937
|
+
...sameList2(props.nodes, live.nodes) ? {} : { nodes: props.nodes },
|
|
6938
|
+
...sameList2(props["prune-backups"], live["prune-backups"]) ? {} : { "prune-backups": props["prune-backups"] },
|
|
6939
|
+
...same7(props.disable, live.disable) ? {} : { disable: props.disable },
|
|
6940
|
+
...sharedAccepted(props.type) && !same7(props.shared, live.shared) ? { shared: props.shared } : {},
|
|
6941
|
+
...same7(props.preallocation, live.preallocation) ? {} : { preallocation: props.preallocation },
|
|
6942
|
+
...same7(props.comment, live.comment) ? {} : { comment: props.comment }
|
|
6943
|
+
});
|
|
6944
|
+
var matches10 = (attributes2, props) => !sharedAccepted(props.type) && !same7(props.shared, attributes2.shared) ? Effect96.die(new Error(`storage/${props.storage}: declared shared: ${String(props.shared)}, but PVE reports ` + `${String(attributes2.shared)} and '${props.type}' storages never accept a 'shared' ` + "write (storage-plugin-options.ts) -- remove the declared shared or set it to match " + "what PVE already reports; this value can never be applied.")) : Effect96.succeed(Object.keys(changedProps(attributes2, props)).length === 0);
|
|
6945
|
+
|
|
6946
|
+
// src/proxmox/storage-form.ts
|
|
6947
|
+
var STORAGE_CREATE = "pve:POST /storage";
|
|
6948
|
+
var STORAGE_UPDATE = "pve:PUT /storage/{storage}";
|
|
6949
|
+
var field6 = (name, value) => value === undefined ? {} : { [name]: value };
|
|
6950
|
+
var locatorForm = (locator) => ({ ...locator });
|
|
6951
|
+
var underscored = (form2) => Object.fromEntries(Object.entries(form2).map(([key, value]) => [key.replaceAll("-", "_"), value]));
|
|
6952
|
+
var mutable2 = (type, props) => ({
|
|
6953
|
+
...field6("comment", props.comment),
|
|
6954
|
+
...field6("content", props.content),
|
|
6955
|
+
...field6("disable", flag(props.disable)),
|
|
6956
|
+
...field6("nodes", props.nodes),
|
|
6957
|
+
...field6("preallocation", props.preallocation),
|
|
6958
|
+
...field6("prune-backups", props["prune-backups"]),
|
|
6959
|
+
...sharedAccepted(type) ? field6("shared", flag(props.shared)) : {}
|
|
6960
|
+
});
|
|
6961
|
+
var createForm11 = (props) => ({
|
|
6962
|
+
...locatorForm(props.locator),
|
|
6963
|
+
...mutable2(props.type, props),
|
|
6964
|
+
storage: props.storage,
|
|
6965
|
+
type: props.type
|
|
6966
|
+
});
|
|
6967
|
+
var updateForm9 = (props, live) => ({
|
|
6968
|
+
...mutable2(props.type, live === undefined ? props : changedProps(live, props)),
|
|
6969
|
+
storage: props.storage
|
|
6970
|
+
});
|
|
6971
|
+
var toDistilledCreate4 = (props) => underscored(createForm11(props));
|
|
6972
|
+
var toDistilledUpdate3 = (form2) => underscored(form2);
|
|
6951
6973
|
|
|
6952
6974
|
// src/proxmox/storage.ts
|
|
6953
6975
|
var ProxmoxStorage = Resource35("Proxmox.Storage", {
|
|
@@ -6962,7 +6984,7 @@ var ProxmoxStorageProvider = () => Provider35.effect(ProxmoxStorage, Effect97.su
|
|
|
6962
6984
|
if (!isResolved30(news))
|
|
6963
6985
|
return;
|
|
6964
6986
|
yield* guardWrite2(STORAGE_CREATE, createForm11(news), output === undefined);
|
|
6965
|
-
yield* guardWrite2(STORAGE_UPDATE, updateForm9(news), false);
|
|
6987
|
+
yield* guardWrite2(STORAGE_UPDATE, updateForm9(news, undefined), false);
|
|
6966
6988
|
if (output === undefined)
|
|
6967
6989
|
return;
|
|
6968
6990
|
const live = yield* readStorageOrFail(news);
|
|
@@ -6974,16 +6996,17 @@ var ProxmoxStorageProvider = () => Provider35.effect(ProxmoxStorage, Effect97.su
|
|
|
6974
6996
|
yield* guardWrite2(STORAGE_CREATE, createForm11(news), true);
|
|
6975
6997
|
return { action: "update" };
|
|
6976
6998
|
}
|
|
6977
|
-
return matches10(live, news) ? { action: "noop" } : { action: "update" };
|
|
6999
|
+
return (yield* matches10(live, news)) ? { action: "noop" } : { action: "update" };
|
|
6978
7000
|
}),
|
|
6979
7001
|
reconcile: Effect97.fn(function* ({ news }) {
|
|
6980
7002
|
const before = dropUnreadable7(yield* readStorage(news));
|
|
6981
7003
|
yield* guardWrite2(STORAGE_CREATE, createForm11(news), before === undefined);
|
|
6982
|
-
|
|
7004
|
+
const update2 = updateForm9(news, before);
|
|
7005
|
+
yield* guardWrite2(STORAGE_UPDATE, update2, false);
|
|
6983
7006
|
if (before === undefined) {
|
|
6984
7007
|
yield* runPve(news.target, "provision", true, storage2.createStorage(toDistilledCreate4(news)));
|
|
6985
|
-
} else if (!matches10(before, news)) {
|
|
6986
|
-
yield* runPve(news.target, "provision", true, storage2.putStorage(toDistilledUpdate3(
|
|
7008
|
+
} else if (!(yield* matches10(before, news))) {
|
|
7009
|
+
yield* runPve(news.target, "provision", true, storage2.putStorage(toDistilledUpdate3(update2)));
|
|
6987
7010
|
}
|
|
6988
7011
|
const after = dropUnreadable7(yield* readStorage(news));
|
|
6989
7012
|
if (after === undefined) {
|
|
@@ -7184,5 +7207,5 @@ export {
|
|
|
7184
7207
|
violations
|
|
7185
7208
|
};
|
|
7186
7209
|
|
|
7187
|
-
//# debugId=
|
|
7210
|
+
//# debugId=23CD7F01601D5A3A64756E2164756E21
|
|
7188
7211
|
//# sourceMappingURL=index.js.map
|