Compare commits

...

35 Commits

Author SHA1 Message Date
admin 4e86091f7d updates scripts 2026-06-07 12:59:09 +02:00
admin 754564167f updated probe-loop.sh 2026-06-07 12:50:41 +02:00
admin 40f5532570 added memoty to grafana 2026-06-07 12:01:19 +02:00
admin 0a2efb86ac fixed image 2026-06-07 11:52:28 +02:00
admin b40090dec1 fixed dns 2026-06-07 11:51:35 +02:00
admin 2370f005c6 added fallback_scrape_protocol 2026-06-07 11:32:54 +02:00
admin 05fa40ff5d prom targets for wan 2026-06-07 11:24:29 +02:00
admin ef77ab9285 updated wan rules 2026-06-07 11:20:30 +02:00
admin e0fd669f7c fix 2026-06-07 11:15:44 +02:00
admin 877cda7be1 updated configmap 2026-06-07 11:14:48 +02:00
admin 0887848d29 changed linebreak 2026-06-07 11:12:23 +02:00
admin 565c4c8bd0 fixed repo, added prometheus rules 2026-06-07 11:00:31 +02:00
admin 998cd150a1 added wan-monitor 2026-06-07 10:34:41 +02:00
admin 1a1cded065 outlint 1.8.1 2026-06-06 15:44:24 +02:00
admin a66cef8a9e Merge pull request 'feat: migrate seerr from fallenbagel/jellyseerr:preview-OIDC -> seerr-team/seerr:v3.3.0' (#88) from feat/seerr-migrate-to-seerr-team into main 2026-06-06 13:37:22 +00:00
admin d67ec2af65 seerr: migrate fallenbagel/jellyseerr:preview-OIDC -> ghcr.io/seerr-team/seerr:v3.3.0
Switching from the third-party OIDC-capable jellyseerr fork to the
mainline successor project (Seerr - the combined Overseerr+Jellyseerr
team rebrand, v3.0.0 / Feb 2026 onward). Mainline now has native OIDC
support so the custom preview-OIDC build isn't needed.

  - Image    : docker.io/fallenbagel/jellyseerr:preview-OIDC
              -> ghcr.io/seerr-team/seerr:v3.3.0 (Jun 2, 2026)
  - Migration: automatic on first start per docs.seerr.dev/migration-guide;
              existing sqlite db + settings.json in /app/config are
              directly compatible. v3.1.x added CVE-2026-40175 fix +
              auth-related security patches, so v3.3.0 is the right
              floor anyway.
  - Backup   : ~/seerr-backups/seerr-config-20260606-153633.tar.gz on
              dooplex (covers db.sqlite3 + settings.json + logs).
              Rollback = revert image + restore tarball into the PVC.

Worth verifying after rollout:
  - Pod becomes Ready (readiness probe path /api/v1/status -- should
    still exist in seerr).
  - Authentik OIDC sign-in still works. If the custom build used
    different config keys than mainline seerr expects, OIDC may need
    re-configuration in the seerr UI (Authentik side unchanged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 15:37:21 +02:00
admin 6592bfe309 Merge pull request 'feat: sparkyfitness v0.16.6.3 -> v0.16.8 + handle versioning scheme change' (#87) from feat/sparkyfitness-v0.16.8 into main 2026-06-06 13:24:40 +00:00
admin 23b66875e4 sparkyfitness: bump to v0.16.8 + accept both 3- and 4-segment tags
codewithcj changed sparkyfitness versioning on 2026-06-01:
  - Old (through v0.16.6.3 / 2026-05-24): vMAJOR.MINOR.PATCH.BUILD
  - New (from v0.16.7 / 2026-06-01)      : vMAJOR.MINOR.PATCH

Our version-checker regex was `^v\d+\.\d+\.\d+\.\d+$` (4 segments
only), so the new v0.16.7 / v0.16.8 tags were invisible to it. The
"newest matching" became an arbitrarily-chosen old 4-segment tag
(v0.16.5.9 in the latest scan), which then showed up as an "upgrade
to an older version" -- nonsense, but predictable given the filter.

Two changes:
  1. Bump both `codewithcj/sparkyfitness` (frontend) and
     `codewithcj/sparkyfitness_server` (backend) from v0.16.6.3 to
     v0.16.8 (the actual upstream latest).
  2. Loosen the regex to `^v\d+\.\d+\.\d+(\.\d+)?$` so it matches
     both the legacy 4-segment form and the new 3-segment form.
     Once everything's on 3-segment we can tighten it again if we
     want, but the current form is harmless.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 15:24:39 +02:00
admin 80750369da Merge pull request 'fix: remove orphan } in renovate config (broke PR #85)' (#86) from fix/renovate-json-fix into main 2026-06-06 13:11:53 +00:00
admin f189a742cd renovate: remove orphan } left by previous cleanup PR (#85)
The previous PR deleted the umami packageRule but left a stray closing
brace after it, which broke the embedded config.json. ArgoCD applied
the manifest as a string (it's a ConfigMap; k8s doesn't validate the
JSON inside data), so the live ConfigMap also has the invalid JSON --
next Renovate run would fail to parse the config.

Removing the orphan brace restores valid JSON. Verified `json.loads`
parses to 3 customManagers + 7 packageRules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 15:11:52 +02:00
admin c973d491ce Merge pull request 'cleanup: drop felhom-system stale copies + fix plex datasource + drop obsolete umami config' (#85) from cleanup/felhom-stale-plex-renovate into main 2026-06-06 13:05:00 +00:00
admin ee93b504fa cleanup: drop stale felhom-system copies + fix plex datasource + remove obsolete umami config
Three coordinated changes, all surfaced by the question "will Renovate
track the manually-bumped packages going forward":

1) Delete `felhom-system/` directory (4 files).
   These were never the source of truth -- the `felhom` ArgoCD app
   pulls from `felhom.eu`, path `manifests`. The copies in this repo
   fell out of sync over time and were misleading. Renovate was about
   to start opening DEAD PRs against them (the customManager below
   targeted `felhom-system/umami.yaml`). Removing the directory is the
   cleanest fix; manual bumps for the real felhom-system manifests go
   into the felhom.eu repo.

2) Fix plex inline `# renovate:` comment in helm/plex/values.yaml.
   It referenced `datasource=custom.plex` but no such customDatasource
   exists in the config -- Renovate would silently skip plex. Switched
   to the standard docker datasource with regex versioning that parses
   `1.X.Y.Z-<hash>` (4 segments + git short-hash suffix, same pattern
   approach as servarr and termix).

3) Remove the now-obsolete umami customManager + packageRule.
   The customManager was for the `postgresql-vX.Y.Z` tag form we've
   abandoned -- the real felhom.eu deployment is on `3.1.0` (plain
   semver). The packageRule disabled the kubernetes manager for the
   umami image to silence its failure on `postgresql-vX.Y.Z`; not
   needed since the default versioning handles `3.X.Y` fine. (Moot
   anyway since Renovate doesn't watch felhom.eu -- but cleanup
   reduces config noise.)

After this PR, Renovate's effective tracking:
  - servarr (sonarr/radarr/prowlarr)      -> YES (customManager)
  - plex                                   -> YES (inline comment, docker)
  - termix                                 -> YES (customManager)
  - umami / filebrowser in felhom.eu      -> NO (different repo, manual)
  - all standard semver/named tags in homelab-manifests -> YES (defaults)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 15:04:59 +02:00
admin 6caf521c1f Merge pull request 'feat: bump servarr (sonarr/radarr/prowlarr) + plex image tags' (#84) from feat/servarr-plex-bumps into main 2026-06-06 12:16:25 +00:00
admin 0f2ff3fa52 servarr + plex: bump image tags
- sonarr     version-4.0.16.2944 -> version-4.0.17.2952  (patch within 4.0.x)
  - radarr×2   version-6.0.4.10291 -> version-6.1.1.10360  (minor within 6.x)
  - prowlarr   version-2.3.0.5236  -> version-2.3.5.5327   (patch within 2.3.x)
  - plex       1.43.0.10467-...    -> 1.43.2.10687-...     (patch within 1.43.x)

All four were stuck because of tag-format issues that I addressed in
PR #82 (servarr customManager) / PR #83. Renovate isn't auto-creating
the PRs yet (DH rate-limit), so doing them manually so version-checker
clears.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 14:16:24 +02:00
admin 6f49a21b3d Merge pull request 'fix: re-pin moving tags (filebrowser/umami/recipes) so Renovate can track them' (#83) from fix/moving-tag-repins into main 2026-06-06 11:35:13 +00:00
admin d92d2c31a6 re-pin moving tags so Renovate can track them
Renovate can't propose updates for moving tags (the tag string never
changes; the registry just points it at a different image). These three
were pinned to moving variants:

  felhom-system/webpage.yaml : filebrowser/filebrowser:v2-alpine
  felhom-system/umami.yaml   : ghcr.io/umami-software/umami:postgresql-latest
  tandoor-system/tandoor.yaml: vabene1111/recipes:2.6

Pin each to the current actual version per Viktor's call:
  - filebrowser -> v2.63.13 (matches the other 4 filebrowser pinnings
    in the repo; dropped the `-alpine` variant so Renovate can group
    them via the existing default datasource path)
  - umami       -> postgresql-v1.38.0 (current upstream postgresql
    variant latest; tracked via new customManager below)
  - recipes     -> 2.6.9 (current actual semver of the 2.6 series)

For umami, the `postgresql-vX.Y.Z` tag pattern is rejected by Renovate's
default docker versioning pre-check (same failure class as termix +
linuxserver servarr). Added a customManager regex + packageRule disable
pair so Renovate can track future `postgresql-vX.Y.Z` updates via regex
versioning. filebrowser and recipes use standard semver `X.Y.Z` after
the re-pin and need no special handling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 13:35:12 +02:00
admin 6ca0a7b051 Merge pull request 'fix: version tracking tuning — annotations + linuxserver customManager' (#82) from fix/version-tracking-tuning into main 2026-06-06 11:25:51 +00:00
admin 1d08156d81 version tracking: tune annotations + add customManagers for linuxserver servarr
Several images were showing as outdated in version-checker / unhandled by
Renovate. Each had a distinct cause; this PR fixes the auto-tractable ones.

1) admin-system/renovate.yaml: bump `app.kubernetes.io/version` labels
   `43.197.0 -> 43.209.3` (3 occurrences) to match the live image.
   Renovate's own self-update PR bumped the image tag but left the
   labels stale; the version-checker widget appears to read the label.
   Long-term, this label will drift again on each self-update -- worth
   a customManager later if it becomes a recurring annoyance.

2) admin-system/renovate.yaml: add a customManager + packageRule pair
   for linuxserver servarr apps. Tag pattern is `version-X.Y.Z.B`
   (4 segments + `version-` prefix) which the kubernetes manager's
   default docker versioning rejects at the pre-check, same failure
   class as termix. Regex versioning parses the prefixed 4-segment
   form; the same customManager handles prowlarr/radarr/sonarr (depName
   captured from the regex). kubernetes-manager extraction for these
   three depnames is disabled via packageRule so the dashboard isn't
   cluttered with the failing fallback.

3) nextcloud-system/nextcloud.yaml: add
   `match-regex.version-checker.io/nextcloud: '^\d+\.\d+\.\d+-apache$'`
   so version-checker doesn't treat the bare `33.0.5` server tag as a
   newer version of our `33.0.5-apache` image. The widget was showing
   `33.0.5-apache -> 33.0.5` -- false positive; image is already current.

4) helm/plex/values.yaml: tighten the version-checker regex from
   `^\d+\.\d+\.\d+\.\d+-.*$` to `^\d+\.\d+\.\d+\.\d+-[a-f0-9]+$` so
   per-arch tags (`-armhf`, `-arm64`, ...) are excluded. The widget
   was showing an `-armhf` tag as "newer" than our x86_64 install.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 13:25:49 +02:00
admin a8c657d554 Merge pull request 'pihole: bump image to 2026.05.0 (dnsmasq CVE security release)' (#81) from feat/pihole-2026.05.0 into main 2026-06-06 10:56:00 +00:00
admin 9e020af94d pihole: bump image to 2026.05.0 (dnsmasq CVE security release)
Pi-hole 2026.05.0 bundles FTL v6.6.2 which imports six upstream dnsmasq
security fixes, covering all publicly disclosed CVEs against the
dnsmasq 2.92/2.93 line. Per the upstream release notes the fixes are
"minimal, self-contained changes to the embedded dnsmasq sources. No
FTL-side configuration or API changes; users should see no observable
behavior change beyond the closed vulnerabilities."

Override the chart's default image.tag in helm/pihole/values.yaml (no
chart version bump). The pihole ArgoCD app is intentionally MANUAL
sync per Viktor's call -- after merge, sync the pihole app from the
ArgoCD UI to roll the pod over.

https://github.com/pi-hole/docker-pi-hole/releases/tag/2026.05.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 12:55:58 +02:00
admin ec9ae43bee Merge pull request 'termix: manual bump 1.11.2 -> 2.3.2 (Renovate blocked by DH rate-limit)' (#80) from feat/termix-v2.3.2 into main 2026-06-06 09:16:02 +00:00
admin e822b76982 termix: manual bump 1.11.2 -> 2.3.2 (Renovate blocked by DH rate-limit)
Renovate's `Pending Approval` checkbox for the termix v2 major was ticked
on Dashboard #6, but the manual Renovate runs that should have processed
it both aborted on Docker Hub's authenticated rate-limit:

  HTTP 429: You have reached your pull rate limit as 'kisfenyo'

The free DH plan caps authenticated pulls at 100/6h; with ~270 deps in
this repo and the multiple runs we've done today, we've exhausted it.
Renovate's behavior on a host 429 is to abort the entire repository run
(`result: external-host-error`), so no further work — including ticked
dashboard approvals — gets done until the quota window resets.

Rather than wait ~3-4 hours, this PR does the bump by hand. Upstream
ghcr.io/lukegus/termix:release-2.3.2 is verified present (Termix-SSH
GitHub Release of 2026-06-04). Termix is stateless (host/cred config
stored in PocketBase but compatible across release-1 and release-2),
so the rollout should be a straightforward image swap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 11:16:00 +02:00
admin 876b044d0a Merge pull request 'fix: roll back immich-postgres v17 -> v16 (PG major incompat) + gate postgres-family' (#79) from fix/immich-postgres-rollback into main 2026-06-06 09:00:33 +00:00
admin 99bbc31325 Merge pull request 'Update docker.io/library/nextcloud Docker tag to v33.0.5' (#78) from renovate/docker.io-library-nextcloud-33.x into main 2026-06-06 09:00:24 +00:00
Renovate Bot fee5fafeb0 Update docker.io/library/nextcloud Docker tag to v33.0.5
renovate/stability-days Updates have not met minimum release age requirement
2026-06-06 09:00:21 +00:00
16 changed files with 582 additions and 935 deletions
+24 -3
View File
@@ -62,6 +62,17 @@ data:
"packageNameTemplate": "Termix-SSH/Termix", "packageNameTemplate": "Termix-SSH/Termix",
"versioningTemplate": "regex:^release-(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)$", "versioningTemplate": "regex:^release-(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)$",
"extractVersionTemplate": "^(?<version>release-\\d+\\.\\d+\\.\\d+)" "extractVersionTemplate": "^(?<version>release-\\d+\\.\\d+\\.\\d+)"
},
{
"description": "linuxserver servarr apps (prowlarr, radarr, sonarr) use tag pattern `version-X.Y.Z.B` (4 segments + `version-` prefix). The kubernetes manager's default docker versioning rejects them at the pre-check (same failure class as termix), so no PRs ever open. Use regex versioning to parse the prefixed 4-segment form; depName is captured from the regex so the same customManager handles all three apps.",
"customType": "regex",
"managerFilePatterns": ["/servarr-system/.+\\.ya?ml$/"],
"matchStrings": [
"image:\\s+linuxserver/(?<depName>prowlarr|radarr|sonarr):(?<currentValue>version-\\d+\\.\\d+\\.\\d+\\.\\d+)"
],
"datasourceTemplate": "docker",
"packageNameTemplate": "linuxserver/{{depName}}",
"versioningTemplate": "regex:^version-(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)\\.(?<build>\\d+)$"
} }
], ],
"packageRules": [ "packageRules": [
@@ -128,6 +139,16 @@ data:
"matchManagers": ["kubernetes"], "matchManagers": ["kubernetes"],
"matchPackageNames": ["ghcr.io/lukegus/termix"], "matchPackageNames": ["ghcr.io/lukegus/termix"],
"enabled": false "enabled": false
},
{
"description": "linuxserver servarr apps: same disable pattern as termix. The customManager above handles extraction with the right versioning; turn off the default kubernetes-manager extraction so it doesn't silently skip + clutter the dashboard.",
"matchManagers": ["kubernetes"],
"matchPackageNames": [
"linuxserver/prowlarr",
"linuxserver/radarr",
"linuxserver/sonarr"
],
"enabled": false
} }
], ],
"labels": ["renovate"] "labels": ["renovate"]
@@ -141,7 +162,7 @@ metadata:
labels: labels:
app.kubernetes.io/instance: renovate app.kubernetes.io/instance: renovate
app.kubernetes.io/name: renovate app.kubernetes.io/name: renovate
app.kubernetes.io/version: "43.197.0" app.kubernetes.io/version: "43.209.3"
spec: spec:
# Sat 02:00 Europe/Budapest — leaves the full weekend for troubleshooting # Sat 02:00 Europe/Budapest — leaves the full weekend for troubleshooting
# if a Renovate-merged update breaks something. # if a Renovate-merged update breaks something.
@@ -156,14 +177,14 @@ spec:
labels: labels:
app.kubernetes.io/instance: renovate app.kubernetes.io/instance: renovate
app.kubernetes.io/name: renovate app.kubernetes.io/name: renovate
app.kubernetes.io/version: "43.197.0" app.kubernetes.io/version: "43.209.3"
spec: spec:
template: template:
metadata: metadata:
labels: labels:
app.kubernetes.io/instance: renovate app.kubernetes.io/instance: renovate
app.kubernetes.io/name: renovate app.kubernetes.io/name: renovate
app.kubernetes.io/version: "43.197.0" app.kubernetes.io/version: "43.209.3"
annotations: annotations:
# Renovate uses plain X.Y.Z semver tags (no -slim suffix anymore) # Renovate uses plain X.Y.Z semver tags (no -slim suffix anymore)
match-regex.version-checker.io/renovate: '^\d+\.\d+\.\d+$' match-regex.version-checker.io/renovate: '^\d+\.\d+\.\d+$'
+344
View File
@@ -0,0 +1,344 @@
---
# ============================================================================
# wan-monitor — internet connection quality monitoring
# Single pod (3 containers) in admin-system:
# - blackbox : prometheus blackbox-exporter (HTTP phases, ICMP, DNS) :9115
# - wan-probe : irtt (UDP quality) + iperf3 (throughput) loop -> /shared
# - metrics-http : busybox httpd serving /shared/metrics :9116
# Prometheus scrapes :9115 (blackbox relabel jobs) and :9116 (textfile metrics).
# Scrape jobs live in prometheus-wan-scrape-jobs.yaml (merge into monitoring.yaml).
# ============================================================================
apiVersion: v1
kind: ConfigMap
metadata:
name: wan-monitor-blackbox
namespace: admin-system
labels:
app: wan-monitor
data:
blackbox.yml: |
modules:
http_2xx:
prober: http
timeout: 10s
http:
preferred_ip_protocol: ip4
ip_protocol_fallback: false
method: GET
fail_if_not_ssl: false
icmp:
prober: icmp
timeout: 5s
icmp:
preferred_ip_protocol: ip4
ip_protocol_fallback: false
dns_udp:
prober: dns
timeout: 5s
dns:
transport_protocol: udp
preferred_ip_protocol: ip4
query_name: "telex.hu"
query_type: "A"
---
apiVersion: v1
kind: ConfigMap
metadata:
name: wan-monitor-scripts
namespace: admin-system
labels:
app: wan-monitor
data:
metrics-header.prom: |
# HELP wan_irtt_rtt_seconds irtt round-trip time by statistic (seconds)
# TYPE wan_irtt_rtt_seconds gauge
# HELP wan_irtt_jitter_seconds irtt IPDV jitter mean by direction (seconds)
# TYPE wan_irtt_jitter_seconds gauge
# HELP wan_irtt_loss_ratio irtt packet loss ratio by direction (0-1)
# TYPE wan_irtt_loss_ratio gauge
# HELP wan_irtt_late_ratio irtt late/reordered packet ratio (0-1)
# TYPE wan_irtt_late_ratio gauge
# HELP wan_irtt_duplicate_ratio irtt duplicate packet ratio (0-1)
# TYPE wan_irtt_duplicate_ratio gauge
# HELP wan_irtt_packets irtt packet counters for the run
# TYPE wan_irtt_packets gauge
# HELP wan_irtt_success 1 if the irtt run produced stats
# TYPE wan_irtt_success gauge
# HELP wan_throughput_bits_per_second achieved throughput (bits/sec)
# TYPE wan_throughput_bits_per_second gauge
# HELP wan_throughput_success 1 if the throughput test succeeded
# TYPE wan_throughput_success gauge
# HELP wan_probe_last_run_timestamp_seconds unix time of last probe run
# TYPE wan_probe_last_run_timestamp_seconds gauge
irtt_to_prom.py: |
#!/usr/bin/env python3
# irtt JSON (stdin) -> Prometheus sample lines (no HELP/TYPE; header is static).
# args: <condition> <target>
# Hardened: tolerates missing/null/NaN/Inf fields and clamps ratios to 0..1
# so a pathological irtt run can never emit an out-of-range or invalid sample.
import json, sys, time, math
cond = sys.argv[1] if len(sys.argv) > 1 else "idle"
target = sys.argv[2] if len(sys.argv) > 2 else "hetzner"
L = f'target="{target}",condition="{cond}"'
ts = f'{time.time():.0f}'
def num(x, default=0.0):
# finite float or default (handles None / str / missing / NaN / Inf)
try:
v = float(x)
except (TypeError, ValueError):
return default
return v if math.isfinite(v) else default
def pct_ratio(x):
# percent (0..100, possibly garbage) -> ratio clamped to 0..1
return max(0.0, min(1.0, num(x) / 100.0))
def fail():
print(f'wan_irtt_success{{{L}}} 0')
print(f'wan_probe_last_run_timestamp_seconds{{probe="irtt",{L}}} {ts}')
sys.exit(0)
try:
s = json.load(sys.stdin).get("stats")
except Exception:
fail()
if not isinstance(s, dict):
fail()
rtt = s.get("rtt") or {}
for k in ("min", "mean", "median", "max", "stddev"):
print(f'wan_irtt_rtt_seconds{{{L},stat="{k}"}} {num(rtt.get(k)) / 1e9}')
def ipdv(key):
d = s.get(key) or {}
return num(d.get("mean")) / 1e9
print(f'wan_irtt_jitter_seconds{{{L},direction="round_trip"}} {ipdv("ipdv_round_trip")}')
print(f'wan_irtt_jitter_seconds{{{L},direction="send"}} {ipdv("ipdv_send")}')
print(f'wan_irtt_jitter_seconds{{{L},direction="receive"}} {ipdv("ipdv_receive")}')
print(f'wan_irtt_loss_ratio{{{L},direction="round_trip"}} {pct_ratio(s.get("packet_loss_percent"))}')
print(f'wan_irtt_loss_ratio{{{L},direction="upstream"}} {pct_ratio(s.get("upstream_loss_percent"))}')
print(f'wan_irtt_loss_ratio{{{L},direction="downstream"}} {pct_ratio(s.get("downstream_loss_percent"))}')
print(f'wan_irtt_late_ratio{{{L}}} {pct_ratio(s.get("late_packets_percent"))}')
print(f'wan_irtt_duplicate_ratio{{{L}}} {pct_ratio(s.get("duplicate_percent"))}')
print(f'wan_irtt_packets{{{L},kind="sent"}} {int(num(s.get("packets_sent")))}')
print(f'wan_irtt_packets{{{L},kind="received"}} {int(num(s.get("packets_received")))}')
print(f'wan_irtt_packets{{{L},kind="server_received"}} {int(num(s.get("server_packets_received")))}')
print(f'wan_irtt_success{{{L}}} 1')
print(f'wan_probe_last_run_timestamp_seconds{{probe="irtt",{L}}} {ts}')
tput_to_prom.py: |
#!/usr/bin/env python3
# iperf3 JSON (stdin) -> Prometheus sample lines. args: <direction> <target>
import json, sys, time
direction = sys.argv[1] if len(sys.argv) > 1 else "download"
target = sys.argv[2] if len(sys.argv) > 2 else "hetzner"
L = f'target="{target}",direction="{direction}"'
ts = f'{time.time():.0f}'
try:
bps = json.load(sys.stdin)["end"]["sum_received"]["bits_per_second"]
print(f'wan_throughput_bits_per_second{{{L}}} {bps:.0f}')
print(f'wan_throughput_success{{{L}}} 1')
except Exception:
print(f'wan_throughput_success{{{L}}} 0')
print(f'wan_probe_last_run_timestamp_seconds{{probe="throughput",{L}}} {ts}')
probe-loop.sh: |
#!/bin/sh
set -u
SHARED=/shared
HDR=/scripts/metrics-header.prom
HETZNER="${HETZNER_HOST:?set HETZNER_HOST}"
IRTT_PORT="${IRTT_PORT:-2112}"
IPERF_PORT="${IPERF_PORT:-5201}"
IRTT_INTERVAL="${IRTT_INTERVAL:-20ms}"
IRTT_DURATION="${IRTT_DURATION:-60}" # seconds (numeric, for timeout math)
TPUT_EVERY="${TPUT_EVERY:-900}" # seconds between throughput tests
TPUT_TIME="${TPUT_TIME:-10}" # iperf3 seconds per direction
IRTT_TARGET="${IRTT_TARGET:-hetzner}"
TPUT_TARGET="${TPUT_TARGET:-hetzner}"
HMAC_OPT=""
[ -n "${IRTT_HMAC:-}" ] && HMAC_OPT="--hmac=${IRTT_HMAC}"
mkdir -p "$SHARED"
: > "$SHARED/.irtt.prom"; : > "$SHARED/.irttload.prom"; : > "$SHARED/.tput.prom"
cp "$HDR" "$SHARED/metrics" # serve header immediately so first scrapes don't 404
# Concatenate fragments into the served file via temp + atomic rename.
assemble() {
cat "$HDR" "$SHARED/.irtt.prom" "$SHARED/.irttload.prom" "$SHARED/.tput.prom" \
> "$SHARED/.metrics.tmp" 2>/dev/null
mv "$SHARED/.metrics.tmp" "$SHARED/metrics"
}
# Each fragment is written to <file>.tmp then renamed, so assemble() never
# cats a partially written file (the cause of the impossible loss spikes).
run_irtt() { # $1 condition $2 outfile $3 duration(seconds)
timeout "$(( $3 + 25 ))" irtt client -i "$IRTT_INTERVAL" -d "${3}s" -q $HMAC_OPT \
-o - "${HETZNER}:${IRTT_PORT}" 2>/dev/null \
| python3 /scripts/irtt_to_prom.py "$1" "$IRTT_TARGET" > "$2.tmp"
mv "$2.tmp" "$2"
}
run_tput() {
P="${IPERF_PARALLEL:-4}" # parallel streams: a single stream can't fill the pipe over the RTT
TO="$(( TPUT_TIME + 20 ))"
TMP="$SHARED/.tput.prom.partial"
: > "$TMP"
timeout "$TO" iperf3 -c "$HETZNER" -p "$IPERF_PORT" -t "$TPUT_TIME" -P "$P" --connect-timeout 5000 -R -J 2>/dev/null \
| python3 /scripts/tput_to_prom.py download "$TPUT_TARGET" > "$TMP"
timeout "$TO" iperf3 -c "$HETZNER" -p "$IPERF_PORT" -t "$TPUT_TIME" -P "$P" --connect-timeout 5000 -J 2>/dev/null \
| python3 /scripts/tput_to_prom.py upload "$TPUT_TARGET" >> "$TMP"
mv "$TMP" "$SHARED/.tput.prom"
}
last_tput=0
while true; do
run_irtt idle "$SHARED/.irtt.prom" "$IRTT_DURATION" # blocks ~IRTT_DURATION = loop cadence
assemble
now=$(date +%s)
if [ $(( now - last_tput )) -ge "$TPUT_EVERY" ]; then
LOAD_DUR=$(( 2 * TPUT_TIME + 4 ))
run_irtt under_load "$SHARED/.irttload.prom" "$LOAD_DUR" & # concurrent = bufferbloat
LOADPID=$!
run_tput
wait "$LOADPID" 2>/dev/null
last_tput="$now"
assemble
fi
done
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wan-monitor
namespace: admin-system
labels:
app: wan-monitor
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: wan-monitor
template:
metadata:
labels:
app: wan-monitor
annotations:
enable.version-checker.io/blackbox: "true"
enable.version-checker.io/metrics-http: "true"
enable.version-checker.io/wan-probe: "true"
match-regex.version-checker.io/blackbox: "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
match-regex.version-checker.io/metrics-http: "^[0-9]+\\.[0-9]+\\.[0-9]+$"
match-regex.version-checker.io/wan-probe: "^[0-9]+\\.[0-9]+\\.[0-9]+$"
spec:
enableServiceLinks: false
containers:
- name: blackbox
image: quay.io/prometheus/blackbox-exporter:v0.28.0
args:
- --config.file=/etc/blackbox/blackbox.yml
- --web.listen-address=:9115
ports:
- name: blackbox
containerPort: 9115
securityContext:
capabilities:
add: ["NET_RAW"] # required for the ICMP prober
resources:
requests: { cpu: 10m, memory: 32Mi }
limits: { memory: 64Mi }
volumeMounts:
- name: blackbox-config
mountPath: /etc/blackbox
readOnly: true
- name: wan-probe
# Build + push from Dockerfile.wan-probe (adjust registry/tag to taste)
image: gitea.dooplex.hu/admin/wan-probe:0.1.0
command: ["/bin/sh", "/scripts/probe-loop.sh"]
env:
- name: HETZNER_HOST
# MUST be the Hetzner origin: a DNS-only (grey-cloud) record or raw IP.
# NOT the Cloudflare-proxied jarrs.eu — CF only forwards HTTP/HTTPS, so
# UDP 2112 (irtt) / TCP 5201 (iperf3) never reach the origin behind it.
value: "metrics.jarrs.eu" # DNS-only A record -> Hetzner IPv4
- name: IRTT_PORT
value: "2112"
- name: IPERF_PORT
value: "5201"
- name: IRTT_INTERVAL
value: "20ms"
- name: IRTT_DURATION
value: "60" # seconds (numeric)
- name: TPUT_EVERY
value: "900" # 15 min
- name: TPUT_TIME
value: "10"
- name: IPERF_PARALLEL
value: "4"
- name: IRTT_HMAC # shared key; apply via secret (see below)
valueFrom:
secretKeyRef:
name: wan-monitor-irtt
key: hmac
optional: true
resources:
requests: { cpu: 20m, memory: 48Mi }
limits: { memory: 96Mi }
volumeMounts:
- name: scripts
mountPath: /scripts
readOnly: true
- name: shared
mountPath: /shared
- name: metrics-http
image: busybox:1.36
command: ["httpd", "-f", "-v", "-p", "9116", "-h", "/shared"]
ports:
- name: metrics
containerPort: 9116
resources:
requests: { cpu: 5m, memory: 8Mi }
limits: { memory: 24Mi }
volumeMounts:
- name: shared
mountPath: /shared
readOnly: true
volumes:
- name: blackbox-config
configMap:
name: wan-monitor-blackbox
- name: scripts
configMap:
name: wan-monitor-scripts
- name: shared
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: wan-monitor
namespace: admin-system
labels:
app: wan-monitor
spec:
type: ClusterIP
selector:
app: wan-monitor
ports:
- name: blackbox
port: 9115
targetPort: 9115
- name: metrics
port: 9116
targetPort: 9116
-142
View File
@@ -1,142 +0,0 @@
# Contact Mailer - Lightweight email sender for felhom.eu contact form
# Uses Resend.com API for transactional email delivery.
#
# PREREQUISITES:
# 1. Build and push the container image:
# docker build -t contact-mailer:latest .
# # Option A: Push to Gitea registry (if configured):
# # docker tag contact-mailer:latest gitea.felhom.eu/felhom/contact-mailer:latest
# # docker push gitea.felhom.eu/felhom/contact-mailer:latest
# # Option B: Import directly into k3s (single node):
# # docker save contact-mailer:latest | sudo k3s ctr images import -
#
# 2. Create the Secret with your Resend API key:
# kubectl create secret generic contact-mailer-config \
# --namespace=felhom-system \
# --from-literal=RESEND_API_KEY='re_xxxxxxxxxxxx'
#
# 3. Apply this manifest:
# kubectl apply -f contact-mailer.yaml
#
# 4. Test:
# # Health check:
# curl https://felhom.eu/api/healthz
# # Send test email (only works if DEBUG=true):
# curl -X POST https://felhom.eu/api/debug/test
#
# 5. Update contact form endpoint in kapcsolat.html:
# CONFIG.formEndpoint = '/api/contact';
#
# DEBUGGING:
# kubectl logs -n felhom-system deploy/contact-mailer -f
# kubectl exec -it -n felhom-system deploy/contact-mailer -- wget -qO- http://localhost:8080/healthz
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: contact-mailer
namespace: felhom-system
labels:
app: contact-mailer
spec:
replicas: 1
selector:
matchLabels:
app: contact-mailer
template:
metadata:
labels:
app: contact-mailer
spec:
containers:
- name: contact-mailer
image: contact-mailer:latest
# Use 'Never' for locally imported images, 'Always' for registry
imagePullPolicy: Never
ports:
- containerPort: 8080
env:
- name: RESEND_API_KEY
valueFrom:
secretKeyRef:
name: contact-mailer-config
key: RESEND_API_KEY
- name: FROM_EMAIL
value: "Felhom.eu <noreply@felhom.eu>"
- name: TO_EMAIL
value: "info@felhom.eu"
- name: ALLOWED_ORIGIN
value: "https://felhom.eu"
- name: TZ
value: "Europe/Budapest"
# Set to "true" to enable /debug/test endpoint
- name: DEBUG
value: "false"
resources:
requests:
memory: "16Mi"
cpu: "5m"
limits:
memory: "64Mi"
cpu: "100m"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 30
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
---
apiVersion: v1
kind: Service
metadata:
name: contact-mailer
namespace: felhom-system
spec:
selector:
app: contact-mailer
ports:
- port: 80
targetPort: 8080
---
# Ingress: routes felhom.eu/api/* to the contact mailer
# This is a SEPARATE ingress from the website - nginx-ingress merges them
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: contact-mailer
namespace: felhom-system
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
# Allow larger uploads for attachments
nginx.ingress.kubernetes.io/proxy-body-size: "25m"
# Timeout for large file uploads
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
spec:
ingressClassName: nginx-internal
tls:
- hosts:
- felhom.eu
secretName: felhom-webpage-tls
rules:
- host: felhom.eu
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: contact-mailer
port:
number: 80
-194
View File
@@ -1,194 +0,0 @@
# Healthchecks - Self-hosted cron/backup monitoring with dead man's switch
# Dashboard: https://status.felhom.eu
# Ping endpoint: https://status.felhom.eu/ping/<uuid>
#
# Customer servers ping this after successful backup.
# If a ping is missed, Healthchecks sends email alerts.
#
# After deploying, create superuser:
# kubectl exec -it -n felhom-system deploy/healthchecks -- python manage.py createsuperuser
#
# SMTP: Configure the Secret below with your email provider credentials.
# Recommended free options:
# - Resend.com (3000 emails/month free, easy setup)
# - Brevo/Sendinblue (300 emails/day free)
# - SMTP2GO (1000 emails/month free)
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: healthchecks-data
namespace: felhom-system
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: healthchecks
namespace: felhom-system
labels:
app: healthchecks
spec:
replicas: 1
selector:
matchLabels:
app: healthchecks
template:
metadata:
labels:
app: healthchecks
spec:
securityContext:
fsGroup: 999
containers:
- name: healthchecks
image: healthchecks/healthchecks:v4.2
ports:
- containerPort: 8000
env:
# --- Site settings ---
- name: SITE_ROOT
value: "https://status.felhom.eu"
- name: SITE_NAME
value: "Felhom Monitoring"
- name: ALLOWED_HOSTS
value: "status.felhom.eu"
- name: PING_ENDPOINT
value: "https://status.felhom.eu/ping/"
- name: DEBUG
value: "False"
- name: REGISTRATION_OPEN
value: "False"
- name: DB
value: "sqlite"
- name: DB_NAME
value: "/data/hc.sqlite"
- name: TZ
value: "Europe/Budapest"
# --- Secrets (from Secret) ---
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: healthchecks-config
key: SECRET_KEY
- name: SUPERUSER_EMAIL
valueFrom:
secretKeyRef:
name: healthchecks-config
key: SUPERUSER_EMAIL
- name: SUPERUSER_PASSWORD
valueFrom:
secretKeyRef:
name: healthchecks-config
key: SUPERUSER_PASSWORD
- name: EMAIL_HOST
valueFrom:
secretKeyRef:
name: healthchecks-config
key: EMAIL_HOST
- name: EMAIL_PORT
valueFrom:
secretKeyRef:
name: healthchecks-config
key: EMAIL_PORT
- name: EMAIL_HOST_USER
valueFrom:
secretKeyRef:
name: healthchecks-config
key: EMAIL_HOST_USER
- name: EMAIL_HOST_PASSWORD
valueFrom:
secretKeyRef:
name: healthchecks-config
key: EMAIL_HOST_PASSWORD
- name: EMAIL_USE_TLS
valueFrom:
secretKeyRef:
name: healthchecks-config
key: EMAIL_USE_TLS
- name: EMAIL_USE_VERIFICATION
valueFrom:
secretKeyRef:
name: healthchecks-config
key: EMAIL_USE_VERIFICATION
- name: DEFAULT_FROM_EMAIL
valueFrom:
secretKeyRef:
name: healthchecks-config
key: DEFAULT_FROM_EMAIL
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
memory: "128Mi"
cpu: "50m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /api/v3/status/
port: 8000
httpHeaders:
- name: Host
value: status.felhom.eu
initialDelaySeconds: 30
periodSeconds: 60
readinessProbe:
httpGet:
path: /api/v3/status/
port: 8000
httpHeaders:
- name: Host
value: status.felhom.eu
initialDelaySeconds: 10
periodSeconds: 10
volumes:
- name: data
persistentVolumeClaim:
claimName: healthchecks-data
---
apiVersion: v1
kind: Service
metadata:
name: healthchecks
namespace: felhom-system
spec:
selector:
app: healthchecks
ports:
- port: 80
targetPort: 8000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: healthchecks
namespace: felhom-system
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx-internal
tls:
- hosts:
- status.felhom.eu
secretName: healthchecks-tls
rules:
- host: status.felhom.eu
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: healthchecks
port:
number: 80
-288
View File
@@ -1,288 +0,0 @@
# Umami v3 - Privacy-focused web analytics for felhom.eu
# Dashboard: https://stats.felhom.eu
# Tracking: Add <script> tag to website HTML pages (see bottom of file)
#
# Umami v3 requires PostgreSQL (no SQLite/MySQL support).
# This manifest deploys a dedicated small PostgreSQL instance alongside Umami
# to keep it self-contained within the felhom-system namespace.
#
# PREREQUISITES:
# 1. Create the Secret with credentials:
# APP_SECRET: Random string for session encryption (generate with: openssl rand -hex 32)
# POSTGRES_PASSWORD: Database password (generate with: openssl rand -hex 16)
#
# kubectl create secret generic umami-config \
# --namespace=felhom-system \
# --from-literal=APP_SECRET="$(openssl rand -hex 32)" \
# --from-literal=POSTGRES_PASSWORD="$(openssl rand -hex 16)"
#
# 2. Apply this manifest:
# kubectl apply -f umami.yaml
#
# 3. Wait for pods to be ready (~30-60 seconds for first start, DB init):
# kubectl get pods -n felhom-system -l app=umami -w
# kubectl get pods -n felhom-system -l app=umami-db -w
#
# 4. Login at https://stats.felhom.eu
# Default credentials: admin / umami
# ⚠️ CHANGE THE PASSWORD IMMEDIATELY after first login!
#
# 5. Add your website in Umami:
# Settings → Websites → Add website → Name: "felhom.eu", Domain: "felhom.eu"
# Copy the tracking code and add it to your HTML pages (see bottom of file).
#
# DEBUGGING:
# kubectl logs -n felhom-system deploy/umami -f
# kubectl logs -n felhom-system deploy/umami-db -f
# kubectl exec -it -n felhom-system deploy/umami -- wget -qO- http://localhost:3000/api/heartbeat
# kubectl exec -it -n felhom-system deploy/umami-db -- pg_isready -U umami
#
# BACKUP:
# # Dump the database:
# kubectl exec -n felhom-system deploy/umami-db -- pg_dump -U umami umami > umami-backup-$(date +%Y%m%d).sql
# # Restore:
# cat umami-backup-YYYYMMDD.sql | kubectl exec -i -n felhom-system deploy/umami-db -- psql -U umami umami
# =============================================================================
# PERSISTENT STORAGE
# =============================================================================
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: umami-db-data
namespace: felhom-system
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 2Gi
# =============================================================================
# POSTGRESQL - Dedicated database for Umami
# =============================================================================
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: umami-db
namespace: felhom-system
labels:
app: umami-db
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: umami-db
template:
metadata:
labels:
app: umami-db
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: "umami"
- name: POSTGRES_USER
value: "umami"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: umami-config
key: POSTGRES_PASSWORD
- name: PGDATA
value: "/var/lib/postgresql/data/pgdata"
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "64Mi"
cpu: "25m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
exec:
command:
- pg_isready
- -U
- umami
initialDelaySeconds: 15
periodSeconds: 30
readinessProbe:
exec:
command:
- pg_isready
- -U
- umami
initialDelaySeconds: 5
periodSeconds: 10
volumes:
- name: data
persistentVolumeClaim:
claimName: umami-db-data
---
apiVersion: v1
kind: Service
metadata:
name: umami-db
namespace: felhom-system
spec:
selector:
app: umami-db
ports:
- port: 5432
targetPort: 5432
# =============================================================================
# UMAMI - Web Analytics Application
# =============================================================================
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: umami
namespace: felhom-system
labels:
app: umami
spec:
replicas: 1
selector:
matchLabels:
app: umami
template:
metadata:
labels:
app: umami
spec:
# Wait for DB to be available before starting Umami
initContainers:
- name: wait-for-db
image: postgres:16-alpine
command:
- sh
- -c
- |
echo "Waiting for PostgreSQL to be ready..."
until pg_isready -h umami-db -p 5432 -U umami; do
echo " ...still waiting"
sleep 2
done
echo "PostgreSQL is ready!"
resources:
requests:
memory: "16Mi"
cpu: "5m"
limits:
memory: "32Mi"
cpu: "50m"
containers:
- name: umami
image: ghcr.io/umami-software/umami:postgresql-latest
ports:
- containerPort: 3000
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: umami-config
key: POSTGRES_PASSWORD
- name: DATABASE_URL
value: "postgresql://umami:$(POSTGRES_PASSWORD)@umami-db:5432/umami"
- name: APP_SECRET
valueFrom:
secretKeyRef:
name: umami-config
key: APP_SECRET
# Disable Umami's own telemetry
- name: DISABLE_TELEMETRY
value: "1"
- name: TZ
value: "Europe/Budapest"
resources:
requests:
memory: "128Mi"
cpu: "50m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /api/heartbeat
port: 3000
initialDelaySeconds: 30
periodSeconds: 30
readinessProbe:
httpGet:
path: /api/heartbeat
port: 3000
initialDelaySeconds: 15
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: umami
namespace: felhom-system
spec:
selector:
app: umami
ports:
- port: 80
targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: umami
namespace: felhom-system
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx-internal
tls:
- hosts:
- stats.felhom.eu
secretName: umami-tls
rules:
- host: stats.felhom.eu
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: umami
port:
number: 80
# =============================================================================
# TRACKING SCRIPT - Add to your HTML pages
# =============================================================================
#
# After deploying and creating your website in Umami, add this to every page's
# <head> section (replace WEBSITE_ID with the ID from Umami dashboard):
#
# <script defer src="https://stats.felhom.eu/script.js" data-website-id="YOUR-WEBSITE-ID"></script>
#
# The script is <2KB, async/deferred, cookie-free, and GDPR compliant.
# No cookie consent banner needed!
#
# TIP: Since your HTML files are managed via FileBrowser, you can add the
# script tag to all pages at once. Add it right before </head> in:
# - index.html
# - alkalmazasok.html
# - technologiak.html
# - gyik.html
# - kapcsolat.html
# - biztonsagimentes.html (if exists)
# - Any other pages
-286
View File
@@ -1,286 +0,0 @@
# FileBrowser + Webpage deployment for felhom.eu
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: felhom-website-content
namespace: felhom-system
spec:
accessModes:
- ReadWriteMany
storageClassName: longhorn
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: filebrowser-db
namespace: felhom-system
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 100Mi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebrowser-config
namespace: felhom-system
data:
.filebrowser.json: |
{
"port": 8080,
"baseURL": "",
"address": "0.0.0.0",
"log": "stdout",
"database": "/database/filebrowser.db",
"root": "/srv"
}
---
# ===================
# NGINX CONFIG FOR CLEAN URLs
# ===================
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
namespace: felhom-system
data:
default.conf: |
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Enable clean URLs - serve .html files without extension
location / {
try_files $uri $uri.html $uri/ =404;
}
# Cache static assets
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 7d;
add_header Cache-Control "public, immutable";
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
# Error pages
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
}
---
# ===================
# FILEBROWSER
# ===================
apiVersion: apps/v1
kind: Deployment
metadata:
name: filebrowser
namespace: felhom-system
labels:
app: filebrowser
spec:
replicas: 1
selector:
matchLabels:
app: filebrowser
template:
metadata:
labels:
app: filebrowser
spec:
containers:
- name: filebrowser
image: filebrowser/filebrowser:v2-alpine
ports:
- containerPort: 8080
volumeMounts:
- name: website-content
mountPath: /srv
- name: database
mountPath: /database
- name: config
mountPath: /.filebrowser.json
subPath: .filebrowser.json
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
volumes:
- name: website-content
persistentVolumeClaim:
claimName: felhom-website-content
- name: database
persistentVolumeClaim:
claimName: filebrowser-db
- name: config
configMap:
name: filebrowser-config
---
apiVersion: v1
kind: Service
metadata:
name: filebrowser
namespace: felhom-system
spec:
selector:
app: filebrowser
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: filebrowser
namespace: felhom-system
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
spec:
ingressClassName: nginx-internal
tls:
- hosts:
- files.felhom.eu
secretName: filebrowser-tls
rules:
- host: files.felhom.eu
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: filebrowser
port:
number: 80
---
# ===================
# WEBPAGE (nginx)
# ===================
apiVersion: apps/v1
kind: Deployment
metadata:
name: felhom-webpage
namespace: felhom-system
labels:
app: felhom-webpage
spec:
replicas: 1
selector:
matchLabels:
app: felhom-webpage
template:
metadata:
labels:
app: felhom-webpage
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: website-content
mountPath: /usr/share/nginx/html
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
resources:
requests:
memory: "32Mi"
cpu: "10m"
limits:
memory: "128Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 30
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 10
volumes:
- name: website-content
persistentVolumeClaim:
claimName: felhom-website-content
- name: nginx-config
configMap:
name: nginx-config
---
apiVersion: v1
kind: Service
metadata:
name: felhom-webpage
namespace: felhom-system
spec:
selector:
app: felhom-webpage
ports:
- port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: felhom-webpage
namespace: felhom-system
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx-internal
tls:
- hosts:
- felhom.eu
- www.felhom.eu
secretName: felhom-webpage-tls
rules:
- host: felhom.eu
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: felhom-webpage
port:
number: 80
- host: www.felhom.eu
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: felhom-webpage
port:
number: 80
+7
View File
@@ -1,4 +1,11 @@
--- ---
# Image tag override: bumps pihole/pihole to 2026.05.0 without changing
# the chart version. The 2026.05.0 release bundles FTL v6.6.2 which
# imports 6 upstream dnsmasq CVE fixes (covering the dnsmasq 2.92/2.93
# disclosures). No FTL-side config or API changes per the release notes.
# https://github.com/pi-hole/docker-pi-hole/releases/tag/2026.05.0
image:
tag: "2026.05.0"
DNS1: "1.1.1.1" # Cloudflare DNS1: "1.1.1.1" # Cloudflare
DNS2: "8.8.8.8" # Google DNS2: "8.8.8.8" # Google
DNS3: "9.9.9.9" #Quad9 DNS3: "9.9.9.9" #Quad9
+6 -3
View File
@@ -3,8 +3,8 @@ image:
# -- The public dockerhub registry # -- The public dockerhub registry
registry: index.docker.io registry: index.docker.io
repository: plexinc/pms-docker repository: plexinc/pms-docker
# renovate: datasource=custom.plex depName=plex versioning=loose # renovate: datasource=docker depName=plexinc/pms-docker versioning=regex:^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)\.(?<build>\d+)-[a-f0-9]+$
tag: "1.43.0.10467-2b1ba6e69" tag: "1.43.2.10687-563d026ea"
sha: "" sha: ""
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
@@ -235,7 +235,10 @@ statefulSet:
annotations: {} annotations: {}
# -- Optional extra annotations to add to the pods in the statefulset # -- Optional extra annotations to add to the pods in the statefulset
podAnnotations: podAnnotations:
match-regex.version-checker.io/plex-plex-media-server-pms: ^\d+\.\d+\.\d+\.\d+-.*$ # Match only `<X.Y.Z.B>-<short-hash>` (the amd64/native tag form) and exclude
# per-arch tags (e.g. `-armhf`, `-arm64`) so version-checker doesn't show an
# ARM tag as "newer" than our x86_64 install.
match-regex.version-checker.io/plex-plex-media-server-pms: '^\d+\.\d+\.\d+\.\d+-[a-f0-9]+$'
service: service:
type: LoadBalancer type: LoadBalancer
+70 -1
View File
@@ -237,6 +237,75 @@ data:
regex: 'ak-outpost-(.*)-outpost' regex: 'ak-outpost-(.*)-outpost'
replacement: '$1' replacement: '$1'
# --- end-to-end latency + loss (ICMP) to many destinations ---
- job_name: 'wan-icmp'
metrics_path: /probe
params:
module: [icmp]
scrape_interval: 15s
static_configs:
- targets:
- 8.8.8.8
- 1.1.1.1
- jarrs.eu # Hetzner
- telex.hu
- store.steampowered.com
- 192.168.0.1 # gateway
- 37.191.56.193 # your public IP (update if it changes)
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: wan-monitor.admin-system:9115
# --- HTTP phase breakdown (dns/connect/tls/processing/transfer) ---
- job_name: 'wan-http'
metrics_path: /probe
params:
module: [http_2xx]
scrape_interval: 30s
static_configs:
- targets:
- https://telex.hu
- https://store.steampowered.com
- https://jarrs.eu
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: wan-monitor.admin-system:9115
# --- DNS resolution time per resolver (Pi-hole vs public) ---
- job_name: 'wan-dns'
metrics_path: /probe
params:
module: [dns_udp]
scrape_interval: 30s
static_configs:
- targets:
- 192.168.0.250 # Pi-hole
- 1.1.1.1
- 8.8.8.8
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: wan-monitor.admin-system:9115
# --- irtt (UDP quality) + iperf3 (throughput) textfile metrics ---
- job_name: 'wan-probe'
metrics_path: /metrics
scrape_interval: 30s
fallback_scrape_protocol: PrometheusText0.0.4
static_configs:
- targets: ['wan-monitor.admin-system:9116']
# CloudNativePG - Postgres metrics per instance # CloudNativePG - Postgres metrics per instance
- job_name: 'cloudnativepg' - job_name: 'cloudnativepg'
kubernetes_sd_configs: kubernetes_sd_configs:
@@ -621,7 +690,7 @@ spec:
memory: 128Mi memory: 128Mi
limits: limits:
cpu: 500m cpu: 500m
memory: 256Mi memory: 768Mi
volumeMounts: volumeMounts:
- name: data - name: data
mountPath: /var/lib/grafana mountPath: /var/lib/grafana
+102 -1
View File
@@ -13,7 +13,7 @@ metadata:
labels: labels:
app: prometheus app: prometheus
data: data:
authentik-alerts.yml: | authentik-alerts.yml: |
groups: groups:
- name: authentik-availability - name: authentik-availability
@@ -210,3 +210,104 @@ data:
annotations: annotations:
summary: "Longhorn node {{ $labels.node }} storage pressure" summary: "Longhorn node {{ $labels.node }} storage pressure"
description: "Node {{ $labels.node }} disk usage is at {{ printf \"%.1f\" $value }}%." description: "Node {{ $labels.node }} disk usage is at {{ printf \"%.1f\" $value }}%."
# Add this as a new data key (wan-alerts.yml) in the existing
# prometheus-rules ConfigMap (mon-system). Thresholds anchored to One.hu's
# "normal conditions" figures: 700 Mbit/s down / 28 Mbit/s up.
# Throughput is sampled every ~15 min, so `for:` spans >=2 samples to avoid
# firing on a single fluke. Recalibrate floors after a week of baseline data.
# NOTE: uses Prometheus template funcs (humanize/humanizePercentage/humanizeDuration);
# mul/div are NOT valid Prometheus template functions.
wan-alerts.yml: |
groups:
- name: wan-quality-alerts
rules:
# --- upstream loss: the prime suspect for dropped calls / WireGuard ---
- alert: WanUpstreamPacketLoss
expr: wan_irtt_loss_ratio{direction="upstream",condition="idle"} > 0.01
for: 2m
labels:
severity: warning
annotations:
summary: "WAN upstream packet loss to {{ $labels.target }}"
description: "irtt upstream loss {{ $value | humanizePercentage }} (>1%) for 2m. Cable-upstream symptom; capture for ISP."
- alert: WanDownstreamPacketLoss
expr: wan_irtt_loss_ratio{direction="downstream",condition="idle"} > 0.01
for: 2m
labels:
severity: warning
annotations:
summary: "WAN downstream packet loss to {{ $labels.target }}"
description: "irtt downstream loss {{ $value | humanizePercentage }} (>1%) for 2m."
# --- latency / jitter ---
- alert: WanLatencyHigh
expr: wan_irtt_rtt_seconds{stat="max",condition="idle"} > 0.08
for: 5m
labels:
severity: warning
annotations:
summary: "WAN RTT spikes to {{ $labels.target }}"
description: "irtt max RTT {{ $value | humanizeDuration }} (>80 ms) for 5m (idle). Real-time apps will feel this."
- alert: WanJitterHigh
expr: wan_irtt_jitter_seconds{direction="round_trip",condition="idle"} > 0.03
for: 5m
labels:
severity: warning
annotations:
summary: "WAN jitter high to {{ $labels.target }}"
description: "Round-trip jitter {{ $value | humanizeDuration }} (>30 ms) for 5m. Degrades VoIP/video."
# --- bufferbloat: latency added while the line is saturated ---
- alert: WanBufferbloat
expr: |
(
wan_irtt_rtt_seconds{stat="mean",condition="under_load"}
- on(target) wan_irtt_rtt_seconds{stat="mean",condition="idle"}
) > 0.1
for: 0m
labels:
severity: info
annotations:
summary: "WAN bufferbloat on {{ $labels.target }}"
description: "RTT rises {{ $value | humanizeDuration }} under load (>100 ms). Line buckles when saturated."
# --- throughput vs One.hu "normal" 700/28 (alert below 50%) ---
- alert: WanDownloadDegraded
expr: wan_throughput_bits_per_second{direction="download"} < 350e6
for: 20m
labels:
severity: warning
annotations:
summary: "WAN download below half of plan"
description: "Download {{ $value | humanize }}bit/s (< 350M, half of 700 normal) for 20m."
- alert: WanUploadDegraded
expr: wan_throughput_bits_per_second{direction="upload"} < 14e6
for: 20m
labels:
severity: warning
annotations:
summary: "WAN upload below half of plan"
description: "Upload {{ $value | humanize }}bit/s (< 14M, half of 28 normal) for 20m."
# --- the monitor itself stopped producing data ---
- alert: WanProbeStalled
expr: time() - max by(probe) (wan_probe_last_run_timestamp_seconds) > 300
for: 0m
labels:
severity: warning
annotations:
summary: "WAN probe '{{ $labels.probe }}' stalled"
description: "No fresh samples for >5 min. Check the wan-monitor pod / Hetzner endpoint."
- alert: WanBlackboxTargetDown
expr: probe_success{job=~"wan-.*"} == 0
for: 3m
labels:
severity: warning
annotations:
summary: "WAN probe to {{ $labels.instance }} failing"
description: "blackbox {{ $labels.job }} to {{ $labels.instance }} unreachable for 3m."
+4 -1
View File
@@ -392,10 +392,13 @@ spec:
nextcloud-config-hash: 06b49913be13b1f9a81745166dd75ada59e7ddd39e8f6a2c5538affe2a6d1093 nextcloud-config-hash: 06b49913be13b1f9a81745166dd75ada59e7ddd39e8f6a2c5538affe2a6d1093
php-config-hash: 5a497358af870e06b42325eee83d7c0e5466b7f6819cb49b598559d96def7428 php-config-hash: 5a497358af870e06b42325eee83d7c0e5466b7f6819cb49b598559d96def7428
hooks-hash: 44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a hooks-hash: 44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
# Only match the `X.Y.Z-apache` variant tags so version-checker doesn't
# treat the bare `X.Y.Z` server tag as a "newer" version of our apache image.
match-regex.version-checker.io/nextcloud: '^\d+\.\d+\.\d+-apache$'
spec: spec:
containers: containers:
- name: nextcloud - name: nextcloud
image: docker.io/library/nextcloud:33.0.4-apache image: docker.io/library/nextcloud:33.0.5-apache
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
env: env:
- name: SMTP_HOST - name: SMTP_HOST
+3 -3
View File
@@ -10,7 +10,7 @@ metadata:
labels: labels:
app.kubernetes.io/instance: outline app.kubernetes.io/instance: outline
app.kubernetes.io/name: outline app.kubernetes.io/name: outline
app.kubernetes.io/version: 1.1.0 app.kubernetes.io/version: 1.8.1
name: outline name: outline
namespace: outline-system namespace: outline-system
spec: spec:
@@ -31,7 +31,7 @@ spec:
spec: spec:
containers: containers:
- name: outline - name: outline
image: outlinewiki/outline:1.8.0 image: outlinewiki/outline:1.8.1
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
env: env:
- name: NODE_ENV - name: NODE_ENV
@@ -331,7 +331,7 @@ metadata:
labels: labels:
app.kubernetes.io/instance: outline app.kubernetes.io/instance: outline
app.kubernetes.io/name: outline app.kubernetes.io/name: outline
app.kubernetes.io/version: 1.1.0 app.kubernetes.io/version: 1.8.1
name: outline name: outline
namespace: outline-system namespace: outline-system
spec: spec:
+12 -5
View File
@@ -30,7 +30,7 @@ spec:
spec: spec:
containers: containers:
- name: prowlarr - name: prowlarr
image: linuxserver/prowlarr:version-2.3.0.5236 image: linuxserver/prowlarr:version-2.3.5.5327
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
env: env:
- name: PUID - name: PUID
@@ -91,7 +91,7 @@ spec:
spec: spec:
containers: containers:
- name: radarr - name: radarr
image: linuxserver/radarr:version-6.0.4.10291 image: linuxserver/radarr:version-6.1.1.10360
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
env: env:
- name: PUID - name: PUID
@@ -164,7 +164,7 @@ spec:
spec: spec:
containers: containers:
- name: sonarr - name: sonarr
image: linuxserver/sonarr:version-4.0.16.2944 image: linuxserver/sonarr:version-4.0.17.2952
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
env: env:
- name: PUID - name: PUID
@@ -705,7 +705,7 @@ spec:
spec: spec:
containers: containers:
- name: radarr - name: radarr
image: linuxserver/radarr:version-6.0.4.10291 image: linuxserver/radarr:version-6.1.1.10360
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
env: env:
- name: PUID - name: PUID
@@ -904,7 +904,14 @@ spec:
spec: spec:
containers: containers:
- name: seerr - name: seerr
image: docker.io/fallenbagel/jellyseerr:preview-OIDC # 2026-06-06: migrating from fallenbagel/jellyseerr:preview-OIDC
# (a custom OIDC-capable build) to seerr-team/seerr v3.x — the
# successor project (combined Overseerr+Jellyseerr team rebrand
# from v3.0.0). Mainline now has native OIDC support so we don't
# need the custom build. Migration is auto on first start; backed
# up the config PVC to ~/seerr-backups on dooplex before this PR.
# https://docs.seerr.dev/migration-guide
image: ghcr.io/seerr-team/seerr:v3.3.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
env: env:
- name: TZ - name: TZ
+2 -2
View File
@@ -30,7 +30,7 @@ spec:
spec: spec:
initContainers: initContainers:
- name: create-superuser - name: create-superuser
image: vabene1111/recipes:2.6 image: vabene1111/recipes:2.6.9
workingDir: /opt/recipes workingDir: /opt/recipes
command: command:
- /bin/sh - /bin/sh
@@ -106,7 +106,7 @@ spec:
key: email key: email
containers: containers:
- name: tandoor - name: tandoor
image: vabene1111/recipes:2.6 image: vabene1111/recipes:2.6.9
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
env: env:
- name: TZ - name: TZ
+1 -1
View File
@@ -45,7 +45,7 @@ spec:
# Renovate handles it via a customManagers regex defined in # Renovate handles it via a customManagers regex defined in
# admin-system/renovate.yaml (the kubernetes manager doesn't # admin-system/renovate.yaml (the kubernetes manager doesn't
# process inline `# renovate:` comments). # process inline `# renovate:` comments).
image: ghcr.io/lukegus/termix:release-1.11.2 image: ghcr.io/lukegus/termix:release-2.3.2
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- name: http - name: http
+7 -5
View File
@@ -144,8 +144,10 @@ spec:
app.kubernetes.io/instance: sparkyfitness app.kubernetes.io/instance: sparkyfitness
app.kubernetes.io/name: server app.kubernetes.io/name: server
annotations: annotations:
# Tag format is vMAJOR.MINOR.PATCH.BUILD (e.g. v0.16.6.3) # Tag format used to be vMAJOR.MINOR.PATCH.BUILD (e.g. v0.16.6.3),
match-regex.version-checker.io/server: '^v\d+\.\d+\.\d+\.\d+$' # changed to vMAJOR.MINOR.PATCH on 2026-06-01 (v0.16.7+). Accept
# both so historical comparisons + new releases both match.
match-regex.version-checker.io/server: '^v\d+\.\d+\.\d+(\.\d+)?$'
spec: spec:
enableServiceLinks: false enableServiceLinks: false
securityContext: securityContext:
@@ -166,7 +168,7 @@ spec:
echo "PostgreSQL is ready!" echo "PostgreSQL is ready!"
containers: containers:
- name: server - name: server
image: codewithcj/sparkyfitness_server:v0.16.6.3 image: codewithcj/sparkyfitness_server:v0.16.8
env: env:
# ---- Database (owner / superuser role, used for migrations) ---- # ---- Database (owner / superuser role, used for migrations) ----
- name: SPARKY_FITNESS_DB_HOST - name: SPARKY_FITNESS_DB_HOST
@@ -330,12 +332,12 @@ spec:
app.kubernetes.io/instance: sparkyfitness app.kubernetes.io/instance: sparkyfitness
app.kubernetes.io/name: frontend app.kubernetes.io/name: frontend
annotations: annotations:
match-regex.version-checker.io/frontend: '^v\d+\.\d+\.\d+\.\d+$' match-regex.version-checker.io/frontend: '^v\d+\.\d+\.\d+(\.\d+)?$'
spec: spec:
enableServiceLinks: false enableServiceLinks: false
containers: containers:
- name: frontend - name: frontend
image: codewithcj/sparkyfitness:v0.16.6.3 image: codewithcj/sparkyfitness:v0.16.8
env: env:
- name: SPARKY_FITNESS_SERVER_HOST - name: SPARKY_FITNESS_SERVER_HOST
value: "sparkyfitness-server" value: "sparkyfitness-server"