@homeflare/alchemy 0.50.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/linux/container-env-quote.d.ts +39 -0
  2. package/dist/linux/container-env-quote.d.ts.map +1 -0
  3. package/dist/linux/container-form.d.ts +14 -13
  4. package/dist/linux/container-form.d.ts.map +1 -1
  5. package/dist/linux/container-paths.d.ts +17 -0
  6. package/dist/linux/container-paths.d.ts.map +1 -0
  7. package/dist/linux/index.js +41 -8
  8. package/dist/linux/index.js.map +6 -4
  9. package/dist/unifi/acl-rule-ordering.d.ts +6 -1
  10. package/dist/unifi/acl-rule-ordering.d.ts.map +1 -1
  11. package/dist/unifi/acl-rule.d.ts +6 -1
  12. package/dist/unifi/acl-rule.d.ts.map +1 -1
  13. package/dist/unifi/dns-policy.d.ts +6 -1
  14. package/dist/unifi/dns-policy.d.ts.map +1 -1
  15. package/dist/unifi/fake-unifi.d.ts +4 -0
  16. package/dist/unifi/fake-unifi.d.ts.map +1 -1
  17. package/dist/unifi/firewall-policy-ordering.d.ts +6 -1
  18. package/dist/unifi/firewall-policy-ordering.d.ts.map +1 -1
  19. package/dist/unifi/firewall-policy.d.ts +6 -1
  20. package/dist/unifi/firewall-policy.d.ts.map +1 -1
  21. package/dist/unifi/firewall-zone.d.ts +6 -1
  22. package/dist/unifi/firewall-zone.d.ts.map +1 -1
  23. package/dist/unifi/index.d.ts +5 -3
  24. package/dist/unifi/index.d.ts.map +1 -1
  25. package/dist/unifi/index.js +339 -86
  26. package/dist/unifi/index.js.map +10 -6
  27. package/dist/unifi/network-scope.d.ts +31 -0
  28. package/dist/unifi/network-scope.d.ts.map +1 -0
  29. package/dist/unifi/network-update.d.ts +44 -0
  30. package/dist/unifi/network-update.d.ts.map +1 -0
  31. package/dist/unifi/network.d.ts +34 -13
  32. package/dist/unifi/network.d.ts.map +1 -1
  33. package/dist/unifi/policy.d.ts +89 -1
  34. package/dist/unifi/policy.d.ts.map +1 -1
  35. package/dist/unifi/resource.d.ts +17 -43
  36. package/dist/unifi/resource.d.ts.map +1 -1
  37. package/dist/unifi/traffic-matching-list.d.ts +6 -1
  38. package/dist/unifi/traffic-matching-list.d.ts.map +1 -1
  39. package/dist/unifi/update-reconcile.d.ts +31 -0
  40. package/dist/unifi/update-reconcile.d.ts.map +1 -0
  41. package/dist/unifi/wifi-broadcast.d.ts +6 -1
  42. package/dist/unifi/wifi-broadcast.d.ts.map +1 -1
  43. package/dist/unifi/wire-guard.d.ts +33 -0
  44. package/dist/unifi/wire-guard.d.ts.map +1 -0
  45. package/docs/unifi-network-write.md +90 -0
  46. package/docs/unifi.md +40 -39
  47. package/package.json +1 -1
  48. package/src/linux/container-env-chain.test.ts +135 -0
  49. package/src/linux/container-env-live.test.ts +115 -0
  50. package/src/linux/container-env-oracle.ts +109 -0
  51. package/src/linux/container-env-quadlet-oracle.ts +174 -0
  52. package/src/linux/container-env-quote.test.ts +131 -0
  53. package/src/linux/container-env-quote.ts +126 -0
  54. package/src/linux/container-fixture.test.ts +3 -1
  55. package/src/linux/container-form.ts +32 -28
  56. package/src/linux/container-paths.ts +21 -0
  57. package/src/unifi/fake-unifi.ts +15 -2
  58. package/src/unifi/handler-allow-news.test.ts +80 -0
  59. package/src/unifi/index.ts +21 -5
  60. package/src/unifi/network-scope.test.ts +224 -0
  61. package/src/unifi/network-scope.ts +75 -0
  62. package/src/unifi/network-update.test.ts +250 -0
  63. package/src/unifi/network-update.ts +95 -0
  64. package/src/unifi/network.test.ts +27 -1
  65. package/src/unifi/network.ts +58 -13
  66. package/src/unifi/policy.ts +128 -10
  67. package/src/unifi/resource.test.ts +15 -3
  68. package/src/unifi/resource.ts +64 -105
  69. package/src/unifi/update-reconcile.ts +126 -0
  70. package/src/unifi/wire-guard-anchor.test.ts +198 -0
  71. package/src/unifi/wire-guard-round2.test.ts +120 -0
  72. package/src/unifi/wire-guard.test.ts +218 -0
  73. package/src/unifi/wire-guard.ts +192 -0
  74. package/src/unifi/write-op-reference.test.ts +41 -4
  75. package/src/unifi/get-only-guard.test.ts +0 -165
@@ -0,0 +1,39 @@
1
+ /** Same shape as container-form.ts's `UnitLine` — structural, so this module imports nothing back. */
2
+ export type EnvironmentLine = readonly [key: string, value: string];
3
+ declare const QuadletEnvironmentValueError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
4
+ readonly _tag: "QuadletEnvironmentValueError";
5
+ } & Readonly<A>;
6
+ /**
7
+ * ⛔ CARRIES THE KEY AND THE RULE, NEVER THE VALUE — an error is logged, retried and pasted into a
8
+ * PR, and a newline-containing value may be arbitrarily large or semi-secret; the key name (which
9
+ * `secretLikeEnvironment` already names in its own messages) is enough to locate the prop.
10
+ */
11
+ export declare class QuadletEnvironmentValueError extends QuadletEnvironmentValueError_base<{
12
+ readonly key: string;
13
+ }> {
14
+ get message(): string;
15
+ }
16
+ /**
17
+ * `Environment="KEY=value"` — the whole assignment double-quoted, with `\`, `"`, `%` and `$`
18
+ * escaped (in that order, over the joined `KEY=value`), so the wrapping quotes this function adds
19
+ * can never be confused with quotes the value contained, every `%` reaches systemd's specifier
20
+ * expansion as the documented `%%` literal instead of an unknown `%x` that invalidates the unit,
21
+ * and every `$` reaches it as `$$` — the documented literal-dollar escape (systemd.service(5)
22
+ * §Command lines) — instead of a `${FOO}` systemd would erase and replace with a variable of ITS
23
+ * choosing, or a `$$` it would fold and halve.
24
+ */
25
+ export declare const quoteEnvironmentValue: (key: string, value: string) => string;
26
+ /**
27
+ * The sorted, quoted `Environment=` lines of a `[Container]` section — spliced by container-form.ts
28
+ * between `EnvironmentFile=` and `Volume=`, exactly where the unquoted renderer emitted them, so
29
+ * the render ORDER does not change (only the bytes of the Environment lines themselves do).
30
+ */
31
+ export declare const environmentLines: (environment: Readonly<Record<string, string>>) => readonly EnvironmentLine[];
32
+ /**
33
+ * The newline refusal as problem STRINGS (containerProblems' currency), so one plan still shows the
34
+ * whole list instead of aborting on the first newline — the typed error above stays the contract
35
+ * for anyone rendering directly.
36
+ */
37
+ export declare const environmentValueProblems: (environment: Readonly<Record<string, string>>) => string[];
38
+ export {};
39
+ //# sourceMappingURL=container-env-quote.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-env-quote.d.ts","sourceRoot":"","sources":["../../src/linux/container-env-quote.ts"],"names":[],"mappings":"AA6CA,sGAAsG;AACtG,MAAM,MAAM,eAAe,GAAG,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;;;;AAQpE;;;;GAIG;AACH,qBAAa,4BAA6B,SAAQ,kCAAiD;IACjG,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB,CAAC;IACA,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,QAAS,MAAM,SAAS,MAAM,KAAG,MAYlE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,gBACd,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAC5C,SAAS,eAAe,EAMoE,CAAC;AAEhG;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,gBACtB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAC5C,MAAM,EAaR,CAAC"}
@@ -16,14 +16,25 @@
16
16
  * `containerProblems` below. A container that needs a secret takes `EnvironmentFile=` pointing at
17
17
  * a host path an out-of-band secret renderer (openbao-agent) maintains; this family never reads
18
18
  * or renders that file's contents.
19
+ * ⛔ `Environment=` VALUES ARE ALWAYS QUOTED, VIA container-env-quote.ts — measured 2026-10-11:
20
+ * unquoted, a spaced value splits at its first space, a quoted fragment re-parses into a bogus
21
+ * variable, and an unknown C escape drops the line's assignments silently (ice2/grafana-oidc.md;
22
+ * the live podman 5.4.2 generator reproduced all three). Quoting costs the PARSE nothing — the
23
+ * generator reads a quoted line as exactly one assignment — and it also stops systemd expanding
24
+ * `%` specifiers in values. It is NOT free at deploy: every Environment line's BYTES change, so
25
+ * the first deploy after this bump rewrites the file and RESTARTS every container declaring
26
+ * `environment` (all 13 on ct100; only grafana's values change meaning) — plan it as one window.
27
+ * Newlines are refused, never encoded (same module).
19
28
  * ⛔ `[Install]` SUPPORTS ONLY `Alias=`/`WantedBy=`/`RequiredBy=` FOR A `.container` FILE (same doc,
20
29
  * "Currently, only the Alias, WantedBy and RequiredBy keys are supported") — NOT `Also=`, which
21
30
  * plain `systemd.unit(5)` allows and unit-text.ts's `INSTALL_KEYS` therefore lists. See
22
31
  * container-generator.ts's header for why there is no `enabled` prop at all.
23
32
  */
24
33
  import { configDigest, digestOf } from './unit-form.ts';
34
+ import { QUADLET_DEFAULT_DIRECTORY, QUADLET_SEARCH_DIRECTORIES } from './container-paths.ts';
25
35
  /** ★ Reused, not redefined: same digest and `restartOn`-join contract as Systemd.Unit. */
26
36
  export { configDigest, digestOf };
37
+ export { QUADLET_DEFAULT_DIRECTORY, QUADLET_SEARCH_DIRECTORIES };
27
38
  export type UnitLine = readonly [key: string, value: string];
28
39
  /**
29
40
  * The `[Container]` keys this family supports — a deliberate SUBSET of the ~80 keys
@@ -59,7 +70,8 @@ export type ContainerSection = {
59
70
  export type ContainerProps = {
60
71
  /** The Quadlet file's basename, WITHOUT `.container`. Written as `<name>.container`. */
61
72
  name: string;
62
- /** @default QUADLET_DEFAULT_DIRECTORY. Must be one of QUADLET_SEARCH_DIRECTORIES (below). */
73
+ /** @default QUADLET_DEFAULT_DIRECTORY. Must be one of QUADLET_SEARCH_DIRECTORIES
74
+ * (container-paths.ts, where the search path now lives). */
63
75
  directory?: string;
64
76
  /** `[Unit]` — `Description=`/`Documentation=` typed, everything else verbatim (see file header). */
65
77
  unit?: {
@@ -104,19 +116,8 @@ export type ContainerAttributes = {
104
116
  active: boolean;
105
117
  };
106
118
  /**
107
- * Podman's OWN rootful search path, most specific first — `podman-systemd.unit(5)`, "Podman Unit
108
- * Search Path", Podman 5.4, read 2026-09-24: "Quadlet files for the root user can be placed in the
109
- * following directories ordered in precedence" (`/run` over `/etc` over `/usr/share`).
110
- * ★ CT100's `caddy.container` lives in the middle one — measured live, `cat` over `ssh ct100`,
111
- * 2026-09-24: `/etc/containers/systemd/caddy.container`.
112
- * ⛔ A directory outside this list is invisible to Quadlet: the generator only reads these three, so
113
- * a file elsewhere is a `.container` file with no `.service` ever generated from it — silently,
114
- * unlike `Systemd.Unit`'s directory check, which at least writes a unit systemd could load if only
115
- * it were enabled. See `containerProblems`.
119
+ * ★ A conservative subset of what Quadlet itself accepts as a unit name: no template `@`, no `/`.
116
120
  */
117
- export declare const QUADLET_SEARCH_DIRECTORIES: readonly ['/run/containers/systemd', '/etc/containers/systemd', '/usr/share/containers/systemd'];
118
- export declare const QUADLET_DEFAULT_DIRECTORY = "/etc/containers/systemd";
119
- /** ★ A conservative subset of what Quadlet itself accepts as a unit name: no template `@`, no `/`. */
120
121
  export declare const NAME: RegExp;
121
122
  export declare const containerPathFor: (props: Pick<ContainerProps, 'name' | 'directory'>) => string;
122
123
  /** The unit Quadlet generates FROM the `.container` file — systemctl only ever sees this name. */
@@ -1 +1 @@
1
- {"version":3,"file":"container-form.d.ts","sourceRoot":"","sources":["../../src/linux/container-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAoB,YAAY,EAAE,QAAQ,EAAc,MAAM,gBAAgB,CAAC;AAGtF,0FAA0F;AAC1F,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;AAElC,MAAM,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AAE7D;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oFAAoF;IACpF,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,iGAAiG;IACjG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,6BAA6B;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,qGAAqG;IACrG,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,yFAAyF;IACzF,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,qGAAqG;IACrG,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,oGAAoG;IACpG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,yGAAyG;IACzG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAC3C,gGAAgG;IAChG,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,wFAAwF;IACxF,IAAI,EAAE,MAAM,CAAC;IACb,6FAA6F;IAC7F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oGAAoG;IACpG,IAAI,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAA;KAAE,CAAC;IACrF,4DAA4D;IAC5D,SAAS,EAAE,gBAAgB,CAAC;IAC5B,8EAA8E;IAC9E,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAA;KAAE,CAAC;IACjF,8FAA8F;IAC9F,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC7B,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC/B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KAC3B,CAAC;IACF;yGACqG;IACrG,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yGAAyG;IACzG,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,mGAAmG;IACnG,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;6FACyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,0BAA0B,YACrC,yBAAyB,EACzB,yBAAyB,EACzB,+BAA+B,CACvB,CAAC;AAEX,eAAO,MAAM,yBAAyB,4BAA4B,CAAC;AAEnE,sGAAsG;AACtG,eAAO,MAAM,IAAI,QAAiC,CAAC;AAEnD,eAAO,MAAM,gBAAgB,UAAW,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,WAAW,CAAC,KAAG,MACV,CAAC;AAE5E,kGAAkG;AAClG,eAAO,MAAM,cAAc,UAAW,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,KAAG,MAC5C,CAAC;AA8C1B,sGAAsG;AACtG,eAAO,MAAM,mBAAmB,UAAW,cAAc,KAAG,MAQ3D,CAAC;AAEF,eAAO,MAAM,eAAe,SAAU,MAAM,WAAW,MAAM,KAAG,KACb,CAAC;AAEpD,gGAAgG;AAChG,eAAO,MAAM,iBAAiB,UAAW,cAAc,KAAG,MAAM,EAkD/D,CAAC"}
1
+ {"version":3,"file":"container-form.d.ts","sourceRoot":"","sources":["../../src/linux/container-form.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Where Quadlet looks for `.container` files — split out of container-form.ts under the file-length
3
+ * cap, with the measured context that justifies every entry kept intact.
4
+ *
5
+ * Podman's OWN rootful search path, most specific first — `podman-systemd.unit(5)`, "Podman Unit
6
+ * Search Path", Podman 5.4, read 2026-09-24: "Quadlet files for the root user can be placed in the
7
+ * following directories ordered in precedence" (`/run` over `/etc` over `/usr/share`).
8
+ * ★ CT100's `caddy.container` lives in the middle one — measured live, `cat` over `ssh ct100`,
9
+ * 2026-09-24: `/etc/containers/systemd/caddy.container`.
10
+ * ⛔ A directory outside this list is invisible to Quadlet: the generator only reads these three, so
11
+ * a file elsewhere is a `.container` file with no `.service` ever generated from it — silently,
12
+ * unlike `Systemd.Unit`'s directory check, which at least writes a unit systemd could load if only
13
+ * it were enabled. See `containerProblems`.
14
+ */
15
+ export declare const QUADLET_SEARCH_DIRECTORIES: readonly ['/run/containers/systemd', '/etc/containers/systemd', '/usr/share/containers/systemd'];
16
+ export declare const QUADLET_DEFAULT_DIRECTORY = "/etc/containers/systemd";
17
+ //# sourceMappingURL=container-paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-paths.d.ts","sourceRoot":"","sources":["../../src/linux/container-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,0BAA0B,YACrC,yBAAyB,EACzB,yBAAyB,EACzB,+BAA+B,CACvB,CAAC;AAEX,eAAO,MAAM,yBAAyB,4BAA4B,CAAC"}
@@ -909,13 +909,46 @@ var secretLikeLines = (lines) => {
909
909
  };
910
910
  var podmanArgsProblems = (args) => args.filter((arg) => ENV_FLAG.test(arg)).map((arg) => `podmanArgs entry ${JSON.stringify(arg)} sets a container environment variable outside ` + "the typed props, which this family never checks for a secret. Use container.environment " + "(checked) or container.environmentFile (for anything secret) instead.");
911
911
 
912
- // src/linux/container-form.ts
912
+ // src/linux/container-env-quote.ts
913
+ import * as Data from "effect/Data";
914
+ var environmentValueProblem = (key) => `environment key ${JSON.stringify(key)} contains a newline; a .container file is line-based, ` + "so quadlet would end the assignment mid-value and parse whatever follows as a new directive. " + "Nothing was written.";
915
+
916
+ class QuadletEnvironmentValueError extends Data.TaggedError("QuadletEnvironmentValueError") {
917
+ get message() {
918
+ return environmentValueProblem(this.key);
919
+ }
920
+ }
921
+ var quoteEnvironmentValue = (key, value) => {
922
+ if (key.includes(`
923
+ `) || value.includes(`
924
+ `))
925
+ throw new QuadletEnvironmentValueError({ key });
926
+ return `"${`${key}=${value}`.replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("%", "%%").replaceAll("$", () => "$$")}"`;
927
+ };
928
+ var environmentLines = (environment) => Object.entries(environment).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, value]) => ["Environment", quoteEnvironmentValue(key, value)]);
929
+ var environmentValueProblems = (environment) => {
930
+ const found = [];
931
+ for (const [key, value] of Object.entries(environment).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
932
+ try {
933
+ quoteEnvironmentValue(key, value);
934
+ } catch (error) {
935
+ if (!(error instanceof QuadletEnvironmentValueError))
936
+ throw error;
937
+ found.push(environmentValueProblem(key));
938
+ }
939
+ }
940
+ return found;
941
+ };
942
+
943
+ // src/linux/container-paths.ts
913
944
  var QUADLET_SEARCH_DIRECTORIES = [
914
945
  "/run/containers/systemd",
915
946
  "/etc/containers/systemd",
916
947
  "/usr/share/containers/systemd"
917
948
  ];
918
949
  var QUADLET_DEFAULT_DIRECTORY = "/etc/containers/systemd";
950
+
951
+ // src/linux/container-form.ts
919
952
  var NAME2 = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
920
953
  var containerPathFor = (props) => `${props.directory ?? QUADLET_DEFAULT_DIRECTORY}/${props.name}.container`;
921
954
  var serviceNameFor = (props) => `${props.name}.service`;
@@ -932,10 +965,7 @@ var containerLines = (c) => [
932
965
  ...c.exec === undefined ? [] : [["Exec", c.exec]],
933
966
  ...c.autoUpdate === undefined ? [] : [["AutoUpdate", c.autoUpdate]],
934
967
  ...(c.environmentFile ?? []).map((file) => ["EnvironmentFile", file]),
935
- ...Object.keys(c.environment ?? {}).sort().map((key) => [
936
- "Environment",
937
- `${key}=${c.environment[key]}`
938
- ]),
968
+ ...environmentLines(c.environment ?? {}),
939
969
  ...(c.volume ?? []).map((volume) => ["Volume", volume]),
940
970
  ...(c.publishPort ?? []).map((port) => ["PublishPort", port]),
941
971
  ...(c.podmanArgs ?? []).map((arg) => ["PodmanArgs", arg])
@@ -978,13 +1008,16 @@ var containerProblems = (props) => {
978
1008
  }
979
1009
  found.push(...secretLikeEnvironment(props.container.environment ?? {}).map((problem) => `container.${problem}`));
980
1010
  found.push(...podmanArgsProblems(props.container.podmanArgs ?? []).map((problem) => `container.${problem}`));
1011
+ const envProblems = environmentValueProblems(props.container.environment ?? {});
1012
+ found.push(...envProblems);
1013
+ const { environment: _env, ...withoutEnvironment } = props.container;
981
1014
  found.push(...secretLikeLines([
982
1015
  ...unitLines(props),
983
- ...containerLines(props.container).filter(([key]) => key !== "Environment"),
1016
+ ...containerLines(withoutEnvironment),
984
1017
  ...serviceLines(props),
985
1018
  ...installLines(props)
986
1019
  ]));
987
- if (renderContainerFile(props).includes("\x00"))
1020
+ if (envProblems.length === 0 && renderContainerFile(props).includes("\x00"))
988
1021
  found.push("the rendered .container file contains NUL");
989
1022
  return found;
990
1023
  };
@@ -3225,5 +3258,5 @@ export {
3225
3258
  sshSudoRunner
3226
3259
  };
3227
3260
 
3228
- //# debugId=FD1877F9498BF32D64756E2164756E21
3261
+ //# debugId=32C72A25441D70A164756E2164756E21
3229
3262
  //# sourceMappingURL=index.js.map