Commit Graph
17 Commits
Author SHA1 Message Date
jklein b9917505cd Drop the issue-migration script: a one-off job is not repo content
The migration from GitHub issues to Gitea runs exactly once, by hand over
the API. Checking a script in for it would leave permanently maintained
code behind for a task that stops existing after its single run - and
worse, it would read to every later visitor as a tool meant to be reused.
A one-off move is carried out once and is history afterwards; it is not a
part of the repository.

The one reference in CLAUDE.md is rewritten rather than deleted: that the
issue numbers do not survive the move is still true and still worth
knowing, so the bullet now says a `#n` in this repo means the GitHub
issue, and that what to do about the old references is open in #225.
2026-09-16 16:02:41 +02:00
jklein c3889a66b6 Drop the Renovate workflow: the Gitea side already has a shared instance
A measurement overtook this part of the change. A shared `jlab-renovate-bot`
instance already runs on the Gitea side and already has this repository
enrolled - its first run there opened PRs for searxng and gotenberg plus the
dependency dashboard. A second runner in this repo would produce duplicate
PRs and two schedulers rewriting each other's branches, so
.github/workflows/renovate.yml is removed again.

Two things fall out of that run, and both were open risks:

- The instance reads our renovate.json5. searxng and gotenberg are
  customManagers targets of ours - no built-in manager finds an appVersion
  in a Chart.yaml - so the config is demonstrably in effect.

- allowedCommands is already granted there. The searxng PR's diff carries
  `version: 1.0.4+up2026.9.11.ffe96f8a6 -> 1.0.5+up2026.9.13.96479fff6`
  next to the appVersion, and nothing but ci/renovate-bump-chart.sh writes
  `version:`. Had that allow-list been missing, postUpgradeTasks would have
  been skipped SILENTLY and every chart PR on Gitea would have been red on
  the version check with nothing naming the cause.

With the workflow gone there is no container image to pin either; verified
by dry run that the ghcr.io/renovatebot/renovate dependency is no longer
extracted, while TRIVY_VERSION still is - that one belongs to the Trivy
replacement and stays.

README and CLAUDE.md now say Renovate runs on Gitea as an existing shared
instance and that no workflow belongs here, so the next reader who finds
none knows why instead of building one.
2026-09-16 15:49:36 +02:00
jklein a6c27ebef3 Prepare the Gitea move: measured CI deviations, self-hosted Renovate, issue migration script
Groundwork for #225, built on a throwaway pull request against the Gitea
instance rather than on assumptions. The move itself is not part of this
change - no Actions are switched off, no mirror is enabled, no branch
protection is touched.

What the probe run measured, and what follows from it:

- Which jobs need a Docker socket: only the install-test. Measured per
  job - detect-changed-charts, build-chart, the quantity render and the
  Trivy config scan all pass on `ubuntu-latest`, which on that instance
  has no socket. The install-test dies there at "Create k3d cluster"
  with "Cannot connect to the Docker daemon", an error naming k3d and
  never the runner label. Its `runs-on` is now an expression: the label
  `ubuntu-docker` on Gitea, `ubuntu-latest` on GitHub, because the
  former does not exist there and a chart PR would otherwise queue
  forever during the handover. Gitea evaluates it - the probe job was
  taken by the docker runner.

- The install-test still cannot pass on Gitea. That daemon is rootless
  with "Cgroup Driver: none"; the k3s node restarts immediately with an
  empty log because k3s needs cgroup delegation. The label is necessary
  and not sufficient, and the fix is the maintainer's.

- aquasecurity/setup-trivy cannot be used on Gitea: it checks out
  aquasecurity/trivy from github.com with `github.token`, which there is
  a Gitea token, and ends on "could not read Username for
  https://github.com". Replaced by the release tarball - one request,
  same behaviour on both forges, green on the probe run.

Also in this change:

- .github/workflows/renovate.yml, Renovate in self-operation: a daily
  scheduled run in the official container, inert on github.com via a
  `github.server_url` guard. Every rule proved by dry run instead of
  assumed - the pinned Renovate image is tracked by the built-in
  github-actions manager (44.80.0 -> 44.87.1, minor), TRIVY_VERSION is
  still tracked after setup-trivy is gone (v0.72.0 -> v0.74.0, minor),
  and `ubuntu-docker` is not extracted by the github-runners manager at
  all, so nothing will quietly "correct" it.

- ci/migrate-issues-to-gitea.sh, runnable, with --dry-run, and NOT run.
  Its header names what it cannot do: comment authorship, the #<n>
  references between issues, and attachments.

- CLAUDE.md and README.md on the new flow: which forge and which tool,
  auto-merge on green checks in place of the merge queue Gitea does not
  have, and the measured differences of the two CIs.

Refs #225 - deliberately no "Fixes": the move is not finished with this.
2026-09-14 17:07:31 +02:00
jklein 485f4fa52b satisfactory: game.enableSeasonalEvents says what it does (7.0.0+upv1.9.10)
game.seasonalEvents was handed to the container unchanged, into the image's
DISABLESEASONALEVENTS. Its default true therefore wrote
DISABLESEASONALEVENTS=true: the seasonal events were OFF in every install
this chart has ever produced, while values.schema.json described the very
same key as "true means events are enabled" (#215).

Of the two ways out, this is the loud one. Inverting the template under the
old key would have flipped the behaviour of every existing release in
silence - the same class of mistake that created the issue. Renaming the key
cannot be missed: game.seasonalEvents no longer exists, additionalProperties
is false on the game block, and a values file that still carries it fails
schema validation on upgrade instead of quietly meaning the opposite.

The new key is positive (enableSeasonalEvents) to avoid a double negative,
so the chart negates it on the way into the env var. Go's "not" cannot do
that: it reads every non-empty string as true, and the schema deliberately
accepts the string form of booleans, so "false" would have flipped the wrong
way. The helper compares explicitly and fails on anything that is neither
true nor false rather than filing it under "off".

The default is enableSeasonalEvents: false, which renders the
DISABLESEASONALEVENTS this chart has always rendered. At chart defaults the
6.1.0 and 7.0.0 manifests are identical, so upgrading alone moves no server;
switching the events on is now a deliberate act with a key that means it.

Separate finding from the same issue, fixed here because it lives in the
block being touched: "--set game=null" - or a leftover "game:" whose
sub-keys were deleted - aborted the render with a nil pointer at
.Values.game.maxPlayers. The game block never got the null-safe treatment of
#99 that probes, security contexts and resources have. It now falls back to
the documented defaults, and the schema accepts the null accordingly. The
same gap remains in the dev and server blocks; it is reported, not fixed
here.

Major bump: a values key is removed, and the schema rejects it.
2026-09-02 13:42:39 +02:00
jklein 01f804a43a ci: publish charts to a classic index-based Helm registry
The publish step no longer speaks OCI. "helm registry login", "helm push"
and "helm registry logout" are replaced by a single HTTP POST of the
packaged .tgz to <charts-url>/api/charts, with Basic auth per request.

Credentials and the target URL come from a new 1Password item
op://GitHub-Actions/jlab-registry-access (username, password, charts-url).
The old item is left untouched, so a revert of this commit restores the
previous pipeline without any further work. No registry address enters
the repository.

The three properties of the old step survive:

- values are read via steps.<id>.outputs.* into the step's own env block
  and used as quoted shell variables, each guarded with ":?"
- credentials stay out of argv: "curl --config -" instead of "--user"
- a failed upload fails the job: "--fail-with-body", because curl exits 0
  on HTTP 401/403/409 without it and the publish would go green with
  nothing uploaded

The "<chart>-*.tgz" glob is replaced by the file name built from the
chart name and the version already read from Chart.yaml; --upload-file
takes exactly one file. Two small additions fall out of measuring the
new step: the registry host is masked, because a curl connection error
prints only "<host>:<port>" and the mask on the full URL does not cover
that, and whitespace in the credentials is rejected up front, because an
unquoted curl config value ends at the first space.

No chart is touched and no chart version is bumped.
2026-09-01 18:07:08 +02:00
jklein 6b0d8c9232 ytdlp-web-player: new chart with an optional oauth2-proxy subchart (1.0.0+upv1.2.0)
Deploys matszwe02/ytdlp_web_player, a Flask app that fetches videos with
yt-dlp, transcodes them with ffmpeg and serves them as HLS. It ships no
authentication of any kind, so the chart carries a preconfigured oauth2-proxy
as an optional subchart, in the data path in front of the app.

This is the first chart in the repo with a dependency, so the rule "charts
have no dependencies" falls here. It is replaced in CLAUDE.md and README.md by
a bar: a dependency has to be optional and off by default, upstream's chart is
consumed as it comes (documented where it falls short of our conventions,
never patched), and the price is written down - above all that a subchart is
invisible to everything downstream watching Helm release versions, so a new
proxy release only reaches a cluster when this chart is republished.

CI learns the dependency in five places, not the three the plan expected:
helm lint, package, template and install all exit 1 while charts/ is unfilled,
so ci/helm-dependencies.sh runs before lint and package in validate-charts.yml,
before package in publish-helm-chart.yml, and inside run-install-test.sh,
run-trivy-config.sh and run-quantity-render-test.sh - the last of which renders
every chart on every PR and would have gone red first.

Notable chart decisions, each measured rather than assumed:

- The image has no USER and runs as root by default, but not by necessity: it
  runs under an arbitrary uid with readOnlyRootFilesystem and the full
  hardening baseline. Only the pinned uid is a deviation, and it is documented.
- Two emptyDirs, each justified separately. /data holds the video cache
  (DATA_PATH). /tmp is NOT mounted - measured across metadata extraction, HLS
  transcoding, sprite generation and audio extraction, the image never writes
  there. What it does write is $HOME/.cache, so /cache is mounted with HOME and
  XDG_CACHE_HOME pointed at it; without it yt-dlp re-downloads its JavaScript
  challenge-solver from github.com on every extraction.
- The proxy is wired through config.configFile, the only value the subchart
  passes through tpl - config.upstreams is emitted with toJson and would carry
  a literal {{ .Release.Name }} into the manifest.
- Rendering aborts when this chart's Ingress and the proxy's claim the same
  host: ingress-nginx rejects the second one and a Fleet bundle then sits in
  ErrApplied until forceSyncGeneration is bumped.

Fixes #214
2026-08-30 17:34:49 +02:00
jklein 6b25d082a7 renovate: chart-aware config with an automatic version bump
Renovate only ever rewrites appVersion or an image tag, so every PR it
opened against a chart directory failed the build-chart version check
(#138, #139). ci/renovate-bump-chart.sh recomputes
version: <semver>+up<appVersion> as a postUpgradeTasks hook: chart major
when the app major moves, chart patch otherwise.

Each chart renders its main image as <repo>:{{ .Chart.AppVersion }}, so
repository and version live in different files and Renovate cannot join
them. The mapping is one customManager per chart in renovate.json5,
deliberately not a comment inside each Chart.yaml - that would touch all
20 chart directories and force 20 publishes for a comment change.

Patch and digest updates automerge after a 3-day minimumReleaseAge once
the pipeline is green; majors arrive one line at a time to match the
stepwise app-major procedure. ci/** stays excluded: the install-test
fixtures pin throwaway infrastructure to major lines on purpose.
2026-08-24 11:42:43 +02:00
jklein b218f64eac template-chart + README: values.schema.json, gate and null-safe helpers
Closes out the schema programme (#68) by moving what the 18 charts taught
into the reference implementation and the binding guidelines.

template-chart:
- values.schema.json as the reference schema: additionalProperties false
  on every chart-owned level, pod-spec passthrough left open, and the
  reasoning inline in the descriptions so a copy carries it along.
- defaultedDict and subBlock plus the per-block fallbacks for both
  security contexts, the three probes and resources, wired into both the
  Deployment and the StatefulSet.
- the reference itself violated guideline 1: its env block reads
  ingress.domain, so with the Ingress gated off it rendered an empty
  value. That read is now required(), and the comment says why - the
  template is what people copy, so it has to teach the guard, not the
  hole.

README, new section "Values that must not lie" with the five rules:
1. a value read outside its obvious template needs its own guard
   (matrix-synapse published '{"m.server": ":443"}' to federation without
   it, rendering cleanly and reporting healthy)
2. probes, contexts and resources through the null-safe helper, never
   behind an "if" - a guard removes the probe instead of showing a null
3. no liveness on an external dependency where a dependency-free path
   exists, otherwise document why on the helper
4. a fallback reproduces THIS chart's defaults, not the repo standard -
   calibre's readOnlyRootFilesystem: false is what keeps the runtime uid
   at 1000 instead of 911 and existing volume data readable
5. a probe default carries its context - port name, Host header, exec
   command - or the probe vanishes (teamspeak) or gets 403 (patchmon)

Plus the two mechanics that are easiest to lose: null reaches schema
validation only for keys WITHOUT a chart default (which is why the helper
and the ["object","null"] types each cover a case the other cannot), and
schemas must be validated against real bundle values - that step caught a
draft of mine that would have broken three live paperless releases.

CLAUDE.md: the new-chart checklist now names the schema and the
adaptation step, since the copied file is chart-specific and wrong until
it is adapted.

freesailarr is not covered: it is not on main yet (#59).
2026-08-23 22:35:03 +02:00
jklein 1bca98b9ef Add checksum/config annotation to template-chart and make it binding
A changed ConfigMap did not restart any pod in this repo: no chart carries
a checksum/config annotation, so the pod template stays identical and the
running container keeps its old config. With subPath mounts (the usual form
here) the kubelet never refreshes the file either, so the change is silently
ineffective - see #82, where this caused a real outage while every bundle was
green and the pod Ready.

template-chart is the reference implementation, so it gets the annotation on
both pod templates (Deployment and StatefulSet), and the README style guide
gets a new binding section 9 covering the rule, the reason, and the limit for
1Password-backed Secrets.

Major bump: runtime behavior changes - a config change now costs a restart.
2026-08-23 18:49:10 +02:00
jklein 408232a36f Document the consolidated chart style guidelines
README.md gains a comprehensive 'Chart style guidelines' section - the
binding end state of the conventions overhaul (#32, batches 0-4) for all
current and future charts: naming (incl. the PVC-name exception and the
StatefulSet migration pattern), versioning scheme and semver rules,
image pinning, the ingress standard (domain + clusterIssuer strictly
required without defaults, className default nginx, annotations merge,
TLS secret name, homeassistant exception), security hardening with
mandatory documentation of deviations, probes, the auto-limits scheme,
the secrets.<name>SecretRef convention, tpl-rendered env blocks,
scaling, and the icon/lint/CI-fixture/no-dependencies rules.

The Versioning section now points there and documents that app-version
updates go through PRs. CLAUDE.md's conventions section is aligned as a
compact summary referencing the README guide.
2026-08-22 23:16:41 +02:00
jklein f091c33205 Conventions batch 2: flexible ingress standard, configurable probes
Establish the flexible ingress standard from #32 in template-chart and
document it in README/CLAUDE.md: ingress.domain and ingress.clusterIssuer
stay required, new ingress.className value (default nginx) replaces the
hardcoded ingressClassName, new optional ingress.annotations are merged
with the cert-manager cluster-issuer annotation, TLS secret name
unchanged (tls-<release>-<chart>-ingress).

Apply the standard to cyberchef and patchmon (the two charts without an
own batch-3 major PR) and make the patchmon probes values-configurable
(server + guacd, rendered through tpl so the default Host header keeps
following ingress.domain). Defaults render semantically identical to the
previous hardcoded output.

cyberchef 5.0.1 -> 5.1.0, patchmon 5.0.1 -> 5.1.0 (minor, additive).
2026-08-22 15:35:09 +02:00
jklein 13d9abb6d2 Bring CLAUDE.md up to date with current validation and versioning practice
- validation happens via strict lint, PR pipeline and install-test stage
- new charts need ci/ fixtures or an exclusion entry
- codify semver conventions (patch/minor/major)
- app updates via PRs with stepped major upgrades
2026-08-21 23:26:32 +02:00
jklein b5eba4cbf1 Harden pod and container security contexts across all workloads
Add securityContext hardening (runAsNonRoot, readOnlyRootFilesystem,
capabilities drop ALL, allowPrivilegeEscalation false) to the 10 workloads
that lacked it: apache-tika, cyberchef, gotenberg, homeassistant,
matrix-delegation-nginx, paperless-server, paperless-consume cronjob
(plus automountServiceAccountToken false), patchmon-frontend,
urlaubsverwaltung and zipline. All contexts are configurable via
securityContext.pod / securityContext.container in values.yaml; writable
paths are provided as emptyDirs. Image-forced deviations (Home Assistant
root start for s6-overlay + NET_RAW for discovery, paperless root init
container for /run ownership) are documented in the charts' values.yaml.
Major version bump for every changed published chart.
2026-08-21 21:49:05 +02:00
jklein f349428d0e Keep workflow docs in CLAUDE.md and general repo info in README current
- CLAUDE.md: document plan-on-issue step, doc-currency rules, k3d verification convention
- README: document validate-charts PR pipeline, branch-filtered publish trigger, strict lint, local k3d runtime verification
2026-08-21 18:36:31 +02:00
jklein 0d3b722dd1 Update issue workflow: manager plans, dev agent implements and tends its PR 2026-08-21 17:17:48 +02:00
jkleinandClaude Fable 5 0275c01a18 Document mandatory issue workflow in CLAUDE.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 16:35:05 +02:00
jkleinandClaude Fable 5 116a0499c5 Add CLAUDE.md with repo guidance for Claude Code
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 16:17:19 +02:00