Add kustomize images: transformer and configMapGenerator across services

Move hardcoded image tags to kustomization.yaml images: sections (22
services) and replace hand-written ConfigMap manifests with
configMapGenerator (12 services). Image tags are now centralized in
kustomization.yaml, and generated ConfigMaps include content hashes
that trigger automatic pod rollouts on config changes.

New kustomization.yaml files for forgejo-runner and nvidia-device-plugin
switch ArgoCD from directory mode to kustomize mode.

Skipped: argocd (remote upstream), databases, external-secrets,
grafana-config (cross-kustomization dashboards), immich (Helm),
authentik blueprints (special YAML tags).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Erich Blume 2026-02-24 14:23:45 -08:00
commit 34925cb94b
79 changed files with 956 additions and 905 deletions

View file

@ -0,0 +1,169 @@
// Alloy k8s configuration - collects pod logs from all namespaces
// ============== K8S POD LOG DISCOVERY ==============
// Discover all pods in the cluster
discovery.kubernetes "pods" {
role = "pod"
}
// Relabel to extract useful metadata
discovery.relabel "pods" {
targets = discovery.kubernetes.pods.targets
// Keep only running pods
rule {
source_labels = ["__meta_kubernetes_pod_phase"]
regex = "Pending|Succeeded|Failed|Unknown"
action = "drop"
}
// Set namespace label
rule {
source_labels = ["__meta_kubernetes_namespace"]
target_label = "namespace"
}
// Set pod name label
rule {
source_labels = ["__meta_kubernetes_pod_name"]
target_label = "pod"
}
// Set container name label
rule {
source_labels = ["__meta_kubernetes_pod_container_name"]
target_label = "container"
}
// Set app label from pod labels
rule {
source_labels = ["__meta_kubernetes_pod_label_app"]
target_label = "app"
}
// Fallback: use app.kubernetes.io/name if no app label
rule {
source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_name"]
target_label = "app"
regex = "(.+)"
action = "replace"
}
// Set node name
rule {
source_labels = ["__meta_kubernetes_pod_node_name"]
target_label = "node"
}
// Build the log path for the pod container
rule {
source_labels = ["__meta_kubernetes_pod_uid", "__meta_kubernetes_pod_container_name"]
target_label = "__path__"
separator = "/"
replacement = "/var/log/pods/*$1/$2/*.log"
}
}
// Tail pod logs
loki.source.kubernetes "pods" {
targets = discovery.relabel.pods.output
forward_to = [loki.process.pods.receiver]
}
// Process logs - parse JSON if present, add labels
loki.process "pods" {
forward_to = [loki.write.loki.receiver]
// Drop noisy deprecation warning from minikube storage-provisioner
// See: https://github.com/kubernetes/minikube/issues/21009
stage.drop {
source = ""
expression = "v1 Endpoints is deprecated"
}
// Try to parse JSON logs (e.g., structured app logs)
// Handle both "msg" (common) and "message" (zot) field names
stage.json {
expressions = {
level = "level",
msg = "msg",
message = "message",
time = "time",
caller = "caller",
repository = "repository",
}
}
// Drop JSON parsing error labels (non-JSON logs are fine, just won't have extracted fields)
stage.label_drop {
values = ["__error__", "__error_details__"]
}
// Extract labels from parsed JSON data
stage.labels {
values = {
level = "",
caller = "",
repository = "",
}
}
}
// Write logs to Loki
loki.write "loki" {
endpoint {
url = "http://loki.monitoring.svc.cluster.local:3100/loki/api/v1/push"
}
}
// ============== SERVICE HEALTH PROBES ==============
// Blackbox-style HTTP probes for k8s services
prometheus.exporter.blackbox "services" {
config = "{ modules: { http_2xx: { prober: http, timeout: 5s } } }"
target {
name = "miniflux"
address = "http://miniflux.miniflux.svc.cluster.local:8080/healthcheck"
module = "http_2xx"
}
target {
name = "kiwix"
address = "http://kiwix.kiwix.svc.cluster.local:80/"
module = "http_2xx"
}
target {
name = "transmission"
address = "http://transmission.torrent.svc.cluster.local:9091/transmission/web/"
module = "http_2xx"
}
target {
name = "devpi"
address = "http://devpi.devpi.svc.cluster.local:3141/+api"
module = "http_2xx"
}
target {
name = "argocd"
address = "http://argocd-server.argocd.svc.cluster.local:80/healthz"
module = "http_2xx"
}
}
// Scrape blackbox probe results
prometheus.scrape "blackbox" {
targets = prometheus.exporter.blackbox.services.targets
scrape_interval = "30s"
forward_to = [prometheus.remote_write.prometheus.receiver]
}
// Push metrics to Prometheus
prometheus.remote_write "prometheus" {
endpoint {
url = "http://prometheus.monitoring.svc.cluster.local:9090/api/v1/write"
}
}

View file

@ -1,176 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: alloy-config
namespace: alloy
data:
config.alloy: |
// Alloy k8s configuration - collects pod logs from all namespaces
// ============== K8S POD LOG DISCOVERY ==============
// Discover all pods in the cluster
discovery.kubernetes "pods" {
role = "pod"
}
// Relabel to extract useful metadata
discovery.relabel "pods" {
targets = discovery.kubernetes.pods.targets
// Keep only running pods
rule {
source_labels = ["__meta_kubernetes_pod_phase"]
regex = "Pending|Succeeded|Failed|Unknown"
action = "drop"
}
// Set namespace label
rule {
source_labels = ["__meta_kubernetes_namespace"]
target_label = "namespace"
}
// Set pod name label
rule {
source_labels = ["__meta_kubernetes_pod_name"]
target_label = "pod"
}
// Set container name label
rule {
source_labels = ["__meta_kubernetes_pod_container_name"]
target_label = "container"
}
// Set app label from pod labels
rule {
source_labels = ["__meta_kubernetes_pod_label_app"]
target_label = "app"
}
// Fallback: use app.kubernetes.io/name if no app label
rule {
source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_name"]
target_label = "app"
regex = "(.+)"
action = "replace"
}
// Set node name
rule {
source_labels = ["__meta_kubernetes_pod_node_name"]
target_label = "node"
}
// Build the log path for the pod container
rule {
source_labels = ["__meta_kubernetes_pod_uid", "__meta_kubernetes_pod_container_name"]
target_label = "__path__"
separator = "/"
replacement = "/var/log/pods/*$1/$2/*.log"
}
}
// Tail pod logs
loki.source.kubernetes "pods" {
targets = discovery.relabel.pods.output
forward_to = [loki.process.pods.receiver]
}
// Process logs - parse JSON if present, add labels
loki.process "pods" {
forward_to = [loki.write.loki.receiver]
// Drop noisy deprecation warning from minikube storage-provisioner
// See: https://github.com/kubernetes/minikube/issues/21009
stage.drop {
source = ""
expression = "v1 Endpoints is deprecated"
}
// Try to parse JSON logs (e.g., structured app logs)
// Handle both "msg" (common) and "message" (zot) field names
stage.json {
expressions = {
level = "level",
msg = "msg",
message = "message",
time = "time",
caller = "caller",
repository = "repository",
}
}
// Drop JSON parsing error labels (non-JSON logs are fine, just won't have extracted fields)
stage.label_drop {
values = ["__error__", "__error_details__"]
}
// Extract labels from parsed JSON data
stage.labels {
values = {
level = "",
caller = "",
repository = "",
}
}
}
// Write logs to Loki
loki.write "loki" {
endpoint {
url = "http://loki.monitoring.svc.cluster.local:3100/loki/api/v1/push"
}
}
// ============== SERVICE HEALTH PROBES ==============
// Blackbox-style HTTP probes for k8s services
prometheus.exporter.blackbox "services" {
config = "{ modules: { http_2xx: { prober: http, timeout: 5s } } }"
target {
name = "miniflux"
address = "http://miniflux.miniflux.svc.cluster.local:8080/healthcheck"
module = "http_2xx"
}
target {
name = "kiwix"
address = "http://kiwix.kiwix.svc.cluster.local:80/"
module = "http_2xx"
}
target {
name = "transmission"
address = "http://transmission.torrent.svc.cluster.local:9091/transmission/web/"
module = "http_2xx"
}
target {
name = "devpi"
address = "http://devpi.devpi.svc.cluster.local:3141/+api"
module = "http_2xx"
}
target {
name = "argocd"
address = "http://argocd-server.argocd.svc.cluster.local:80/healthz"
module = "http_2xx"
}
}
// Scrape blackbox probe results
prometheus.scrape "blackbox" {
targets = prometheus.exporter.blackbox.services.targets
scrape_interval = "30s"
forward_to = [prometheus.remote_write.prometheus.receiver]
}
// Push metrics to Prometheus
prometheus.remote_write "prometheus" {
endpoint {
url = "http://prometheus.monitoring.svc.cluster.local:9090/api/v1/write"
}
}

View file

@ -19,7 +19,7 @@ spec:
fsGroup: 473 # alloy user group
containers:
- name: alloy
image: grafana/alloy:v1.13.1
image: grafana/alloy
args:
- run
- --server.http.listen-addr=0.0.0.0:12345

View file

@ -1,7 +1,18 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: alloy
resources:
- namespace.yaml
- rbac.yaml
- configmap.yaml
- daemonset.yaml
images:
- name: grafana/alloy
newTag: v1.13.1
configMapGenerator:
- name: alloy-config
files:
- config.alloy

View file

@ -18,7 +18,7 @@ spec:
spec:
containers:
- name: redis
image: docker.io/library/redis:7-alpine
image: docker.io/library/redis
ports:
- name: redis
containerPort: 6379

View file

@ -18,7 +18,7 @@ spec:
spec:
containers:
- name: server
image: registry.ops.eblu.me/blumeops/authentik:v2025.10.1-a72a0d8-nix
image: registry.ops.eblu.me/blumeops/authentik
args: ["server"]
ports:
- name: http

View file

@ -18,7 +18,7 @@ spec:
spec:
containers:
- name: worker
image: registry.ops.eblu.me/blumeops/authentik:v2025.10.1-a72a0d8-nix
image: registry.ops.eblu.me/blumeops/authentik
args: ["worker"]
env:
- name: AUTHENTIK_SECRET_KEY

View file

@ -11,3 +11,8 @@ resources:
- service.yaml
- service-redis.yaml
- ingress-tailscale.yaml
images:
- name: registry.ops.eblu.me/blumeops/authentik
newTag: v2025.10.1-a72a0d8-nix
- name: docker.io/library/redis
newTag: 7-alpine

View file

@ -16,7 +16,7 @@ spec:
spec:
containers:
- name: cv
image: registry.ops.eblu.me/blumeops/cv:v1.0.3-ffa8727
image: registry.ops.eblu.me/blumeops/cv
ports:
- containerPort: 80
name: http

View file

@ -6,3 +6,6 @@ resources:
- deployment.yaml
- service.yaml
- ingress-tailscale.yaml
images:
- name: registry.ops.eblu.me/blumeops/cv
newTag: v1.0.3-ffa8727

View file

@ -8,3 +8,7 @@ resources:
- service.yaml
- ingress-tailscale.yaml
- external-secret.yaml
images:
- name: registry.ops.eblu.me/blumeops/devpi
newTag: v6.19.1-ffa8727

View file

@ -18,7 +18,7 @@ spec:
fsGroup: 1000
containers:
- name: devpi
image: registry.ops.eblu.me/blumeops/devpi:v6.19.1-ffa8727
image: registry.ops.eblu.me/blumeops/devpi
env:
- name: DEVPI_ROOT_PASSWORD
valueFrom:

View file

@ -16,7 +16,7 @@ spec:
spec:
containers:
- name: docs
image: registry.ops.eblu.me/blumeops/quartz:v1.28.2-ffa8727
image: registry.ops.eblu.me/blumeops/quartz
ports:
- containerPort: 80
name: http

View file

@ -6,3 +6,6 @@ resources:
- deployment.yaml
- service.yaml
- ingress-tailscale.yaml
images:
- name: registry.ops.eblu.me/blumeops/quartz
newTag: v1.28.2-ffa8727

View file

@ -0,0 +1,19 @@
# Reviewed against v12.7.0 defaults (2026-02-22)
log:
level: info
runner:
file: /data/.runner
capacity: 2
timeout: 3h
shutdown_timeout: 3h
# Env vars injected into all job containers
envs:
DOCKER_HOST: tcp://127.0.0.1:2375
TZ: America/Los_Angeles
container:
# Job execution image is set via RUNNER_LABELS in deployment.yaml
network: "host"
# Connect to DinD sidecar via TCP (not socket)
docker_host: tcp://127.0.0.1:2375

View file

@ -1,30 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: forgejo-runner-config
namespace: forgejo-runner
data:
config.yaml: |
# Reviewed against v12.7.0 defaults (2026-02-22)
log:
level: info
runner:
file: /data/.runner
capacity: 2
timeout: 3h
shutdown_timeout: 3h
# Env vars injected into all job containers
envs:
DOCKER_HOST: tcp://127.0.0.1:2375
TZ: America/Los_Angeles
container:
# Job execution image is set via RUNNER_LABELS in deployment.yaml
network: "host"
# Connect to DinD sidecar via TCP (not socket)
docker_host: tcp://127.0.0.1:2375
daemon.json: |
{
"registry-mirrors": ["http://host.minikube.internal:5050"]
}

View file

@ -0,0 +1,3 @@
{
"registry-mirrors": ["http://host.minikube.internal:5050"]
}

View file

@ -18,7 +18,7 @@ spec:
containers:
# Forgejo runner daemon
- name: runner
image: code.forgejo.org/forgejo/runner:12.7.0
image: code.forgejo.org/forgejo/runner
env:
- name: TZ
value: America/Los_Angeles
@ -68,7 +68,7 @@ spec:
# Docker-in-Docker sidecar
- name: dind
image: docker:27-dind
image: docker
securityContext:
privileged: true
env:

View file

@ -0,0 +1,21 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: forgejo-runner
resources:
- namespace.yaml
- external-secret.yaml
- deployment.yaml
images:
- name: code.forgejo.org/forgejo/runner
newTag: "12.7.0"
- name: docker
newTag: 27-dind
configMapGenerator:
- name: forgejo-runner-config
files:
- config.yaml
- daemon.json

View file

@ -1,42 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: frigate-notify-config
namespace: frigate
data:
config.yml: |
frigate:
server: http://frigate:5000
public_url: https://nvr.ops.eblu.me
webapi:
enabled: true
interval: 15
mqtt:
enabled: false
alerts:
general:
title: "Frigate Alert"
nosnap: drop
snap_hires: true
notify_once: true
zones:
unzoned: drop
allow:
- driveway_entrance
- driveway
labels:
allow:
- person
- car
ntfy:
enabled: true
server: http://ntfy.ntfy.svc.cluster.local:80
topic: frigate-alerts
headers:
- X-Actions: "view, Open Event, {{.Extra.PublicURL}}/review?id={{.ID}}, clear=true; view, Open Camera, {{.Extra.PublicURL}}/#/cameras/{{.Camera}}"

View file

@ -1,90 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: frigate-config
namespace: frigate
data:
config.yml: |
mqtt:
host: mosquitto.mqtt.svc.cluster.local
port: 1883
go2rtc:
streams:
# GableCam IP is reserved in UX7 DHCP config
gablecam:
- "rtsp://{FRIGATE_CAMERA_USER}:{FRIGATE_CAMERA_PASSWORD}@192.168.1.159:554/h264Preview_01_main"
gablecam_sub:
- "rtsp://{FRIGATE_CAMERA_USER}:{FRIGATE_CAMERA_PASSWORD}@192.168.1.159:554/h264Preview_01_sub"
cameras:
gablecam:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/gablecam
input_args: preset-rtsp-restream
roles: [record]
- path: rtsp://127.0.0.1:8554/gablecam_sub
input_args: preset-rtsp-restream
roles: [detect]
detect:
enabled: true
stationary:
max_frames:
default: 1500
motion:
mask:
- 0.401,0.026,0.4,0.078,0.587,0.072,0.585,0.02
- 0.881,0.422,0.789,0.245,0.595,0.054,0.531,0,0.634,0,0.824,0.192,0.892,0.307
zones:
driveway_entrance:
coordinates: 0.841,0.37,0.735,0.344,0.681,0.2,0.78,0.259
objects: [car, dog, person]
inertia: 3
loitering_time: 0
driveway:
coordinates: 0.767,0.25,0.58,0.2,0.218,0.25,0.128,0.296,0.003,0.565,0.001,0.992,0.826,0.992,0.897,0.665,0.869,0.608,0.788,0.354
review:
alerts:
labels: [person, car]
required_zones:
- driveway_entrance
- driveway
detections:
required_zones:
- driveway
- driveway_entrance
objects:
track: [person, car, dog, cat, bird]
detectors:
onnx:
type: onnx
model:
model_type: yolo-generic
width: 640
height: 640
input_tensor: nchw
input_dtype: float
path: /media/frigate/models/yolov9-c-640.onnx
labelmap_path: /labelmap/coco-80.txt
record:
enabled: true
continuous:
days: 3
alerts:
retain:
days: 30
mode: active_objects
detections:
retain:
days: 14
mode: motion
snapshots:
enabled: true
retain:
default: 14

View file

@ -16,7 +16,7 @@ spec:
spec:
containers:
- name: frigate-notify
image: ghcr.io/0x2142/frigate-notify:v0.5.4
image: ghcr.io/0x2142/frigate-notify
env:
- name: TZ
value: America/Los_Angeles

View file

@ -19,7 +19,7 @@ spec:
runtimeClassName: nvidia
initContainers:
- name: copy-config
image: busybox:1.37
image: busybox
command: ["cp", "/config-ro/config.yml", "/config/config.yml"]
volumeMounts:
- name: config-ro
@ -28,7 +28,7 @@ spec:
mountPath: /config
containers:
- name: frigate
image: ghcr.io/blakeblackshear/frigate:0.17.0-rc2-tensorrt
image: ghcr.io/blakeblackshear/frigate
ports:
- containerPort: 5000
name: http

View file

@ -0,0 +1,83 @@
mqtt:
host: mosquitto.mqtt.svc.cluster.local
port: 1883
go2rtc:
streams:
# GableCam IP is reserved in UX7 DHCP config
gablecam:
- "rtsp://{FRIGATE_CAMERA_USER}:{FRIGATE_CAMERA_PASSWORD}@192.168.1.159:554/h264Preview_01_main"
gablecam_sub:
- "rtsp://{FRIGATE_CAMERA_USER}:{FRIGATE_CAMERA_PASSWORD}@192.168.1.159:554/h264Preview_01_sub"
cameras:
gablecam:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/gablecam
input_args: preset-rtsp-restream
roles: [record]
- path: rtsp://127.0.0.1:8554/gablecam_sub
input_args: preset-rtsp-restream
roles: [detect]
detect:
enabled: true
stationary:
max_frames:
default: 1500
motion:
mask:
- 0.401,0.026,0.4,0.078,0.587,0.072,0.585,0.02
- 0.881,0.422,0.789,0.245,0.595,0.054,0.531,0,0.634,0,0.824,0.192,0.892,0.307
zones:
driveway_entrance:
coordinates: 0.841,0.37,0.735,0.344,0.681,0.2,0.78,0.259
objects: [car, dog, person]
inertia: 3
loitering_time: 0
driveway:
coordinates: 0.767,0.25,0.58,0.2,0.218,0.25,0.128,0.296,0.003,0.565,0.001,0.992,0.826,0.992,0.897,0.665,0.869,0.608,0.788,0.354
review:
alerts:
labels: [person, car]
required_zones:
- driveway_entrance
- driveway
detections:
required_zones:
- driveway
- driveway_entrance
objects:
track: [person, car, dog, cat, bird]
detectors:
onnx:
type: onnx
model:
model_type: yolo-generic
width: 640
height: 640
input_tensor: nchw
input_dtype: float
path: /media/frigate/models/yolov9-c-640.onnx
labelmap_path: /labelmap/coco-80.txt
record:
enabled: true
continuous:
days: 3
alerts:
retain:
days: 30
mode: active_objects
detections:
retain:
days: 14
mode: motion
snapshots:
enabled: true
retain:
default: 14

View file

@ -0,0 +1,35 @@
frigate:
server: http://frigate:5000
public_url: https://nvr.ops.eblu.me
webapi:
enabled: true
interval: 15
mqtt:
enabled: false
alerts:
general:
title: "Frigate Alert"
nosnap: drop
snap_hires: true
notify_once: true
zones:
unzoned: drop
allow:
- driveway_entrance
- driveway
labels:
allow:
- person
- car
ntfy:
enabled: true
server: http://ntfy.ntfy.svc.cluster.local:80
topic: frigate-alerts
headers:
- X-Actions: "view, Open Event, {{.Extra.PublicURL}}/review?id={{.ID}}, clear=true; view, Open Camera, {{.Extra.PublicURL}}/#/cameras/{{.Camera}}"

View file

@ -4,8 +4,6 @@ kind: Kustomization
namespace: frigate
resources:
- external-secret.yaml
- configmap.yaml
- configmap-notify.yaml
- pv-nfs.yaml
- pvc-recordings.yaml
- pvc-database.yaml
@ -13,3 +11,19 @@ resources:
- deployment-notify.yaml
- service.yaml
- ingress-tailscale.yaml
images:
- name: busybox
newTag: "1.37"
- name: ghcr.io/blakeblackshear/frigate
newTag: 0.17.0-rc2-tensorrt
- name: ghcr.io/0x2142/frigate-notify
newTag: v0.5.4
configMapGenerator:
- name: frigate-config
files:
- config.yml=frigate-config.yml
- name: frigate-notify-config
files:
- config.yml=frigate-notify-config.yml

View file

@ -1,100 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana
namespace: monitoring
labels:
app.kubernetes.io/name: grafana
app.kubernetes.io/instance: grafana
data:
grafana.ini: |
[analytics]
check_for_updates = false
reporting_enabled = false
[auth.generic_oauth]
allow_sign_up = true
api_url = https://authentik.ops.eblu.me/application/o/userinfo/
auth_url = https://authentik.ops.eblu.me/application/o/authorize/
auto_login = false
client_id = grafana
client_secret = $__env{GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET}
enabled = true
name = Authentik
role_attribute_path = contains(groups[*], 'admins') && 'Admin' || 'Viewer'
skip_org_role_sync = false
scopes = openid profile email
token_url = https://authentik.ops.eblu.me/application/o/token/
[log]
mode = console
[paths]
data = /var/lib/grafana/
logs = /var/log/grafana
plugins = /var/lib/grafana/plugins
provisioning = /etc/grafana/provisioning
[security]
allow_embedding = false
[server]
root_url = https://grafana.ops.eblu.me
datasources.yaml: |
apiVersion: 1
datasources:
- access: proxy
editable: false
isDefault: true
name: Prometheus
orgId: 1
type: prometheus
uid: prometheus
url: http://prometheus.monitoring.svc.cluster.local:9090
- access: proxy
editable: false
name: Loki
orgId: 1
type: loki
uid: loki
url: http://loki.monitoring.svc.cluster.local:3100
- access: proxy
database: teslamate
editable: false
jsonData:
database: teslamate
connMaxLifetime: 14400
maxIdleConns: 2
maxOpenConns: 5
sslmode: disable
name: TeslaMate
orgId: 1
secureJsonData:
password: $TESLAMATE_DB_PASSWORD
type: postgres
uid: TeslaMate
url: blumeops-pg-rw.databases.svc.cluster.local:5432
user: teslamate
---
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-config-dashboards
namespace: monitoring
labels:
app.kubernetes.io/name: grafana
app.kubernetes.io/instance: grafana
data:
provider.yaml: |
apiVersion: 1
providers:
- name: 'sidecarProvider'
orgId: 1
type: file
disableDeletion: false
allowUiUpdates: false
updateIntervalSeconds: 30
options:
foldersFromFilesStructure: true
path: /tmp/dashboards

View file

@ -0,0 +1,34 @@
apiVersion: 1
datasources:
- access: proxy
editable: false
isDefault: true
name: Prometheus
orgId: 1
type: prometheus
uid: prometheus
url: http://prometheus.monitoring.svc.cluster.local:9090
- access: proxy
editable: false
name: Loki
orgId: 1
type: loki
uid: loki
url: http://loki.monitoring.svc.cluster.local:3100
- access: proxy
database: teslamate
editable: false
jsonData:
database: teslamate
connMaxLifetime: 14400
maxIdleConns: 2
maxOpenConns: 5
sslmode: disable
name: TeslaMate
orgId: 1
secureJsonData:
password: $TESLAMATE_DB_PASSWORD
type: postgres
uid: TeslaMate
url: blumeops-pg-rw.databases.svc.cluster.local:5432
user: teslamate

View file

@ -32,7 +32,7 @@ spec:
runAsUser: 472
initContainers:
- name: init-chown-data
image: docker.io/library/busybox:1.31.1
image: docker.io/library/busybox
imagePullPolicy: IfNotPresent
command: ["chown", "-R", "472:472", "/var/lib/grafana"]
securityContext:
@ -48,7 +48,7 @@ spec:
containers:
# Dashboard sidecar - watches ConfigMaps with grafana_dashboard=1
- name: grafana-sc-dashboard
image: quay.io/kiwigrid/k8s-sidecar:1.28.0
image: quay.io/kiwigrid/k8s-sidecar
imagePullPolicy: IfNotPresent
env:
- name: METHOD
@ -88,7 +88,7 @@ spec:
mountPath: /tmp/dashboards
# Grafana
- name: grafana
image: registry.ops.eblu.me/blumeops/grafana:v12.3.3-d05d2fb
image: registry.ops.eblu.me/blumeops/grafana
imagePullPolicy: IfNotPresent
env:
- name: POD_IP

View file

@ -0,0 +1,32 @@
[analytics]
check_for_updates = false
reporting_enabled = false
[auth.generic_oauth]
allow_sign_up = true
api_url = https://authentik.ops.eblu.me/application/o/userinfo/
auth_url = https://authentik.ops.eblu.me/application/o/authorize/
auto_login = false
client_id = grafana
client_secret = $__env{GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET}
enabled = true
name = Authentik
role_attribute_path = contains(groups[*], 'admins') && 'Admin' || 'Viewer'
skip_org_role_sync = false
scopes = openid profile email
token_url = https://authentik.ops.eblu.me/application/o/token/
[log]
mode = console
[paths]
data = /var/lib/grafana/
logs = /var/log/grafana
plugins = /var/lib/grafana/plugins
provisioning = /etc/grafana/provisioning
[security]
allow_embedding = false
[server]
root_url = https://grafana.ops.eblu.me

View file

@ -5,8 +5,24 @@ namespace: monitoring
resources:
- serviceaccount.yaml
- configmap.yaml
- pvc.yaml
- deployment.yaml
- service.yaml
- rbac.yaml
images:
- name: docker.io/library/busybox
newTag: 1.31.1
- name: quay.io/kiwigrid/k8s-sidecar
newTag: 1.28.0
- name: registry.ops.eblu.me/blumeops/grafana
newTag: v12.3.3-d05d2fb
configMapGenerator:
- name: grafana
files:
- grafana.ini
- datasources.yaml
- name: grafana-config-dashboards
files:
- provider.yaml

View file

@ -0,0 +1,11 @@
apiVersion: 1
providers:
- name: 'sidecarProvider'
orgId: 1
type: file
disableDeletion: false
allowUiUpdates: false
updateIntervalSeconds: 30
options:
foldersFromFilesStructure: true
path: /tmp/dashboards

View file

@ -0,0 +1,16 @@
- Admin:
- Tailscale Admin:
- href: https://login.tailscale.com/admin
icon: tailscale
- 1Password:
- href: https://my.1password.com
icon: 1password
- Pulumi:
- href: https://app.pulumi.com/eblume/blumeops-tailnet
icon: si-pulumi
- ArgoCD:
- href: https://argocd.ops.eblu.me
icon: argo-cd
- UniFi:
- href: https://unifi.ui.com
icon: ubiquiti

View file

@ -1,155 +0,0 @@
# Homepage configuration files
# Extracted from former Helm values.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: homepage-config
namespace: homepage
data:
bookmarks.yaml: |
- Admin:
- Tailscale Admin:
- href: https://login.tailscale.com/admin
icon: tailscale
- 1Password:
- href: https://my.1password.com
icon: 1password
- Pulumi:
- href: https://app.pulumi.com/eblume/blumeops-tailnet
icon: si-pulumi
- ArgoCD:
- href: https://argocd.ops.eblu.me
icon: argo-cd
- UniFi:
- href: https://unifi.ui.com
icon: ubiquiti
services.yaml: |
- Host Services:
- Forgejo:
href: https://forge.ops.eblu.me
icon: forgejo
description: Git forge
widget:
type: gitea
url: https://forge.ops.eblu.me
key: "{{HOMEPAGE_VAR_FORGEJO_API_KEY}}"
- Registry:
href: https://registry.ops.eblu.me
icon: zot-registry
description: Container registry
- Sifaka NAS:
href: https://nas.ops.eblu.me
icon: synology
description: NAS dashboard
widget:
type: prometheusmetric
url: https://prometheus.ops.eblu.me
metrics:
- label: Used
query: node_filesystem_size_bytes{mountpoint="/Volumes/backups"} - node_filesystem_avail_bytes{mountpoint="/Volumes/backups"}
format:
type: bytes
- label: Total
query: node_filesystem_size_bytes{mountpoint="/Volumes/backups"}
format:
type: bytes
- Borgmatic:
href: https://grafana.ops.eblu.me/d/borgmatic
icon: borgmatic
description: Backup system
widget:
type: prometheusmetric
url: https://prometheus.ops.eblu.me
metrics:
- label: Last backup
query: time() - borgmatic_last_archive_timestamp
format:
type: duration
- label: Archive size
query: borgmatic_repo_deduplicated_size_bytes
format:
type: bytes
- Jellyfin:
href: https://jellyfin.ops.eblu.me
icon: jellyfin
description: Media server
widget:
type: jellyfin
url: https://jellyfin.ops.eblu.me
key: "{{HOMEPAGE_VAR_JELLYFIN_API_KEY}}"
enableBlocks: true
enableNowPlaying: true
# TODO: Add Caddy widget when admin API is enabled (currently admin off)
# - Caddy:
# href: https://indri.tail8d86e.ts.net
# icon: caddy
# description: Reverse proxy
# widget:
# type: caddy
# url: http://indri.tail8d86e.ts.net:2019
- Infrastructure:
- Authentik:
href: https://authentik.ops.eblu.me
icon: authentik
description: Identity provider
- NVR:
href: https://nvr.ops.eblu.me
icon: frigate.png
description: Network video recorder
- Ntfy:
href: https://ntfy.ops.eblu.me
icon: ntfy.png
description: Push notifications
widgets.yaml: |
- greeting:
text_size: xl
text: Welcome to Blue Mops
- datetime:
text_size: lg
format:
dateStyle: long
timeStyle: short
hour12: true
- openweathermap:
label: Camano
latitude: 48.18235
longitude: -122.52590
units: imperial
provider: openweathermap
apiKey: "{{HOMEPAGE_VAR_OPENWEATHERMAP_API_KEY}}"
cache: 15
# TODO: Add UniFi widget when controller is set up
# - unifi_console:
# url: https://192.168.1.1
# username: homepage
# password: "{{HOMEPAGE_VAR_UNIFI_PASSWORD}}"
# TODO: Add Glances widget when Glances is deployed
# - glances:
# url: http://indri.tail8d86e.ts.net:61208
# metric: cpu
kubernetes.yaml: |
mode: cluster
docker.yaml: ""
settings.yaml: |
title: BlumeOps
headerStyle: boxed
quicklaunch:
searchDescriptions: true
showSearchSuggestions: true
provider: custom
url: https://kagi.com/search?q=
suggestionUrl: https://kagisuggest.com/api/autosuggest?q=
layout:
Host Services:
style: column
Content:
style: column
Infrastructure:
style: column
Services:
style: column

View file

@ -20,7 +20,7 @@ spec:
fsGroup: 1000
containers:
- name: homepage
image: registry.ops.eblu.me/blumeops/homepage:v1.10.1-a72a0d8
image: registry.ops.eblu.me/blumeops/homepage
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false

View file

View file

@ -0,0 +1 @@
mode: cluster

View file

@ -5,7 +5,6 @@ resources:
- serviceaccount.yaml
- clusterrole.yaml
- clusterrolebinding.yaml
- configmap.yaml
- deployment.yaml
- service.yaml
- ingress-tailscale.yaml
@ -15,3 +14,17 @@ resources:
- external-secret-grafana.yaml
- external-secret-miniflux.yaml
- external-secret-navidrome.yaml
images:
- name: registry.ops.eblu.me/blumeops/homepage
newTag: v1.10.1-a72a0d8
configMapGenerator:
- name: homepage-config
files:
- bookmarks.yaml
- services.yaml
- widgets.yaml
- kubernetes.yaml
- docker.yaml
- settings.yaml

View file

@ -0,0 +1,76 @@
- Host Services:
- Forgejo:
href: https://forge.ops.eblu.me
icon: forgejo
description: Git forge
widget:
type: gitea
url: https://forge.ops.eblu.me
key: "{{HOMEPAGE_VAR_FORGEJO_API_KEY}}"
- Registry:
href: https://registry.ops.eblu.me
icon: zot-registry
description: Container registry
- Sifaka NAS:
href: https://nas.ops.eblu.me
icon: synology
description: NAS dashboard
widget:
type: prometheusmetric
url: https://prometheus.ops.eblu.me
metrics:
- label: Used
query: node_filesystem_size_bytes{mountpoint="/Volumes/backups"} - node_filesystem_avail_bytes{mountpoint="/Volumes/backups"}
format:
type: bytes
- label: Total
query: node_filesystem_size_bytes{mountpoint="/Volumes/backups"}
format:
type: bytes
- Borgmatic:
href: https://grafana.ops.eblu.me/d/borgmatic
icon: borgmatic
description: Backup system
widget:
type: prometheusmetric
url: https://prometheus.ops.eblu.me
metrics:
- label: Last backup
query: time() - borgmatic_last_archive_timestamp
format:
type: duration
- label: Archive size
query: borgmatic_repo_deduplicated_size_bytes
format:
type: bytes
- Jellyfin:
href: https://jellyfin.ops.eblu.me
icon: jellyfin
description: Media server
widget:
type: jellyfin
url: https://jellyfin.ops.eblu.me
key: "{{HOMEPAGE_VAR_JELLYFIN_API_KEY}}"
enableBlocks: true
enableNowPlaying: true
# TODO: Add Caddy widget when admin API is enabled (currently admin off)
# - Caddy:
# href: https://indri.tail8d86e.ts.net
# icon: caddy
# description: Reverse proxy
# widget:
# type: caddy
# url: http://indri.tail8d86e.ts.net:2019
- Infrastructure:
- Authentik:
href: https://authentik.ops.eblu.me
icon: authentik
description: Identity provider
- NVR:
href: https://nvr.ops.eblu.me
icon: frigate.png
description: Network video recorder
- Ntfy:
href: https://ntfy.ops.eblu.me
icon: ntfy.png
description: Push notifications

View file

@ -0,0 +1,17 @@
title: BlumeOps
headerStyle: boxed
quicklaunch:
searchDescriptions: true
showSearchSuggestions: true
provider: custom
url: https://kagi.com/search?q=
suggestionUrl: https://kagisuggest.com/api/autosuggest?q=
layout:
Host Services:
style: column
Content:
style: column
Infrastructure:
style: column
Services:
style: column

View file

@ -0,0 +1,26 @@
- greeting:
text_size: xl
text: Welcome to Blue Mops
- datetime:
text_size: lg
format:
dateStyle: long
timeStyle: short
hour12: true
- openweathermap:
label: Camano
latitude: 48.18235
longitude: -122.52590
units: imperial
provider: openweathermap
apiKey: "{{HOMEPAGE_VAR_OPENWEATHERMAP_API_KEY}}"
cache: 15
# TODO: Add UniFi widget when controller is set up
# - unifi_console:
# url: https://192.168.1.1
# username: homepage
# password: "{{HOMEPAGE_VAR_UNIFI_PASSWORD}}"
# TODO: Add Glances widget when Glances is deployed
# - glances:
# url: http://indri.tail8d86e.ts.net:61208
# metric: cpu

View file

@ -1,68 +0,0 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: zim-torrent-sync-script
namespace: kiwix
data:
sync-zim-torrents.sh: |
#!/bin/bash
# Sync ZIM torrents from kiwix ConfigMap to Transmission
# Runs as a sidecar in the kiwix deployment
set -euo pipefail
TORRENT_LIST="${TORRENT_LIST:-/config/torrents.txt}"
TRANSMISSION_HOST="${TRANSMISSION_HOST:-transmission.torrent.svc.cluster.local}"
TRANSMISSION_PORT="${TRANSMISSION_PORT:-9091}"
echo "Syncing ZIM torrents to transmission at ${TRANSMISSION_HOST}:${TRANSMISSION_PORT}"
# Wait for transmission to be ready
# Transmission RPC returns 409 on first request (to provide session ID), which is fine
echo "Waiting for Transmission RPC..."
max_attempts=30
attempt=0
until curl -s -o /dev/null -w "%{http_code}" "http://${TRANSMISSION_HOST}:${TRANSMISSION_PORT}/transmission/rpc" | grep -qE "^(200|409)$"; do
attempt=$((attempt + 1))
if [[ $attempt -ge $max_attempts ]]; then
echo "Transmission not ready after ${max_attempts} attempts, will retry next cycle"
exit 0 # Don't fail, just skip this sync
fi
sleep 10
done
echo "Transmission is ready"
# Get current torrents from transmission
# transmission-remote returns header + data + footer, extract just torrent names
current=$(transmission-remote "${TRANSMISSION_HOST}:${TRANSMISSION_PORT}" -l 2>/dev/null | \
tail -n +2 | head -n -1 | awk '{print $NF}' || true)
added=0
skipped=0
while IFS= read -r url || [[ -n "$url" ]]; do
# Skip empty lines and comments
[[ -z "$url" || "$url" =~ ^[[:space:]]*# ]] && continue
# Trim whitespace
url=$(echo "$url" | xargs)
[[ -z "$url" ]] && continue
# Extract base name from URL (remove .torrent extension)
basename=$(basename "$url" .torrent)
# Also try without .zim in case transmission reports it differently
basename_no_zim="${basename%.zim}"
# Check if already in transmission
if echo "$current" | grep -qF "$basename_no_zim"; then
((skipped++)) || true
else
if transmission-remote "${TRANSMISSION_HOST}:${TRANSMISSION_PORT}" -a "$url" 2>/dev/null; then
echo "Added: $basename"
((added++)) || true
else
echo "Warning: Failed to add $url" >&2
fi
fi
done < "$TORRENT_LIST"
echo "Sync complete: $added added, $skipped already present"

View file

@ -1,69 +0,0 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: kiwix-zim-torrents
namespace: kiwix
data:
torrents.txt: |
# Declarative ZIM archive torrent URLs
# These are synced to transmission automatically by the kiwix sidecar
# Format: one URL per line, comments start with #
#
# Users can also add ZIM torrents manually via torrent.tail8d86e.ts.net
# and kiwix will pick them up automatically.
# Wikipedia - Top 1M English articles (43G)
https://download.kiwix.org/zim/wikipedia/wikipedia_en_top1m_maxi_2025-09.zim.torrent
# Project Gutenberg - Public domain books (72G)
https://download.kiwix.org/zim/gutenberg/gutenberg_en_all_2023-08.zim.torrent
# iFixit - Repair guides (3.3G)
https://download.kiwix.org/zim/ifixit/ifixit_en_all_2025-12.zim.torrent
# Stack Exchange
https://download.kiwix.org/zim/stack_exchange/superuser.com_en_all_2025-12.zim.torrent
https://download.kiwix.org/zim/stack_exchange/math.stackexchange.com_en_all_2025-12.zim.torrent
# LibreTexts - Open educational resources
https://download.kiwix.org/zim/libretexts/libretexts.org_en_bio_2025-01.zim.torrent
https://download.kiwix.org/zim/libretexts/libretexts.org_en_chem_2025-01.zim.torrent
https://download.kiwix.org/zim/libretexts/libretexts.org_en_eng_2025-01.zim.torrent
https://download.kiwix.org/zim/libretexts/libretexts.org_en_math_2025-01.zim.torrent
https://download.kiwix.org/zim/libretexts/libretexts.org_en_phys_2025-01.zim.torrent
https://download.kiwix.org/zim/libretexts/libretexts.org_en_human_2025-01.zim.torrent
# DevDocs - Programming documentation
https://download.kiwix.org/zim/devdocs/devdocs_en_bash_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_c_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_click_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_cmake_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_cpp_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_css_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_django-rest-framework_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_django_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_docker_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_duckdb_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_fish_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_gcc_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_git_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_go_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_godot_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_hammerspoon_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_homebrew_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_javascript_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_kubectl_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_kubernetes_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_latex_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_lua_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_markdown_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_nginx_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_nix_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_postgresql_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_python_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_redis_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_sqlite_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_typescript_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_werkzeug_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_zig_2026-01.zim.torrent

View file

@ -15,7 +15,7 @@ spec:
serviceAccountName: zim-watcher
containers:
- name: watcher
image: registry.ops.eblu.me/blumeops/kubectl:v1.34.4-a72a0d8
image: registry.ops.eblu.me/blumeops/kubectl
command: ["/bin/bash", "-c"]
args:
- |

View file

@ -20,7 +20,7 @@ spec:
containers:
# Main kiwix-serve container
- name: kiwix-serve
image: registry.ops.eblu.me/blumeops/kiwix-serve:v3.8.1-ffa8727
image: registry.ops.eblu.me/blumeops/kiwix-serve
args:
- "/bin/sh"
- "-c"
@ -53,7 +53,7 @@ spec:
# Sidecar: Syncs declarative ZIM torrents to transmission
- name: torrent-sync
image: registry.ops.eblu.me/blumeops/transmission:v4.0.6-r4-ffa8727
image: registry.ops.eblu.me/blumeops/transmission
command: ["/bin/bash", "-c"]
args:
- |

View file

@ -3,9 +3,23 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: kiwix
resources:
- configmap-zim-torrents.yaml
- configmap-sync-script.yaml
- deployment.yaml
- service.yaml
- ingress-tailscale.yaml
- cronjob-zim-watcher.yaml
images:
- name: registry.ops.eblu.me/blumeops/kiwix-serve
newTag: v3.8.1-ffa8727
- name: registry.ops.eblu.me/blumeops/transmission
newTag: v4.0.6-r4-ffa8727
- name: registry.ops.eblu.me/blumeops/kubectl
newTag: v1.34.4-a72a0d8
configMapGenerator:
- name: kiwix-zim-torrents
files:
- torrents.txt
- name: zim-torrent-sync-script
files:
- sync-zim-torrents.sh

View file

@ -0,0 +1,60 @@
#!/bin/bash
# Sync ZIM torrents from kiwix ConfigMap to Transmission
# Runs as a sidecar in the kiwix deployment
set -euo pipefail
TORRENT_LIST="${TORRENT_LIST:-/config/torrents.txt}"
TRANSMISSION_HOST="${TRANSMISSION_HOST:-transmission.torrent.svc.cluster.local}"
TRANSMISSION_PORT="${TRANSMISSION_PORT:-9091}"
echo "Syncing ZIM torrents to transmission at ${TRANSMISSION_HOST}:${TRANSMISSION_PORT}"
# Wait for transmission to be ready
# Transmission RPC returns 409 on first request (to provide session ID), which is fine
echo "Waiting for Transmission RPC..."
max_attempts=30
attempt=0
until curl -s -o /dev/null -w "%{http_code}" "http://${TRANSMISSION_HOST}:${TRANSMISSION_PORT}/transmission/rpc" | grep -qE "^(200|409)$"; do
attempt=$((attempt + 1))
if [[ $attempt -ge $max_attempts ]]; then
echo "Transmission not ready after ${max_attempts} attempts, will retry next cycle"
exit 0 # Don't fail, just skip this sync
fi
sleep 10
done
echo "Transmission is ready"
# Get current torrents from transmission
# transmission-remote returns header + data + footer, extract just torrent names
current=$(transmission-remote "${TRANSMISSION_HOST}:${TRANSMISSION_PORT}" -l 2>/dev/null | \
tail -n +2 | head -n -1 | awk '{print $NF}' || true)
added=0
skipped=0
while IFS= read -r url || [[ -n "$url" ]]; do
# Skip empty lines and comments
[[ -z "$url" || "$url" =~ ^[[:space:]]*# ]] && continue
# Trim whitespace
url=$(echo "$url" | xargs)
[[ -z "$url" ]] && continue
# Extract base name from URL (remove .torrent extension)
basename=$(basename "$url" .torrent)
# Also try without .zim in case transmission reports it differently
basename_no_zim="${basename%.zim}"
# Check if already in transmission
if echo "$current" | grep -qF "$basename_no_zim"; then
((skipped++)) || true
else
if transmission-remote "${TRANSMISSION_HOST}:${TRANSMISSION_PORT}" -a "$url" 2>/dev/null; then
echo "Added: $basename"
((added++)) || true
else
echo "Warning: Failed to add $url" >&2
fi
fi
done < "$TORRENT_LIST"
echo "Sync complete: $added added, $skipped already present"

View file

@ -0,0 +1,61 @@
# Declarative ZIM archive torrent URLs
# These are synced to transmission automatically by the kiwix sidecar
# Format: one URL per line, comments start with #
#
# Users can also add ZIM torrents manually via torrent.tail8d86e.ts.net
# and kiwix will pick them up automatically.
# Wikipedia - Top 1M English articles (43G)
https://download.kiwix.org/zim/wikipedia/wikipedia_en_top1m_maxi_2025-09.zim.torrent
# Project Gutenberg - Public domain books (72G)
https://download.kiwix.org/zim/gutenberg/gutenberg_en_all_2023-08.zim.torrent
# iFixit - Repair guides (3.3G)
https://download.kiwix.org/zim/ifixit/ifixit_en_all_2025-12.zim.torrent
# Stack Exchange
https://download.kiwix.org/zim/stack_exchange/superuser.com_en_all_2025-12.zim.torrent
https://download.kiwix.org/zim/stack_exchange/math.stackexchange.com_en_all_2025-12.zim.torrent
# LibreTexts - Open educational resources
https://download.kiwix.org/zim/libretexts/libretexts.org_en_bio_2025-01.zim.torrent
https://download.kiwix.org/zim/libretexts/libretexts.org_en_chem_2025-01.zim.torrent
https://download.kiwix.org/zim/libretexts/libretexts.org_en_eng_2025-01.zim.torrent
https://download.kiwix.org/zim/libretexts/libretexts.org_en_math_2025-01.zim.torrent
https://download.kiwix.org/zim/libretexts/libretexts.org_en_phys_2025-01.zim.torrent
https://download.kiwix.org/zim/libretexts/libretexts.org_en_human_2025-01.zim.torrent
# DevDocs - Programming documentation
https://download.kiwix.org/zim/devdocs/devdocs_en_bash_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_c_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_click_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_cmake_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_cpp_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_css_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_django-rest-framework_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_django_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_docker_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_duckdb_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_fish_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_gcc_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_git_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_go_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_godot_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_hammerspoon_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_homebrew_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_javascript_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_kubectl_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_kubernetes_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_latex_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_lua_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_markdown_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_nginx_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_nix_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_postgresql_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_python_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_redis_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_sqlite_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_typescript_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_werkzeug_2026-01.zim.torrent
https://download.kiwix.org/zim/devdocs/devdocs_en_zig_2026-01.zim.torrent

View file

@ -18,7 +18,7 @@ spec:
serviceAccountName: kube-state-metrics
containers:
- name: kube-state-metrics
image: registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.18.0
image: registry.k8s.io/kube-state-metrics/kube-state-metrics
ports:
- containerPort: 8080
name: http-metrics

View file

@ -4,3 +4,6 @@ resources:
- rbac.yaml
- deployment.yaml
- service.yaml
images:
- name: registry.k8s.io/kube-state-metrics/kube-state-metrics
newTag: v2.18.0

View file

@ -1,58 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: loki-config
namespace: monitoring
data:
loki-config.yaml: |
auth_enabled: false
server:
http_listen_port: 3100
http_listen_address: 0.0.0.0
grpc_listen_port: 9096
common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
storage_config:
tsdb_shipper:
active_index_directory: /loki/tsdb-index
cache_location: /loki/tsdb-cache
limits_config:
retention_period: 744h # 31 days
compactor:
working_directory: /loki/compactor
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
retention_delete_worker_count: 150
delete_request_store: filesystem

View file

@ -4,7 +4,15 @@ kind: Kustomization
namespace: monitoring
resources:
- configmap.yaml
- statefulset.yaml
- service.yaml
- ingress-tailscale.yaml
images:
- name: grafana/loki
newTag: "3.6.5"
configMapGenerator:
- name: loki-config
files:
- loki-config.yaml

View file

@ -0,0 +1,51 @@
auth_enabled: false
server:
http_listen_port: 3100
http_listen_address: 0.0.0.0
grpc_listen_port: 9096
common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
storage_config:
tsdb_shipper:
active_index_directory: /loki/tsdb-index
cache_location: /loki/tsdb-cache
limits_config:
retention_period: 744h # 31 days
compactor:
working_directory: /loki/compactor
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
retention_delete_worker_count: 150
delete_request_store: filesystem

View file

@ -20,7 +20,7 @@ spec:
runAsUser: 10001
containers:
- name: loki
image: grafana/loki:3.6.5
image: grafana/loki
args:
- -config.file=/etc/loki/loki-config.yaml
ports:

View file

@ -15,7 +15,7 @@ spec:
spec:
containers:
- name: miniflux
image: registry.ops.eblu.me/blumeops/miniflux:v2.2.17-a72a0d8
image: registry.ops.eblu.me/blumeops/miniflux
ports:
- containerPort: 8080
env:

View file

@ -7,3 +7,7 @@ resources:
- deployment.yaml
- service.yaml
- ingress-tailscale.yaml
images:
- name: registry.ops.eblu.me/blumeops/miniflux
newTag: v2.2.17-a72a0d8

View file

@ -1,10 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mosquitto-config
namespace: mqtt
data:
mosquitto.conf: |
listener 1883
allow_anonymous true
persistence false

View file

@ -16,7 +16,7 @@ spec:
spec:
containers:
- name: mosquitto
image: eclipse-mosquitto:2.0.22
image: eclipse-mosquitto
ports:
- containerPort: 1883
name: mqtt

View file

@ -3,6 +3,12 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: mqtt
resources:
- configmap.yaml
- deployment.yaml
- service.yaml
images:
- name: eclipse-mosquitto
newTag: "2.0.22"
configMapGenerator:
- name: mosquitto-config
files:
- mosquitto.conf

View file

@ -0,0 +1,3 @@
listener 1883
allow_anonymous true
persistence false

View file

@ -20,7 +20,7 @@ spec:
fsGroup: 1000
containers:
- name: navidrome
image: registry.ops.eblu.me/blumeops/navidrome:v0.60.3-a72a0d8
image: registry.ops.eblu.me/blumeops/navidrome
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false

View file

@ -9,3 +9,6 @@ resources:
- deployment.yaml
- service.yaml
- ingress-tailscale.yaml
images:
- name: registry.ops.eblu.me/blumeops/navidrome
newTag: v0.60.3-a72a0d8

View file

@ -1,13 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: ntfy-config
namespace: ntfy
data:
server.yml: |
base-url: https://ntfy.ops.eblu.me
upstream-base-url: https://ntfy.sh
attachment-cache-dir: /var/cache/ntfy/attachments
attachment-total-size-limit: 1G
attachment-file-size-limit: 10M
attachment-expiry-duration: 24h

View file

@ -16,7 +16,7 @@ spec:
spec:
containers:
- name: ntfy
image: registry.ops.eblu.me/blumeops/ntfy:v2.17.0-ffa8727-nix
image: registry.ops.eblu.me/blumeops/ntfy
args: ["serve", "--config", "/etc/ntfy/server.yml"]
ports:
- containerPort: 80

View file

@ -3,7 +3,13 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ntfy
resources:
- configmap.yaml
- deployment.yaml
- service.yaml
- ingress-tailscale.yaml
images:
- name: registry.ops.eblu.me/blumeops/ntfy
newTag: v2.17.0-ffa8727-nix
configMapGenerator:
- name: ntfy-config
files:
- server.yml

View file

@ -0,0 +1,6 @@
base-url: https://ntfy.ops.eblu.me
upstream-base-url: https://ntfy.sh
attachment-cache-dir: /var/cache/ntfy/attachments
attachment-total-size-limit: 1G
attachment-file-size-limit: 10M
attachment-expiry-duration: 24h

View file

@ -22,7 +22,7 @@ spec:
priorityClassName: system-node-critical
containers:
- name: nvidia-device-plugin
image: nvcr.io/nvidia/k8s-device-plugin:v0.18.2
image: nvcr.io/nvidia/k8s-device-plugin
args:
- --device-id-strategy=index
env:

View file

@ -0,0 +1,12 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: nvidia-device-plugin
resources:
- daemonset.yaml
- runtime-class.yaml
images:
- name: nvcr.io/nvidia/k8s-device-plugin
newTag: v0.18.2

View file

@ -1,53 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: monitoring
data:
prometheus.yml: |
global:
scrape_interval: 15s
evaluation_interval: 15s
# Indri system metrics are pushed via Alloy remote_write
# K8s services are scraped directly
scrape_configs:
# Sifaka NAS exporters (via Caddy L4 TCP proxy on indri)
- job_name: "node-exporter-sifaka"
static_configs:
- targets: ["nas.ops.eblu.me:9100"]
- job_name: "smartctl-sifaka"
scrape_interval: 60s
static_configs:
- targets: ["nas.ops.eblu.me:9633"]
# CNPG PostgreSQL metrics (k8s internal)
- job_name: "cnpg-postgres"
static_configs:
- targets: ["blumeops-pg-metrics-tailscale.databases.svc.cluster.local:9187"]
labels:
instance: "blumeops-pg"
# Prometheus self-monitoring
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
# Loki metrics
- job_name: "loki"
static_configs:
- targets: ["loki.monitoring.svc.cluster.local:3100"]
# Kubernetes state metrics (pods, deployments, resource usage, etc.)
- job_name: "kube-state-metrics"
static_configs:
- targets: ["kube-state-metrics.monitoring.svc.cluster.local:8080"]
# Frigate NVR metrics (via Caddy on indri — Frigate runs on ringtail)
- job_name: "frigate"
scheme: https
static_configs:
- targets: ["nvr.ops.eblu.me"]
metrics_path: /api/metrics

View file

@ -4,7 +4,15 @@ kind: Kustomization
namespace: monitoring
resources:
- configmap.yaml
- statefulset.yaml
- service.yaml
- ingress-tailscale.yaml
images:
- name: registry.ops.eblu.me/blumeops/prometheus
newTag: v3.9.1-2ba5d8a
configMapGenerator:
- name: prometheus-config
files:
- prometheus.yml

View file

@ -0,0 +1,46 @@
global:
scrape_interval: 15s
evaluation_interval: 15s
# Indri system metrics are pushed via Alloy remote_write
# K8s services are scraped directly
scrape_configs:
# Sifaka NAS exporters (via Caddy L4 TCP proxy on indri)
- job_name: "node-exporter-sifaka"
static_configs:
- targets: ["nas.ops.eblu.me:9100"]
- job_name: "smartctl-sifaka"
scrape_interval: 60s
static_configs:
- targets: ["nas.ops.eblu.me:9633"]
# CNPG PostgreSQL metrics (k8s internal)
- job_name: "cnpg-postgres"
static_configs:
- targets: ["blumeops-pg-metrics-tailscale.databases.svc.cluster.local:9187"]
labels:
instance: "blumeops-pg"
# Prometheus self-monitoring
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
# Loki metrics
- job_name: "loki"
static_configs:
- targets: ["loki.monitoring.svc.cluster.local:3100"]
# Kubernetes state metrics (pods, deployments, resource usage, etc.)
- job_name: "kube-state-metrics"
static_configs:
- targets: ["kube-state-metrics.monitoring.svc.cluster.local:8080"]
# Frigate NVR metrics (via Caddy on indri — Frigate runs on ringtail)
- job_name: "frigate"
scheme: https
static_configs:
- targets: ["nvr.ops.eblu.me"]
metrics_path: /api/metrics

View file

@ -20,7 +20,7 @@ spec:
runAsUser: 65534
containers:
- name: prometheus
image: registry.ops.eblu.me/blumeops/prometheus:v3.9.1-2ba5d8a
image: registry.ops.eblu.me/blumeops/prometheus
args:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus

View file

@ -8,3 +8,7 @@ resources:
- operator.yaml
- proxyclass.yaml
- dnsconfig.yaml
images:
- name: docker.io/tailscale/k8s-operator
newTag: v1.94.2

View file

@ -5362,7 +5362,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.uid
image: docker.io/tailscale/k8s-operator:v1.94.2
image: docker.io/tailscale/k8s-operator
imagePullPolicy: Always
name: operator
volumeMounts:

View file

@ -15,7 +15,7 @@ spec:
spec:
containers:
- name: teslamate
image: registry.ops.eblu.me/blumeops/teslamate:v2.2.0-ffa8727
image: registry.ops.eblu.me/blumeops/teslamate
ports:
- containerPort: 4000
env:

View file

@ -9,3 +9,7 @@ resources:
- ingress-tailscale.yaml
- external-secret-db.yaml
- external-secret-encryption-key.yaml
images:
- name: registry.ops.eblu.me/blumeops/teslamate
newTag: v2.2.0-ffa8727

View file

@ -16,7 +16,7 @@ spec:
spec:
containers:
- name: transmission
image: registry.ops.eblu.me/blumeops/transmission:v4.0.6-r4-ffa8727
image: registry.ops.eblu.me/blumeops/transmission
env:
- name: PUID
value: "1000"

View file

@ -8,3 +8,6 @@ resources:
- deployment.yaml
- service.yaml
- ingress-tailscale.yaml
images:
- name: registry.ops.eblu.me/blumeops/transmission
newTag: v4.0.6-r4-ffa8727

View file

@ -0,0 +1 @@
Move image tags to kustomize `images:` transformer across 22 services and replace hand-written ConfigMaps with `configMapGenerator:` in 12 services, enabling content-hash-based automatic rollouts on config changes.