@crewhaus/helm-chart 0.6.0 → 0.7.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/dist/index.d.ts CHANGED
@@ -159,6 +159,23 @@ export type RenderContext = {
159
159
  };
160
160
  };
161
161
  export declare function renderContext(values: ChartValues, releaseName?: string): RenderContext;
162
+ /**
163
+ * The shapes whose compiled daemon actually opens an HTTP socket and answers
164
+ * `/healthz` — and therefore the only ones that can carry a Service, an
165
+ * Ingress and `httpGet` probes.
166
+ *
167
+ * NOT the same as "runs continuously". `crew`, `voice` and `browser` are
168
+ * long-running too, but their bundles contain no `Bun.serve` and no
169
+ * `/healthz` at all (voice is a stdin/JSONL loop, crew consumes a brief on
170
+ * stdin and exits), so an `httpGet` probe against them can never pass at any
171
+ * port. They take the exec probe (`doctor --liveness`) instead, which the
172
+ * deployment template already renders for every non-HTTP shape.
173
+ *
174
+ * Kept in lock-step with the three templates that repeat it inline by
175
+ * `index.test.ts` — the list used to be duplicated in four places with
176
+ * nothing checking they agreed.
177
+ */
178
+ export declare const HTTP_SHAPES: readonly ["channel", "managed"];
162
179
  type Node = {
163
180
  kind: "text";
164
181
  value: string;
@@ -196,6 +213,7 @@ type Node = {
196
213
  */
197
214
  export declare function renderTemplate(text: string, ctx: RenderContext, defines?: Map<string, Node[]>): string;
198
215
  export declare function renderChart(values: ChartValues, releaseName?: string): Record<string, string>;
199
- /** Daemon shapes that get a Service + Ingress rendered. */
200
- export declare function isDaemonShape(target: TargetShape): boolean;
216
+ /** Whether this shape serves HTTP, and so gets a Service, an Ingress and
217
+ * `httpGet` probes. See {@link HTTP_SHAPES} for why it is only two. */
218
+ export declare function servesHttp(target: TargetShape): boolean;
201
219
  export {};
package/dist/index.js CHANGED
@@ -119,6 +119,17 @@ export function validateValues(values) {
119
119
  if (values.service.port <= 0 || values.service.port > 65535) {
120
120
  throw new HelmChartError(`service.port out of range: ${values.service.port}`);
121
121
  }
122
+ // `targetPort` is the one that matters and nothing checked it: it becomes
123
+ // the containerPort, the Service's destination, the port both probes dial
124
+ // AND — since the chart now emits it as PORT — the port the process binds.
125
+ // The floor is 1024 rather than 1 because the pod runs as uid 10001 with
126
+ // every capability dropped, so a privileged port could never be bound and
127
+ // would fail as a crash loop rather than a validation error.
128
+ if (!Number.isInteger(values.service.targetPort) ||
129
+ values.service.targetPort < 1024 ||
130
+ values.service.targetPort > 65535) {
131
+ throw new HelmChartError(`service.targetPort must be an integer 1024..65535 (the pod runs unprivileged as uid 10001); got ${values.service.targetPort}`);
132
+ }
122
133
  if (!values.image.tag) {
123
134
  throw new HelmChartError("image.tag must be non-empty");
124
135
  }
@@ -148,7 +159,23 @@ export function renderContext(values, releaseName = "crewhaus") {
148
159
  Chart: { Name: "crewhaus", AppVersion: "1.0.0" },
149
160
  };
150
161
  }
151
- const DAEMON_SHAPES = ["channel", "managed", "crew", "voice", "browser"];
162
+ /**
163
+ * The shapes whose compiled daemon actually opens an HTTP socket and answers
164
+ * `/healthz` — and therefore the only ones that can carry a Service, an
165
+ * Ingress and `httpGet` probes.
166
+ *
167
+ * NOT the same as "runs continuously". `crew`, `voice` and `browser` are
168
+ * long-running too, but their bundles contain no `Bun.serve` and no
169
+ * `/healthz` at all (voice is a stdin/JSONL loop, crew consumes a brief on
170
+ * stdin and exits), so an `httpGet` probe against them can never pass at any
171
+ * port. They take the exec probe (`doctor --liveness`) instead, which the
172
+ * deployment template already renders for every non-HTTP shape.
173
+ *
174
+ * Kept in lock-step with the three templates that repeat it inline by
175
+ * `index.test.ts` — the list used to be duplicated in four places with
176
+ * nothing checking they agreed.
177
+ */
178
+ export const HTTP_SHAPES = ["channel", "managed"];
152
179
  function tokenise(text) {
153
180
  const out = [];
154
181
  // Strip helm comments first
@@ -506,7 +533,8 @@ export function renderChart(values, releaseName = "crewhaus") {
506
533
  }
507
534
  return result;
508
535
  }
509
- /** Daemon shapes that get a Service + Ingress rendered. */
510
- export function isDaemonShape(target) {
511
- return DAEMON_SHAPES.includes(target);
536
+ /** Whether this shape serves HTTP, and so gets a Service, an Ingress and
537
+ * `httpGet` probes. See {@link HTTP_SHAPES} for why it is only two. */
538
+ export function servesHttp(target) {
539
+ return HTTP_SHAPES.includes(target);
512
540
  }
@@ -33,6 +33,15 @@ spec:
33
33
  env:
34
34
  - name: CREWHAUS_TARGET
35
35
  value: {{ .Values.target | quote }}
36
+ {{- if has .Values.target (list "channel" "managed") }}
37
+ # The daemon reads PORT; the image bakes its own ENV PORT default
38
+ # (3000 for channel, 8080 for managed), which used to win and left
39
+ # the process bound to a port the probes never dialled. Emitting it
40
+ # here makes service.targetPort the single source of truth for the
41
+ # containerPort, the Service destination, both probes AND the bind.
42
+ - name: PORT
43
+ value: {{ .Values.service.targetPort | quote }}
44
+ {{- end }}
36
45
  {{- if .Values.secrets.anthropic }}
37
46
  - name: ANTHROPIC_AUTH_TOKEN
38
47
  valueFrom:
@@ -71,7 +80,7 @@ spec:
71
80
  value: {{ .Values.otel.headers | quote }}
72
81
  {{- end }}
73
82
  {{- end }}
74
- {{- if has .Values.target (list "channel" "managed" "crew" "voice" "browser") }}
83
+ {{- if has .Values.target (list "channel" "managed") }}
75
84
  ports:
76
85
  - name: http
77
86
  containerPort: {{ .Values.service.targetPort }}
@@ -1,4 +1,4 @@
1
- {{- if and .Values.ingress.enabled (has .Values.target (list "channel" "managed" "crew" "voice" "browser")) -}}
1
+ {{- if and .Values.ingress.enabled (has .Values.target (list "channel" "managed")) -}}
2
2
  apiVersion: networking.k8s.io/v1
3
3
  kind: Ingress
4
4
  metadata:
@@ -1,4 +1,4 @@
1
- {{- if has .Values.target (list "channel" "managed" "crew" "voice" "browser") -}}
1
+ {{- if has .Values.target (list "channel" "managed") -}}
2
2
  apiVersion: v1
3
3
  kind: Service
4
4
  metadata:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewhaus/helm-chart",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "Kubernetes Helm chart (templates + values schema + ServiceMonitor + OTel collector sidecar) plus an in-process renderChart() for tests (Section 32)",
6
6
  "main": "dist/index.js",
@@ -15,8 +15,8 @@
15
15
  "test": "bun test src"
16
16
  },
17
17
  "dependencies": {
18
- "@crewhaus/docker-images": "0.6.0",
19
- "@crewhaus/errors": "0.6.0"
18
+ "@crewhaus/docker-images": "0.7.0",
19
+ "@crewhaus/errors": "0.7.0"
20
20
  },
21
21
  "license": "Apache-2.0",
22
22
  "author": {