Files
helm-charts/renovate.json5
T
jklein 46942db53a searxng: new chart (1.0.0+up2026.8.22.9fea41204)
SearXNG as a stateless Deployment plus a settings ConfigMap, with the four CI
steps a new chart needs: publish workflow, change-app-version choice list,
ci/searxng/ fixtures and a renovate.json5 block.

Three points where the chart is not a cyberchef with another name:

* The image entrypoint writes settings.yml into /etc/searxng only when the
  file is missing. The chart always mounts one by subPath, so the copy and
  the random-secret sed never run and the container needs no write access
  there - which is what allows readOnlyRootFilesystem: true.
* The signing key is a required OnePasswordItem, not a ConfigMap key. It
  signs the settings cookie and the image-proxy URLs, and the entrypoint's
  self-generating fallback cannot fire here, so without it the instance would
  run on the literal "ultrasecretkey" - which webapp.py refuses with exit 1.
* All three probes use /healthz, the only path the bot detection lets through
  unconditionally. Measured: with the limiter active, "/" answers 429 while
  /healthz still answers 200.

ingress.domain is read a second time outside the Ingress template, for
SEARXNG_BASE_URL, and carries its own required() there - so it stays
mandatory with the Ingress switched off.

The Renovate versioning regex deviates from the plan on the issue and matches
the commit hash WITHOUT capturing it. Renovate parses a "build" group with
Number.parseInt, so a hash becomes the digits leading it; measured on
renovate 41.173.1 and 44.48.0, the planned form reports an image pushed
earlier the same day as an update to a newer one. Reasoning in the block and
in README.
2026-08-27 21:09:59 +02:00

396 lines
18 KiB
JSON

{
$schema: "https://docs.renovatebot.com/renovate-schema.json",
// JSON5 on purpose: the per-chart managers below only make sense with the
// reasoning next to them. Renovate reads renovate.json5 natively — there must
// be no second config file (renovate.json, .github/renovate.json, ...) or
// Renovate aborts with "found multiple config files".
//
// Requires Renovate >= 41 (`managerFilePatterns`, `customType`). See
// README.md → "Dependency updates (Renovate)" for the whole picture,
// including the one-time `allowedCommands` change the self-hosted instance
// needs for the version bump to work.
extends: [
"config:recommended",
// Commit subjects in this repo are plain sentences, not conventional
// commits. Renovate's own merged PRs already look like that.
":semanticCommitsDisabled",
],
timezone: "Europe/Berlin",
labels: ["dependencies"],
// 21 charts plus the workflows: without a cap the first run after a quiet
// week opens a wall of PRs, and every chart PR spins up a k3d install-test.
prConcurrentLimit: 10,
prHourlyLimit: 4,
// template-chart is the scaffold. Its image is the literal placeholder
// ghcr.io/ORG/IMAGE and its appVersion is "xxx" — nothing to update, and the
// chart is never published.
ignorePaths: ["template-chart/**"],
// CLAUDE.md/README require app major jumps to be taken stepwise: first the
// last release of the old major line, then the new major. With this flag a
// v1 -> v3 jump arrives as two PRs (latest v2, then v3), which is exactly
// that sequence — merge the first, and the second rebases onto it.
separateMultipleMajor: true,
// Any change inside a chart directory must bump `version:` in its
// Chart.yaml, otherwise the `build-chart` job fails the PR and the publish
// on main would collide with an already published version. Renovate only
// rewrites `appVersion:` (or an image tag in values.yaml), so this hook
// recomputes `version:` afterwards.
//
// IMPORTANT: postUpgradeTasks only runs on self-hosted Renovate and only if
// the command is allow-listed in the *instance* config (not in this repo):
//
// allowedCommands: ["^ci/renovate-bump-chart\\.sh"]
//
// Without that entry Renovate skips the task silently and every chart PR
// stays red on the version-bump check.
postUpgradeTasks: {
commands: ["ci/renovate-bump-chart.sh {{{packageFileDir}}}"],
fileFilters: ["**/Chart.yaml"],
// "update" runs the script once per dependency update with the package
// file's directory, which is what the script needs to find the chart. The
// script derives everything from the base commit, so repeated runs on the
// same branch are idempotent.
executionMode: "update",
},
packageRules: [
{
description: [
"Install-test fixtures deliberately pin throwaway infrastructure to major lines",
"(postgres:16-alpine, redis:7-alpine, busybox:1.36). They are test scaffolding, not",
"shipped artifacts, and churning them only risks breaking the install-test.",
],
matchFileNames: ["ci/**"],
enabled: false,
},
{
description: [
"Patch, minor and digest updates merge themselves once the PR pipeline is green.",
"Minor is included because the sidecar images barely move in patch steps —",
"alpine 3.22 -> 3.24 and nginx 1.30.4 -> 1.31.4 are both minor, so a patch-only",
"rule would practically never fire. Majors stay manual.",
],
matchUpdateTypes: ["patch", "minor", "digest"],
automerge: true,
automergeType: "pr",
// Renovate waits for the checks itself instead of handing the PR to
// GitHub's auto-merge. GitHub would merge immediately when the branch has
// no required status checks configured — and validate-charts is not (yet)
// a required check.
platformAutomerge: false,
// A release that is hours old should not walk into main unattended; a
// chart publish follows the merge straight away.
minimumReleaseAge: "3 days",
},
{
description: [
"Chart app versions. Kept out of the automerge grouping decisions above only by label,",
"so it is visible at a glance which PRs republish a chart.",
],
matchDepTypes: ["app-version"],
addLabels: ["app-version"],
},
{
description: [
"nginx numbers its release branches by minor: even is the stable branch, odd is",
"mainline. The Synapse delegation nginx belongs on stable, so only even minors are",
"allowed — that is what PR #139 (1.30.4 -> 1.31.4) would otherwise have done,",
"quietly moving the chart onto mainline. Patch updates inside the stable branch",
"(1.30.4 -> 1.30.5) still come through and still automerge.",
],
matchDatasources: ["docker"],
matchPackageNames: ["nginx", "library/nginx", "docker.io/library/nginx"],
// Even minor: any minor whose last digit is even, so 1.30.x passes and
// 1.31.x does not. The trailing (-.+)? is load-bearing: #84 commissioned
// a check on moving this sidecar to 1.30.4-alpine (outcome open, to be
// done alongside the seccompProfile PR), and with a variant tag
// like 1.30.4-alpine docker versioning only ever offers candidates
// carrying the same suffix. Without the optional suffix, every one of
// them would fail this filter and nginx would silently stop receiving
// updates - no error, no warning, green dashboard. Verified: with the
// anchored form and currentValue 1.28.0-alpine, the lookup returns
// "updates": [] and "warnings": [].
allowedVersions: "/^\\d+\\.\\d*[02468]\\.\\d+(-.+)?$/",
},
{
description: [
"linuxserver/qbittorrent carries two historic tags from earlier tagging schemes,",
"14.3.9 and 20.04.1, which docker versioning sorts above every real release and",
"offers as major updates — PRs #152 and #153 are exactly those. Both are excluded",
"by name.",
],
matchDatasources: ["docker"],
matchPackageNames: [
"linuxserver/qbittorrent",
"docker.io/linuxserver/qbittorrent",
],
// Excluded by name rather than by a bound such as "<10": the strays are
// finished artifacts that cannot grow, so naming them can never block a
// release that does not exist yet, while any upper bound would sooner or
// later reject a genuine major. Shape-based filters were measured and
// rejected too: requiring a non-padded minor removes 20.04.1 and leaves
// 14.3.9 (verified, the lookup then offers 14.3.9 instead).
//
// Verified with renovate 44.41.0 (dry-run=lookup, real registry), two
// fixtures against the live tag list:
// currentValue 5.2.1 -> updates [5.2.3 (patch)]; both strays gone.
// currentValue 4.6.4 -> updates [4.6.7 (patch), 5.2.3 (major)].
// The second fixture is the guard the nginx rule taught us to run: it
// proves a genuine major still comes through instead of the filter
// silently emptying the update list. It also shows what the strays cost
// today — without the rule that same lookup offers 20.04.1 as the major
// candidate and never mentions the real 5.2.3, because Renovate fills
// the major bucket once.
allowedVersions: "!/^(14\\.3\\.9|20\\.04\\.1)$/",
},
],
customManagers: [
{
description: [
"Tool versions pinned as workflow env vars (TRIVY_VERSION). Annotate the line with",
"`# renovate: datasource=<ds> depName=<dep>` and Renovate keeps it current.",
],
customType: "regex",
managerFilePatterns: [".github/workflows/*.yml"],
matchStrings: [
"# renovate: datasource=(?<datasource>\\S+) depName=(?<depName>\\S+)(?: versioning=(?<versioning>\\S+))?\\s+\\w+: [\"']?(?<currentValue>[^\"'\\s]+)",
],
versioningTemplate: "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}",
},
// --- Chart app versions -------------------------------------------------
//
// Every chart renders its main image as `<repo>:{{ .Chart.AppVersion }}`,
// so the repository lives in the template and the version in Chart.yaml.
// Renovate cannot join the two across files, which is why each chart gets
// its own manager that hard-codes the repository and reads `appVersion:`.
//
// Deliberately *not* solved with a `# renovate:` comment inside each
// Chart.yaml: that would touch all 21 chart directories and force 21
// version bumps plus 21 publishes for a pure comment change. Keeping the
// mapping here leaves the charts untouched.
//
// When adding a chart, add a block here — see README "Creating a new chart".
//
// Exception, deliberately no block: `freesailarr`. It bundles nine
// independently versioned upstreams, so none of them is "the" app version
// and no image tag comes from appVersion — all nine are pinned as
// image.repository/image.tag in its values.yaml, where the built-in
// helm-values manager already finds them and proposes one update per
// image. Adding a block here would either duplicate those updates or
// rewrite an appVersion that selects nothing. Its appVersion is a
// hand-maintained marker; see freesailarr/Chart.yaml.
{
description: "apache-tika: four-part version plus flavour suffix (3.3.1.0-full); the compatibility group keeps the chart on -full.",
customType: "regex",
managerFilePatterns: ["apache-tika/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "apache/tika",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
versioningTemplate: "regex:^(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)\\.(?<build>\\d+)-(?<compatibility>full)$",
},
{
customType: "regex",
managerFilePatterns: ["calibre-web-automated/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "ghcr.io/crocodilestick/calibre-web-automated",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
{
customType: "regex",
managerFilePatterns: ["cyberchef/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "ghcr.io/gchq/cyberchef",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
{
customType: "regex",
managerFilePatterns: ["gotenberg/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "gotenberg/gotenberg",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
{
description: "homeassistant: CalVer tags (2026.8.3) sort correctly under docker versioning; the floating stable/beta/dev tags are skipped because they parse as no version at all.",
customType: "regex",
managerFilePatterns: ["homeassistant/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "ghcr.io/home-assistant/home-assistant",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
{
customType: "regex",
managerFilePatterns: ["matrix-synapse/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "ghcr.io/element-hq/synapse",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
{
customType: "regex",
managerFilePatterns: ["minecraft-router/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "itzg/mc-router",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
{
description: "minecraft-server: tags encode the JRE as a suffix (2026.8.2-java21); the compatibility group prevents a silent runtime switch.",
customType: "regex",
managerFilePatterns: ["minecraft-server/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "itzg/minecraft-server",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
versioningTemplate: "regex:^(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)-(?<compatibility>java\\d+)$",
},
{
customType: "regex",
managerFilePatterns: ["outline/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "outlinewiki/outline",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
{
customType: "regex",
managerFilePatterns: ["paperless-ngx/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "ghcr.io/paperless-ngx/paperless-ngx",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
{
customType: "regex",
managerFilePatterns: ["patchmon/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "ghcr.io/patchmon/patchmon-server",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
{
customType: "regex",
managerFilePatterns: ["rallly/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "lukevella/rallly",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
{
description: "rss-bridge: date-based tags (2025-06-03). The bump script normalizes the dashes to dots for the +up suffix, as the versioning scheme requires.",
customType: "regex",
managerFilePatterns: ["rss-bridge/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "docker.io/rssbridge/rss-bridge",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
versioningTemplate: "regex:^(?<major>\\d{4})-(?<minor>\\d{2})-(?<patch>\\d{2})$",
},
{
description: [
"searxng: every tag is <date>-<commit> (2026.8.22-9fea41204) - no semver line at all,",
"and 'latest' moves daily. The date carries the ordering; the commit hash is matched",
"but deliberately NOT captured.",
],
customType: "regex",
managerFilePatterns: ["searxng/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "docker.io/searxng/searxng",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
// The hash sits outside every capture group on purpose, and that is the
// whole point of this line. Renovate's regex versioning reads a "build"
// group NUMERICALLY - regex/index.js _parse() does
// release.push(Number.parseInt(build)) - so a commit hash becomes
// whatever digits happen to lead it: parseInt("9fea41204") is 9,
// parseInt("777ba8fa4") is 777, and any hash starting with a letter
// (6 of the 25 tags live today) is NaN.
//
// Verified against the real API on renovate 41.173.1 and 44.48.0, with
// currentValue 2026.8.22-9fea41204:
// with (?<build>[0-9a-f]+):
// isGreaterThan("2026.8.22-777ba8fa4", current) -> true
// without it (this line):
// isGreaterThan("2026.8.22-777ba8fa4", current) -> false, equals -> true
// 777ba8fa4 was pushed at 03:06 and 9fea41204 at 05:38 the SAME day, so
// the build group makes Renovate offer the older image as an update -
// a downgrade, unattended, because date updates land in the patch/minor
// automerge rule. With NaN the pair is neither equal nor ordered at all.
//
// Uncaptured, the hash still has to be THERE (the pattern is anchored),
// it just takes no part in the comparison: two builds of one day are
// equal, so no sideways update is proposed, and a newer date wins
// whatever the hash looks like.
//
// "prerelease" instead of "build" was measured too and rejected: it
// sorts correctly but marks every single tag isStable === false, which
// hands the whole chart to ignoreUnstable.
versioningTemplate: "regex:^(?<major>\\d{4})\\.(?<minor>\\d{1,2})\\.(?<patch>\\d{1,2})-[0-9a-f]+$",
},
{
customType: "regex",
managerFilePatterns: ["samba/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "ghcr.io/dockur/samba",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
{
customType: "regex",
managerFilePatterns: ["satisfactory/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "ghcr.io/wolveix/satisfactory-server",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
{
description: "teamspeak-server: the 6.0.0 line only ships beta tags (6.0.0-beta12.1). Loose versioning compares the numeric parts left to right, so beta12.1 sorts above beta9; semver would rank every beta as an equal prerelease.",
customType: "regex",
managerFilePatterns: ["teamspeak-server/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "teamspeaksystems/teamspeak6-server",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
versioningTemplate: "loose",
},
{
customType: "regex",
managerFilePatterns: ["urlaubsverwaltung/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "urlaubsverwaltung/urlaubsverwaltung",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
{
customType: "regex",
managerFilePatterns: ["voucher-vault/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "l4rm4nd/vouchervault",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
{
customType: "regex",
managerFilePatterns: ["zipline/Chart.yaml"],
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
depNameTemplate: "ghcr.io/diced/zipline",
datasourceTemplate: "docker",
depTypeTemplate: "app-version",
},
],
}