d8b392bf64cb6c8715bf17ce33ab7f59ede2c93c
17
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
78d7020dab
|
freesailarr: MediathekArr as the tenth container, off by default (3.1.0+up1.0.0)
MediathekArr fakes a Usenet indexer towards Prowlarr and a SABnzbd downloader towards Sonarr/Radarr and fetches from the ARD and ZDF media libraries. Both consumers already share gluetun's network namespace, so they reach it over 127.0.0.1 - no service, no DNS, no ingress detour. Purely additive: the flag defaults to false and a default render is byte-identical to 3.0.5 (verified against the previous chart for both the default values and the CI values). The four things that fail silently without them: - A START GATE on gluetun. MediathekArr fetches its ruleset once at start and never retries; started before the tunnel it answers every Newznab search with 0 results while the container is up, Ready and quiet. Kubernetes cannot order two regular containers and an initContainer would deadlock, so the wait sits in front of the image entrypoint: poll gluetun's health server on 127.0.0.1:9999 and exec the entrypoint afterwards; on timeout the container exits rather than starting blind, because a crash loop is visible and an empty indexer is not. The startup probe budget (300s) deliberately outlasts the gate (180s). - incomplete/ AND complete/ ON THE SHARED DOWNLOADS CLAIM, not in an emptyDir. An emptyDir counts against the container's ephemeral-storage limit, and one large download would have the kubelet evict the whole pod with all ten containers - the trap ytdlp-web-player documents. - THE COMPLETE PATH SONARR SEES. The claim is mounted at /downloads exactly as sonarr/radarr mount it, and the chart sets DOWNLOAD_COMPLETE_PATH itself, so the SABnzbd complete_dir it reports is the same absolute path on both sides. - TWO PORTS: 5007 (SABnzbd role and UI, primary) and 5008 (Newznab role for Prowlarr), both declared to gluetun's firewall and both on one Service. The SABnzbd API is at /download/api - /api on 5007 answers with a misleading Newznab validation error. Further details: - Image pinned by digest (tag + digest): "latest" is a 2024 build, "beta" moves. Single-arch linux/amd64. The built-in helm-values manager cannot follow that pair, so renovate.json5 gets the chart's first customManagers block; verified by dry-run with two fixtures, since a matching and a broken rule look identical while the pin is current. - Hardened as measured: non-root, uid/gid from puid/pgid, readOnlyRootFilesystem: true, all capabilities dropped, no PUID/PGID. - Probes on measured 200 endpoints (there is no health endpoint): readiness and startup on /download/, liveness as an exec covering BOTH ports, because one container runs two processes and a wedged Newznab half is exactly what makes searches come back empty. - No ingress by default: the UI links its assets with root-absolute paths, so it can only be served at the root of a host; on the shared domain that root belongs to seerr. A sub-path fails the render instead of producing a broken UI. - values.yaml and NOTES.txt document the geo MARKER PROCEDURE (not a table of series), the escape route of a German exit and its price for the whole stack, the fail_message: null trap, and the HLS question as an open question. - CI: mediathekarr enabled in ci/freesailarr/test-values.yaml, install timeout raised to 1200s for the extra image and the gate. Fixes #245 |
||
|
|
ce73bc1111
|
freesailarr: no exponential notation in computed limits (3.0.2+up1.0.0)
The null-safe fallback helpers from #99 route values through fromYaml, which parses every number as float64. Go's "%v" switches float64 to exponential notation from about 1e6 upwards, so a bare byte count like 1048576 reached the suffix check inside multiplyQuantity as "1.048576e+06" and the render aborted with `unsupported resource quantity suffix "e+06"`. Both printf "%v" sites in multiplyQuantity now go through a new formatNumber helper that forces plain decimal notation and trims the padded zeros. The second site is the quiet half of the bug: a fractional limitFactor together with a bare number produced no error at all, just an invalid quantity such as "1.4999985e+06" that only the API server rejects on apply. limitFactor is typed "number" in this chart's values.schema.json, so a fractional factor is an ordinary, schema-valid input. All ten resource paths of the stack are affected and were verified one by one (gluetun, qbittorrent and its configInit init container, prowlarr, flaresolverr, radarr, recyclarr, sonarr, seerr, bazarr). The VPN, firewall and container structure is untouched: rendering the chart with its default values is byte-identical to main. Teil von #157 |
||
|
|
fc768efaf5
|
freesailarr: shared domain with sub-paths, qBittorrent save path (3.0.0+up1.0.0)
Adds a second, optional ingress shape and fixes the download target of qBittorrent. The existing per-app-host shape stays the default and renders unchanged. Shared domain (#167) Setting ingress.host moves every app without a host of its own into one Ingress object with one path each (<app>.ingress.path, default /<app>), one TLS secret and therefore one DNS name and one certificate for the whole stack. A per-app host always wins and keeps its own object, so mixed operation needs no extra values. The apps differ in what they can do under a sub-path, and the chart follows that rather than pretending they are alike: - radarr, sonarr, prowlarr get <APP>__SERVER__URLBASE from the chart, but only when their own env block does not already carry the variable. - bazarr has no environment variable for its base path and stays one documented manual step (Settings -> General -> Base URL). - qbittorrent has no base path at all and renders a second object on the same host with a prefix-stripping rewrite. It repeats the same TLS secret name and deliberately omits the cert-manager annotation, so both objects state their own TLS while only one certificate is requested. - seerr has no base path either and cannot be rewritten, so it is pinned to "/" and a fail refuses any other path. A second fail refuses two apps claiming the same path. qBittorrent save path (#166) Without Session\DefaultSavePath qBittorrent writes to its built-in default /config/Downloads - the 1Gi config volume. It fills within minutes, every torrent goes to "errored" and qBittorrent can no longer write its own metadata to BT_backup, so a restart in that state costs the torrent list. The init container (renamed qbittorrent-config-interface -> qbittorrent-config-seed, since it no longer only sets the interface) now seeds qbittorrent.savePath, default /downloads. Unlike the interface binding it is inserted ONLY when the key is absent and never replaced: a path changed in the WebUI is a legitimate operator decision and must survive a restart. This is the reason for the major bump: an installation that never set the path writes somewhere else after the upgrade. Documentation NOTES.txt and the media block in values.yaml now spell out why a shared claim with three subPaths does not produce hardlinks: the kernel compares the vfsmount in do_linkat, not the superblock, so two bind mounts of one filesystem are EXDEV - while stat reports the same device number on both sides and suggests the opposite. The layout that would work (one claim, one mount, categories as sub-directories) is named as a possibility; the chart cannot express it today. README.md gains the shared domain as the second admissible ingress shape of the style guideline. |
||
|
|
39e9613ee3
|
freesailarr: one volume entry per media claim (2.0.2+up1.0.0)
media.downloads, media.movies and media.tv rendered three fixed volume entries, one per category. Two categories on the same existingClaim therefore produced two pod volumes with an identical claimName, and such a pod never starts: it stays in Init:0/1 without a single kubelet event (measured on RKE2/Longhorn, RWO and RWX alike). The volume list is now deduplicated by claim name. A new mediaVolumeName helper names the entry after the first ENABLED category in the fixed order downloads, movies, tv that carries the claim; the other categories mount that same entry and differ only in their subPath. The restriction to enabled categories matters because the three entries render under different conditions - a canonical owner whose entry is not rendered would be a dangling reference. With three distinct claims the chart renders byte-identically to 2.0.1, hence a patch bump. Also corrected in the values documentation, after measuring instead of assuming: the kubelet creates missing subPath directories itself, but they inherit the claim root's mode, and fsGroup cannot be relied on to fix that - Longhorn's fsGroupPolicy (ReadWriteOnceWithFSType) skips fsGroup on RWX claims entirely. What has to hold is that the claim root is writable for PUID/PGID. No chown or init container was added. |
||
|
|
a37588c2fe
|
freesailarr: schema, null-safe fallbacks, seccompProfile, NOTES.txt
Retrofits the repo-wide groundwork that #59 deliberately left out to keep the reviewed scope. Chart 2.0.0+up1.0.0 - a major, because seccompProfile changes runtime behaviour; appVersion stays the freely chosen marker 1.0.0. values.schema.json (#68). The largest schema in the repo: nine app blocks with probes, resources, security contexts, env and image. additionalProperties is false on every chart-owned level; the objects that go verbatim into the pod spec stay open. It rejects a mistyped app name, a mistyped key at any depth and ingress.domain - the key #59 removed, which until now would have been accepted in silence. Null safety (#99), both halves, because they cover different cases. The defaultedDict helper catches a key WITH a chart default, where Helm's coalescing deletes the null before the schema ever sees it; the widened ["object","null"] types catch a key WITHOUT one, where the null survives into validation. Measured on the old chart: radarr.securityContext: null rendered "securityContext: null" - the whole hardening gone - and securityContext.capabilities: null dropped the capability block entirely, so the container kept every default capability instead of dropping ALL. Both render green. The fallback table reproduces THIS chart's defaults including its deviations: the five linuxserver.io containers keep runAsNonRoot unset and a writable root filesystem so their s6 init can drop to PUID/PGID, flaresolverr keeps its numerically pinned uid, gluetun keeps NET_ADMIN, and recyclarr keeps its exec probes. Restoring the "clean" standard there would put the linuxserver containers back on the image's uid 911 - the failure calibre-web-automated hit. The sync between table and values.yaml is not left to discipline: rendering with every block of the silent class explicitly set to null is byte-identical to rendering the defaults, which is how a drift shows up. seccompProfile RuntimeDefault (#84) in the pod context, so it covers the init container too. Trivy's KSV-0104 drops from ten findings to none. NOTES.txt (#43) covering what actually bites: the pod is not Ready until the tunnel is up, the two fields the 1Password item needs, the warning when firewallOutboundSubnets is unset, the immutable volumeClaimTemplates, and the measured fact that imports do not hardlink across the media categories. Also, from point 5 of the plan: checksum/config does not apply - the chart has no ConfigMap - but checksum/secret-ref does, and was missing. Helm never sees the secret's contents, yet it does see which op:// item the OnePasswordItem points at, so repointing secrets.gluetunSecretRef now restarts the pod instead of leaving the old credentials running. README: the #43 status line was stale (it still claimed only homeassistant ships NOTES.txt), and the mandatory-icon rule now records freesailarr as a documented exception - #59 dropped the icon and did not amend the rule. |
||
|
|
af5f9ed79b
|
Pin gluetun in values too, so Renovate proposes it there
The chart bundles nine independently versioned upstreams, and eight of them were already pinned in values.yaml while only gluetun took its tag from appVersion. That split had no upside: it made gluetun the one image Renovate could not reach through the built-in helm-values manager, and it implied a "main app" the stack does not have. gluetun now carries gluetun.image.repository/tag like every other image, and the StatefulSet renders it from there. No tag comes from appVersion any more. Renovate picks all nine up through helm-values - the manager the repo already relies on for sidecar images - and ci/renovate-bump-chart.sh bumps the chart version with each update. Consequences, all documented where they bite: - renovate.json5 deliberately gets no app-version block for this chart. One would either duplicate the helm-values updates or rewrite an appVersion that selects no image. The absence is commented so it does not read as an oversight. - freesailarr leaves the change-app-version.yml choice list. That workflow rewrites appVersion and republishes, which for this chart would change no deployed image at all. - appVersion becomes a hand-maintained marker for the gluetun line. Since renovate-bump-chart.sh bumps the patch level on an unchanged appVersion, a gluetun update has to be paired with a hand edit of appVersion, or the +up suffix stops describing what the chart deploys. Noted in Chart.yaml, values.yaml and README. |
||
|
|
e4f70bc856
|
Correct two firewall claims the runtime test disproved
FIREWALL_INPUT_PORTS does not open the app ports inside a pod network. gluetun detects the pod's own subnet and inserts an INPUT accept for it ahead of the per-port rules, and every packet arriving at the pod IP matches that rule by destination. Measured with the packet counters: 50 fresh requests moved the subnet rule from 306 to 356 packets while every --dport rule stayed at 0 since container start. A port that listens but is not in the list (gluetun's control server on 8000) is reachable from another pod all the same. The variable is kept - it is the explicit statement of which ports are meant to be reachable, and the subnet detection is gluetun's behaviour rather than a guarantee of this chart - but the comments no longer claim the traffic would be dropped without it. Second correction, same run: the /tmp/gluetun emptyDir fixed the "open ... permission denied" failure, but gluetun also chowns the file, which needs CAP_CHOWN. With all capabilities but NET_ADMIN dropped that logs "chown /tmp/gluetun/ip: operation not permitted" on every VPN restart. The file is written and the public IP is persisted, so this is a log line rather than a failure, and granting CHOWN to silence it would widen the container's capabilities for nothing. Noted where the mount is declared. |
||
|
|
cdb66a32f4
|
Per-app ingress and service, media subPaths
Three review changes to the freesailarr chart. Ingress: the chart no longer derives hosts as <app>.<ingress.domain> from one Ingress object. The global ingress.domain is gone; every app carries its own <app>.ingress.host and renders its own Ingress <release>-<chart>-<app>-ingress with its own TLS secret tls-<release>-<chart>-<app>-ingress. The hosts are therefore independent and may live in unrelated domains. The host is required, but the required() call sits inside the loop over the *enabled* app ingresses, so switching an app's ingress off must not make the chart demand a host for it. clusterIssuer, className and annotations stay global defaults and are overridable per app; an explicitly set per-app value wins (nil check instead of "default", so an empty override is honoured). Media: media.<category>.subPath mounts a sub-directory of the claim instead of its root, default null (claim root = previous behaviour). The same existingClaim may back all three categories with different subPaths. That is the point rather than cosmetics: Radarr and Sonarr import by moving, and a move within one filesystem can be a hardlink instead of a copy - across volume boundaries every import duplicates the file. Services: the collective Service with named ports is replaced by one ClusterIP Service <release>-<chart>-<app>-service per enabled app that listens on a port, flaresolverr included; recyclarr serves no port in daemon mode and gets none. Each ingress points at its own app's service. All containers share gluetun's network namespace, so the services differ only in their port - they provide stable per-app DNS names, not network separation. The StatefulSet's governing serviceName no longer names a removed object. |
||
|
|
d4f03cdfd9
|
Give gluetun a writable /tmp/gluetun
gluetun logs "clearing public IP data: open /tmp/gluetun/ip: permission denied" every few seconds and never persists its public IP. It creates /tmp/gluetun with mode 0644, i.e. without the execute bit needed to enter the directory; it runs as root, but with all capabilities except NET_ADMIN dropped there is no DAC_OVERRIDE left to paper over the missing bit. An emptyDir provides the path with usable permissions. |
||
|
|
adc87c2cc1
|
Bind the qBittorrent session to the VPN interface
qBittorrent had no network interface configured, so libtorrent listened and announced on every interface of the shared pod network namespace - including eth0 (the pod IP) and loopback. gluetun's kill switch drops exactly those packets, so trackers answered with "skipping tracker announce (unreachable)" or timed out, no peers were found and torrents stalled at 0 bytes in stalledDL. Beyond the stall this is a leak: without the binding qBittorrent also listens on the pod IP, i.e. reachable past the tunnel. The setting only exists in the persisted config file, which qBittorrent creates itself on first start, so an init container seeds it before the app comes up. The edit is idempotent and preserves existing user settings: it replaces the keys if present, inserts them into the [BitTorrent] section if missing, and creates file or section if needed. The interface is configurable via qbittorrent.networkInterface (default tun0); emptying it drops the init container. |
||
|
|
5e2d8e4083
|
Allow writable root filesystem for the linuxserver.io containers
With readOnlyRootFilesystem the s6-overlay init cannot rewrite /etc/passwd, so the containers silently kept the image's built-in uid/gid 911 instead of the configured PUID/PGID (verified at runtime) - breaking ownership on the shared media volumes. Drop the now-unneeded /run and /tmp emptyDirs; deviation documented in values.yaml. |
||
|
|
0a5035ae83
|
Clean stale VPN state synchronously before gluetun starts
Replace the upstream-recommended postStart hook, which runs concurrently with the entrypoint and loses the race (observed: it deleted the ip rule the restarted gluetun had just created, leaving the container in a permanent CrashLoop behind the kill switch). A command wrapper now removes stale table-51820 rules and the leftover tun device deterministically before exec'ing /gluetun-entrypoint, covering container restarts after unclean exits. |
||
|
|
818a71f7b6
|
Clean up stale ip rules on gluetun container start
An uncleanly exited gluetun leaves its ip rule in the shared pod network namespace; the restarted container then fails forever with 'adding ip rule 101: file exists' while the kill switch keeps blocking all egress. Apply the upstream-recommended Kubernetes fix: a postStart hook dropping table-51820 rules. |
||
|
|
2211226c8e
|
Allow writable root filesystem for flaresolverr
The image copies and patches the chromedriver binary to the hardcoded path /app/chromedriver at startup (no env override exists) and writes chromedriver caches under /app, so readOnlyRootFilesystem cannot hold; deviation documented in values.yaml. Reverts the HOME=/tmp workaround, which only covered the cache path, not the binary patching. |
||
|
|
bce2b147a8
|
Set HOME=/tmp for flaresolverr
The image's home is /app on the read-only root filesystem, but flaresolverr writes chromedriver caches under $HOME (/app/.local); pointing HOME at the /tmp emptyDir keeps the root filesystem read-only. |
||
|
|
914c2d0205
|
Add freesailarr chart: gluetun-tunneled media automation stack
Single-pod StatefulSet running gluetun (appVersion 3.41.1) as Wireguard VPN gateway with qbittorrent, prowlarr, flaresolverr, radarr, recyclarr, sonarr, seerr and bazarr sharing its network namespace. Config volumes via volumeClaimTemplates, media libraries as existing claims, per-app enable/ingress toggles, computed FIREWALL_INPUT_PORTS, hardened security contexts with documented per-image deviations. Wires up publish workflow, change-app-version choice, install-test exclusion and README overview. |