Commit Graph
20 Commits
Author SHA1 Message Date
jklein b614b53602 ci: remove the temporary repository_owner probe, record the measurement
Validate Helm Charts / Resource quantities (pull_request) Successful in 7s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 2s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test (pull_request) Skipped
github.repository_owner is populated on this Gitea instance: measured as
"jlab-cluster" (12 characters) in run 282 of validate-charts.yml. The
value is now in the comment above the upload step, so the fact survives
without the step that produced it.
2026-09-30 18:51:55 +00:00
jklein a12983fbea ci(publish): compose the registry URL from a base and the repository owner
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Successful in 15s
charts-url in op://GitHub-Actions/jlab-registry-access held the full
registry URL and publish-helm-chart.yml appended /api/charts to it. It
now holds only the base up to /api/packages, and the owner segment comes
from github.repository_owner, so the target is composed as
<base>/<owner>/helm/api/charts.

Nothing in this repository points at the registry any more, not even a
path segment, and the second owner on the same instance needs no second
setting. The 26 thin publish-<chart>.yml callers are unchanged.

Two guards keep the switch from the old shape loud rather than a 404
against a masked URL: charts-url is rejected unless it ends in
/api/packages, naming /helm, /api/charts or an included owner
explicitly; and the derived owner is rejected when empty or when it is
not a usable URL path segment. Everything the upload step already had
is untouched - the per-field :? guards, the whitespace check on the
credentials, the host masking, the built file name, --fail-with-body.

The probe step in validate-charts.yml is temporary and measures whether
this forge populates github.repository_owner at all; it is removed again
once the value has been read off the run.
2026-09-30 18:49:56 +00:00
jklein 61e28a1459 docs: the instructions block is a snapshot from session start
Validate Helm Charts / Detect changed charts (pull_request) Successful in 2s
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test (pull_request) Skipped
The first wording said the project-instructions block "is read from the
spawning session's working directory", which suggests that pulling in
that directory fixes it. It does not. The block is a snapshot taken when
the spawning session started, and nothing re-reads it afterwards.

Measured: a session whose working directory had been fast-forwarded to
the tip immediately before, and verified as up to date, still handed the
next agent a block from before Gitea #47 — while that agent's own
worktree was identical to origin/main (0 0). A current checkout and
stale instructions at the same moment.

Hence the two consequences that actually hold: the directory must be
current before the session starts, and an agent checks a rule against
the file in its own worktree and the diff against origin/main, because
the first cannot be enforced from inside an agent and the second can.
2026-09-27 23:05:06 +00:00
jklein 56bed94064 docs: cut every worktree from a freshly fetched origin/main
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
A worktree branched from the local main ref can be arbitrarily old, and
the staleness is invisible where it costs most: the project-instructions
block a subagent receives at the head of its context is read from the
spawning session's working directory, not from origin/main. An outdated
worktree therefore hands every agent rules that were already corrected.

Measured on 2026-09-27: an agent reported a contradiction in CLAUDE.md
that two PRs had already fixed, and quoted the stale copy as binding.
Nothing broke only because the same agent compared its diff against
origin/main rather than against the local pointer. The same day, in the
shared checkout, main..HEAD showed 105 differing files while the
difference against origin/main was one line.

Also name the wiki's default branch: it is main, not master. Since the
rule to fetch and rebase before every wiki push, the name is required —
an agent tried to rebase onto origin/master and the rebase failed.

Fixes #57
2026-09-27 22:58:39 +00:00
jklein 5c513291a8 docs: a PR that does not finish its issue writes Part of #n, not Fixes #n
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
2026-09-27 22:02:32 +00:00
jklein 6a3b05503e Make the all-charts install-test repeatable, as one shared job
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Resource quantities (pull_request) Successful in 10s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Successful in 21s
The install-test matrix follows the charts a pull request changes, so a
chart nobody has touched since the forge move carries a green history from
a different runner - and that green says nothing about today. Gitea PR #48
showed what the answer is worth: correcting a comment in 21 charts made
every one of them "changed" and turned the run into the first all-charts
install-test after the move, 19 of 20 matrix charts Ready in 12m41s and
exactly one not (rss-bridge, Gitea #51). By accident.

The install-test job moves into the reusable .github/workflows/
install-test.yml and is called twice: by validate-charts.yml with the
charts a pull request changed, and by the new install-test-all-charts.yml
with all of them. Not copied for the second caller - a second copy is a
second chance to test differently, unnoticed, which is the argument that
gives ci/excluded-charts.txt exactly one parser. The parts that would
drift are the ones that were paid for once already: the pinned kubectl
with its skew check, envsubst, the containerised-job detection, the
per-leg cluster name.

Nothing about how a chart is installed changed. The moved job differs from
its predecessor in the matrix source (inputs.charts instead of the detect
job's output), an id on the install step, and a step that records the
result; every other step is byte-identical.

The all-charts run is workflow_dispatch only - a schedule would spend
compute every day on a question that changes rarely - with an optional
subset input, which is what lets a single chart be tested against the
runner as Gitea #51 needs. One throwaway cluster per chart, no reuse:
reuse saves time and changes what the test proves. An unknown or excluded
chart name, and an empty matrix, are loud errors rather than a green run
over nothing.

Every leg writes its own outcome into the job summary next to the four
facts that decide what the outcome means - date, runner label, K3S_IMAGE,
kubectl version - and a final job records the verdict over the matrix. A
green run nobody notes is unknown again next time. The excluded charts
appear with their justification, from the one parser, as in the pull
request pipeline. 20 charts in 12m41s is written down as the expectation,
so a later deviation is visible as one.

Fixes #52
2026-09-27 14:30:39 +00:00
jklein 813c796a7b feat(valkey): one Valkey instance per application (1.0.0+up9.1.2.alpine)
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Successful in 4s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Successful in 11s
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Successful in 1m22s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 7s
Publish Valkey Helm Chart / Publish Valkey Helm Chart (push) Successful in 7s
A new chart for a single Valkey instance per application, replacing the
shared cluster-redis whose separation rests on one ACL file in one
1Password field - a construction in which every change touches six
applications and which cost a month of Paperless standstill.

Shape: StatefulSet with one replica, ClusterIP Service, no cluster mode,
no Ingress and no value to add one.

The password arrives as a finished configuration line ("requirepass
<secret>") in the 1Password field auth-conf, is mounted as a file and
included by the main configuration - never in argv, never in the
environment. The include is rendered last, so nothing in extraConfig can
override it. Because a misspelled or empty field would leave an open
server that looks perfectly healthy, an init container greps the mounted
file for the line and refuses to start the pod without it; it never
prints the file.

maxmemory is derived from the computed memory limit (maxmemoryFactor,
default 0.75) and a configured value above that limit aborts the render:
otherwise the kubelet's OOM kill decides what the eviction policy was
configured to decide. Persistence and eviction are documented as pairs -
queue (PVC + noeviction, the default) and cache (emptyDir +
allkeys-lru) - and the combination "no persistence + noeviction" carries
a warning in values.yaml and in NOTES.txt.

Probes: valkey-cli ping as an exec probe, authenticated through
VALKEYCLI_AUTH read out of the mounted file, with "-e" and an assertion
on the answer PONG. Without -e valkey-cli exits 0 on an error reply
(valkey 9.1.2, src/valkey-cli.c: the config.set_errcode branch in
cliReadReply), which would make the probe unable to fail.

Hardening: uid 999 / gid 1000 - the image's own valkey user - with
fsGroup matching, runAsNonRoot, readOnlyRootFilesystem, all capabilities
dropped and seccompProfile RuntimeDefault in the pod context so the init
container is covered too. /data is the only writable path.

NetworkPolicy on by default (first chart in this repo with one), with a
switch and a list of additional namespaces; values.yaml names that it is
only a boundary where the CNI enforces policies.

Wiring: publish-valkey.yml, the chart-name choice list, ci/valkey
fixtures for the persistent variant, a plain docker-versioning Renovate
block, README chart row.
2026-09-27 11:24:55 +00:00
jklein e0b552d93e Replace the obsolete no-dependencies rationale in every chart helper
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Successful in 31s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Successful in 1m34s
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Successful in 15m49s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 7s
Publish Apache Tika Helm Chart / Publish Apache Tika Helm Chart (push) Successful in 7s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 7s
Publish BentoPDF Helm Chart / Publish BentoPDF Helm Chart (push) Successful in 7s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Calibre Web Automated Helm Chart / Publish Calibre Web Automated Helm Chart (push) Successful in 7s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Cyberchef Helm Chart / Publish Cyberchef Helm Chart (push) Successful in 7s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 7s
Publish Freesailarr Helm Chart / Publish Freesailarr Helm Chart (push) Successful in 7s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 7s
Publish Gotenberg Helm Chart / Publish Gotenberg Helm Chart (push) Successful in 7s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 7s
Publish Homeassistant Helm Chart / Publish Homeassistant Helm Chart (push) Successful in 8s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 7s
Publish Matrix Synapse Helm Chart / Publish Matrix Synapse Helm Chart (push) Successful in 7s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 7s
Publish Minecraft-Router Helm Chart / Publish Minecraft-Router Helm Chart (push) Successful in 7s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 7s
Publish Minecraft-Server Helm Chart / Publish Minecraft-Server Helm Chart (push) Successful in 7s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Outline Helm Chart / Publish Outline Helm Chart (push) Successful in 6s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Patchmon Helm Chart / Publish Patchmon Helm Chart (push) Successful in 6s
Publish Paperless-NGX Helm Chart / Publish Paperless-NGX Helm Chart (push) Successful in 6s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Rallly Helm Chart / Publish Rallly Helm Chart (push) Successful in 6s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Samba Helm Chart / Publish Samba Helm Chart (push) Successful in 6s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 7s
Publish Satisfactory Helm Chart / Publish Satisfactory Helm Chart (push) Successful in 7s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish TeamSpeak Server Helm Chart / Publish TeamSpeak Server Helm Chart (push) Successful in 7s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Urlaubsverwaltung Helm Chart / Publish Urlaubsverwaltung Helm Chart (push) Successful in 7s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish VoucherVault Helm Chart / Publish VoucherVault Helm Chart (push) Successful in 6s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Zipline Helm Chart / Publish Zipline Helm Chart (push) Successful in 6s
The doc comments on the resources and defaultedDict helpers justified the
copy-per-chart with "charts in this repo deliberately have no dependencies".
That rule fell with #214: a dependency is admissible now, just expensive, and
it sits in the README as guideline 14, not 13. The justification still holds,
but its reason is a different one - sharing the helper would mean a library
chart, a second artefact to publish, version and pull for something that
changes about once a year. searxng got that wording in PR #38; this carries it
to 21 further charts, 40 occurrences in all.

rss-bridge is deliberately left out. Its install-test fails on the Gitea
runner for a reason that predates this branch, and touching the chart would
keep it in the install-test matrix and the check red. Its copy of the sentence
is carried by #51, which has to open the chart anyway.

Comments only: every touched chart renders byte-identical against its
ci/<chart>/test-values.yaml. Each published chart takes a patch bump,
appVersion and +up suffix untouched; template-chart is never published and
takes none, but is corrected because every new chart inherits from it.

Two repo files carried the same claim at no version cost:
validate-charts.yml explained the all-charts quantity render test with it, and
change-app-version.yml still counted nine images for freesailarr - MediathekArr
made it ten.
2026-09-27 10:27:57 +00:00
jklein 733fea5d9e docs: point the two workflow cross-references at the right steps
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 7s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Skipped
Moving the wiki step from 7 to 5 shifted the two items behind it, and the
references that name them by number stayed where they were. The wiki
section still called itself step 7, and the auto-merge bullet cited rule 6
for "the user merges", which is rule 7 — rule 6 is the agent's lifetime.

A number pointing at the wrong step is the kind of reference that passes
for a reason on the next reading, so both are corrected here rather than
in an issue of their own.
2026-09-26 21:46:03 +00:00
jklein 1d3bacfb12 docs: name the remote that exists, and a push rule for the wiki
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
The remote bullet described two remotes, github and JLab-Git, and denied
that origin exists. The checkout has exactly one, origin, pointing at the
Gitea instance, and so does every worktree cut from it; two agents lost
time on the difference. The point the bullet made survives — Gitea is the
leading copy, the mirror is fed from there, and where a second remote does
exist the advice to name it still holds.

The wiki has no PR flow, so parallel edits meet only at push time. During
Gitea #16 and #18 two agents edited the same two pages minutes apart and
the rebase happened to be clean; a force push would have deleted the other
edit with nothing to report it. Fetch and rebase before every push, never
force-push.

The chart pages no longer quote versions, so the sentence that explained
them as snapshots now says they carry no numbers at all. That change is
already in the wiki.
2026-09-26 21:43:14 +00:00
jklein 8074ed14a5 Guard the four unguarded OnePasswordItem references with required()
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Successful in 3m11s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Calibre Web Automated Helm Chart / Publish Calibre Web Automated Helm Chart (push) Successful in 7s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Minecraft-Server Helm Chart / Publish Minecraft-Server Helm Chart (push) Successful in 6s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish SearXNG Helm Chart / Publish SearXNG Helm Chart (push) Successful in 6s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Urlaubsverwaltung Helm Chart / Publish Urlaubsverwaltung Helm Chart (push) Successful in 7s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Successful in 6s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Successful in 19s
calibre-web-automated 7.0.0, minecraft-server 6.0.0 and urlaubsverwaltung
10.0.0 each majors: an installation that renders today with an empty itemPath
stops rendering. searxng 1.0.6 is documentation only - its valkey reference is
correctly optional and stays gated by {{- if }}.

Every message names the values path. values.yaml now says per key why it is
required, reusing the wording the charts' NOTES.txt already carry. The
hand-rolled "cwaSecretRef IS UNSET" block in calibre-web-automated's NOTES.txt
is gone: after the guard the render aborts first and that block is
unreachable. minecraft-server's note stays true and now points at the guard.

No "required" array in the schemas, and README guideline 12 now says why. The
plan called for one; measuring it showed it could never fire: the key ships as
null, so it is present at validation time, and JSON Schema's required tests
presence, not null-ness. Making it fire means either dropping the null default
or narrowing the type to "string", and both turn helm lint --strict red on the
chart's own defaults. The schema therefore describes the key as required and
required() enforces it. The README bullet on how null reaches the schema was
also wrong in the case it was being applied to - a null chart default is not
coalesced away - and now distinguishes the three cases.

searxng: the claim "charts in this repo have no dependencies (README,
guideline 13)" in the valkey template, in values.yaml and twice in _helpers.tpl
has been false since #214 and cited the wrong guideline; dependencies are
admissible and are guideline 14. The same sentence survives in other charts'
helpers and needs its own round.
2026-09-26 21:15:30 +00:00
jklein d9267b2180 sure: fix the readiness probe, the ephemeral-storage budget and HOME
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Sure Helm Chart / Publish Sure Helm Chart (push) Successful in 6s
Second install-test run, three findings, all measured:

- Readiness on "/" failed with "http: server gave HTTP response to HTTPS
  client" on a pod that was answering correctly. The kubelet's HTTP prober
  follows redirects to the same host, and with RAILS_ASSUME_SSL=true the
  app builds its redirect as https://<pod-ip>:3000/registration/new, which
  Puma does not speak. The probe now runs curl without -L and accepts
  2xx-3xx itself. The same log also proves what the probe is for: against
  an empty database the redirect target was /registration/new, the branch
  User.count decides, so the path really does reach Postgres.
- The pod was evicted within two minutes of every start: "ephemeral local
  storage usage exceeds the total limit of containers 60Mi". emptyDirs and
  the stdout log both count there, and the image rebuilds its Bootsnap
  cache under /rails/tmp on every start because the emptyDir hides the one
  baked into the image. The request is now 256Mi (limit 768Mi) per app
  container - headroom, not a measurement.
- /home/rails is read-only, so Bundler created a throwaway home under
  TMPDIR on every invocation, worker probe included. HOME now points at a
  directory the init container creates.

The CI postgres fixture gets -d sure on its readiness probe, so the server
stops logging a missing database that nothing asked for.
2026-09-26 21:08:55 +00:00
jklein a414af089d sure: give Ruby a temporary directory it accepts
The first install-test run failed with both containers crash-looping
during boot:

  system temporary path is world-writable: /tmp
  /tmp is world-writable: /tmp
  . is not writable: /rails
  ArgumentError: could not find a temporary directory

raised from MiniMagick, which Active Storage loads while Rails boots.
Ruby's Dir.tmpdir skips a candidate that is world-writable without a
sticky bit, and a Kubernetes emptyDir is created mode 0777 with no sticky
bit - so with a read-only root filesystem there was no candidate left.

A tiny init container, the app's own image running as the same non-root
uid and with no capability added, now creates one private subdirectory
(mode 0700) inside each tmp emptyDir, and both containers run with
TMPDIR pointing at it. Creating an entry in a 0777 directory and chmod'ing
what one just created needs no privilege, so the hardening standard holds
unchanged.
2026-09-26 21:08:55 +00:00
jklein 93fdb3eaea sure: new chart for the we-promise Sure app (1.0.0+up0.7.4)
One StatefulSet with two containers - the Puma web process and a Sidekiq
worker - sharing one ReadWriteOnce PVC at /rails/storage. That shared
volume is the reason they live in one pod: splitting them would need an
RWX volume. Only the web container migrates, because the image entrypoint
runs db:prepare solely for the './bin/rails server' command; the worker is
started through args: so the entrypoint (and its jemalloc preload) stays
in place and the two can never race.

Probes are chosen for what they prove: liveness and startup use Rails'
/up, which is a boot check and touches no database, while readiness uses
'/', which runs User.count through authenticate_user! in self-hosted mode
and therefore fails while Postgres is unreachable. The worker speaks no
HTTP, so all three of its probes ask sidekiqmon whether this pod still
holds a Sidekiq heartbeat in Redis.

Three OnePasswordItem CRs (app, database, redis), ingress.domain kept
required outside the Ingress because APP_DOMAIN and the WebAuthn relying
party read it, strict values.schema.json, CI fixtures with throwaway
Postgres 16 and Redis, and a Renovate manager on semver so the
-alpha/-rc/-hotfix channel and the moving tags are never selected.
2026-09-26 21:08:55 +00:00
jklein 4715d85b99 satisfactory: server.storage stays the documented exception (#39)
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Satisfactory Helm Chart / Publish Satisfactory Helm Chart (push) Successful in 7s
Walks back the one part of the previous commit that changed rendered output:
storage was pulled into the server fallback, so an erased size fell back to
20Gi instead of rendering a PVC without one.

values.schema.json had documented the opposite decision - "an erased value
yields an invalid PVC rather than a silently degraded workload" - and that is
someone's reasoned trade, not collateral of the nil-pointer bug next to it.
Overturning it inside a patch would have made this the only release where
valid values render differently, and it would have settled an open design
question in a PR nobody opened for that. It goes to #39 instead.

So storage is out of the defaults again and the schema note is restored
verbatim, extended only by where the exception is argued. values.yaml marks
it as the one key of the block that is deliberately not null-safe. The
README rule that had leaned on this example now says the thing that actually
holds: an exception is fine, an unmarked one is not.

Everything else stands - dev and server null-safe, the single-key findings on
the ports, formatNumber against exponential notation, 7.0.1.

Re-measured after the walk-back: rendered output against the chart defaults
and against a full override set is byte-identical to 7.0.0, NOTES.txt
included, and "--set server=null" now renders exactly the sizeless PVC that
"--set server.storage=null" rendered on 7.0.0.
2026-09-23 20:45:24 +00:00
jklein 5ff6e401c7 satisfactory: dev and server fall back instead of aborting (7.0.1)
--set dev=null aborted the render at .Values.dev.debug and --set server=null
at .Values.server.replicas, while the third block, game, has fallen back to
the chart defaults since 7.0.0. Half-safe is the worse state of the two:
whoever sees game=null work assumes server=null does too.

Both blocks now run through satisfactory.defaultedDict like game, via two new
helpers next to it. server.resources is deliberately NOT re-defaulted there -
it already runs through satisfactory.effectiveResources, which carries those
defaults and is null-safe on its own, and a second copy of limitFactor: 2 /
4Gi / 1 / 5Gi would be a third place for them to drift.

server is read in four files, not one, and the fourth is NOTES.txt, which
helm template never renders - a fix confined to the manifests would have
looked green and still aborted on a real helm install. All four go through
the helpers.

The single erased key was the quieter half of the bug and is fixed with it:
a nulled server.gamePort rendered "port:" and "nodePort:" empty into the
Service and an empty containerPort into the StatefulSet, and a nulled
server.storage a PVC asking for no size - manifests rejected by nothing until
the API server saw them. server.storage was documented in values.schema.json
as deliberately not null-safe; that reasoning does not survive the block-level
case, because it made "server: null" render a sizeless PVC instead of falling
back like everything else. The schema note is replaced rather than left lying.

Rendered output for valid values is unchanged: helm template against the
chart defaults and against a full override set is byte-identical to 7.0.0,
and so is NOTES.txt. Hence a patch bump, appVersion untouched.

Fixes #16
2026-09-23 20:45:24 +00:00
jklein 02b3c3ce75 renovate: align the config with cluster-config
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
The shared jlab-renovate-bot instance serves this repo and cluster-config,
and two repos behaving differently under one bot is a source of surprises
rather than of safety. This adopts cluster-config's configuration:
config:best-practices in extends, labels ["renovate"], no PR caps
(prHourlyLimit 0, prConcurrentLimit 100), separateMinorPatch, an explicit
dependencyDashboard, and global automerge with platformAutomerge.

Two of those choices were previously wrong here and are now right, because
the branch protection on main has caught up: a review requirement and
validate-charts as a required check are both active. platformAutomerge
therefore arms the PR on the forge and Gitea merges it when the check
passes AND the review exists - the review is the gate, which is what makes
automerging majors something other than unattended merging.

Comments that asserted the opposite are rewritten rather than left to rot:
the platformAutomerge: false comment ("not (yet) a required check on either
side"), the "Majors stay manual" rule, the README PR policy bullets and the
required-check paragraph, and the CLAUDE.md forge notes.

config:best-practices pulls in docker:pinDigests, which is switched off
again for custom.regex and helm-values: an appVersion: is a tag by
construction and has nowhere to put a digest. Kept deliberately:
minimumReleaseAge of three days on the routine updates (every merge here
publishes a chart), the ignorePaths and ci/** opt-outs, every per-chart
manager, the nginx and qbittorrent constraints, and postUpgradeTasks.

Fixes #34
2026-09-22 19:44:28 +00:00
jklein 5b13569948 docs: the Coder workspace is the normal case, so tea runs without op
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 7s
Work on this repo happens in a Coder workspace that has no 1Password CLI.
The rule that tea may only be called as 'op plugin run -- tea' therefore
described an environment that no longer exists here, and following it meant
either waiting for a tool that is not coming or ending with a pushed branch
and no PR.

tea is now called directly. It carries the workspace's stored login, so what
it opens is authored by the bot account - accepted, and named in the report
when it happens. The older instruction is kept for machines that do have op.

Signing needed the same correction: it runs through the workspace's own SSH
signer, not op-ssh-sign, and the check is 'git log --format=%G?' printing G.
2026-09-18 11:20:46 +00:00
jklein feb70cf1e4 docs: order the wiki step before the merge, not after it
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
2026-09-18 08:49:24 +00:00
jklein 03bb454eb7 docs: make the wiki the knowledge base and its upkeep a required step
The wiki now carries one page per chart (prerequisites, installation, the
required 1Password fields, the complete values reference and the chart's
traps) plus the workflow, the pipelines, versioning, Renovate, the style
guide and the incidents behind each rule.

README points at it and keeps its own role: the overview for someone reading
the code, while anything an operator needs lives in the wiki. CLAUDE.md gains
the wiki edit as step 7 of the mandatory workflow, with a table of what
obliges an edit - and the one thing that does not, a plain app-version bump,
since Renovate automerges those and never touches the wiki.

Also corrects the patchmon row: the 2.x server serves frontend and /api/* from
one process, so the chart renders one Deployment plus an optional guacd
sidecar, not two Deployments.
2026-09-18 08:48:34 +00:00