new setup

This commit is contained in:
Alvin Wang 2026-04-22 14:31:16 -07:00
parent 0a5c149c5f
commit 28c23faf24
54 changed files with 316 additions and 179 deletions

0
.sops.yaml Normal file → Executable file
View File

0
AGENTS.md Normal file → Executable file
View File

6
Headlamp.md Normal file
View File

@ -0,0 +1,6 @@
kubectl -n apps create serviceaccount headlamp-admin
kubectl create clusterrolebinding headlamp-admin \
--serviceaccount=apps:headlamp-admin \
--clusterrole=cluster-admin
kubectl -n apps create token headlamp-admin

7
MetalLB.md Normal file
View File

@ -0,0 +1,7 @@
helm repo add metallb https://metallb.github.io/metallb
helm repo update
helm search repo metallb
helm dependency build charts/metallb
helm upgrade --install metallb charts/metallb -n kube-system --wait

20
README.md Normal file → Executable file
View File

@ -162,18 +162,24 @@ sops -e -i secrets/secrets.enc.yaml
Or deploy individually:
```bash
helm upgrade --install metallb charts/metallb -n kube-system --wait
helm upgrade --install traefik-internal charts/traefik-internal -n kube-system --wait
# Traefik config goes in kube-system (managed by k3s)
helm upgrade --install traefik-config charts/traefik-config -n kube-system
kubectl create namespace apps
helm upgrade --install headlamp charts/headlamp -n apps
helm upgrade --install dashboards charts/dashboards -n apps
helm upgrade --install paperless charts/paperless -n apps
helm upgrade --install mealie charts/mealie -n apps
kubectl create namespace media
helm upgrade --install media charts/media -n media
kubectl create namespace apps
helm upgrade --install paperless charts/paperless -n apps
helm upgrade --install mealie charts/mealie -n apps
helm upgrade --install dashboards charts/dashboards -n apps
helm upgrade --install headlamp charts/headlamp -n apps
helm upgrade --install utils charts/utils -n apps
# Traefik config goes in kube-system (managed by k3s)
helm upgrade --install traefik-config charts/traefik-config -n kube-system
```
## Verifying

0
charts/dashboards/Chart.yaml Normal file → Executable file
View File

0
charts/dashboards/templates/glance-ingressroute.yaml Normal file → Executable file
View File

0
charts/dashboards/templates/glance.yaml Normal file → Executable file
View File

0
charts/dashboards/templates/homepage-ingressroute.yaml Normal file → Executable file
View File

0
charts/dashboards/templates/homepage.yaml Normal file → Executable file
View File

0
charts/dashboards/values.yaml Normal file → Executable file
View File

0
charts/headlamp/Chart.yaml Normal file → Executable file
View File

0
charts/headlamp/templates/headlamp-ingressroute.yaml Normal file → Executable file
View File

0
charts/headlamp/templates/headlamp.yaml Normal file → Executable file
View File

0
charts/headlamp/values.yaml Normal file → Executable file
View File

0
charts/mealie/Chart.yaml Normal file → Executable file
View File

0
charts/mealie/templates/mealie-ingressroute.yaml Normal file → Executable file
View File

0
charts/mealie/templates/mealie.yaml Normal file → Executable file
View File

0
charts/mealie/values.yaml Normal file → Executable file
View File

0
charts/media/Chart.yaml Normal file → Executable file
View File

5
charts/media/templates/_helpers.tpl Normal file → Executable file
View File

@ -11,3 +11,8 @@ app.kubernetes.io/part-of: media
- name: TZ
value: {{ .Values.tz | quote }}
{{- end -}}
{{- define "media.requireMacWorker" -}}
nodeSelector:
homelab/node-role: worker
{{- end -}}

57
charts/media/templates/bazarr.yaml Normal file → Executable file
View File

@ -1,17 +1,3 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: bazarr-config
labels:
app: bazarr
{{- include "media.labels" . | nindent 4 }}
spec:
accessModes: [ReadWriteOnce]
storageClassName: {{ .Values.storageClass }}
resources:
requests:
storage: {{ .Values.bazarr.configSize }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
@ -31,6 +17,7 @@ spec:
labels:
app: bazarr
spec:
{{- include "media.requireMacWorker" . | nindent 6 }}
containers:
- name: bazarr
image: {{ .Values.bazarr.image }}
@ -47,15 +34,16 @@ spec:
mountPath: /tv
volumes:
- name: config
persistentVolumeClaim:
claimName: bazarr-config
hostPath:
path: {{ .Values.serviceData }}/bazarr/config
type: DirectoryOrCreate
- name: movies
hostPath:
path: {{ .Values.dogstore }}/sonarr/data/radarr-library
path: /dogstore/sonarr/data/radarr-library
type: DirectoryOrCreate
- name: tv
hostPath:
path: {{ .Values.dogstore }}/sonarr/data/library
path: /dogstore/sonarr/data/library
type: DirectoryOrCreate
---
apiVersion: v1
@ -70,3 +58,36 @@ spec:
ports:
- port: 6767
targetPort: 6767
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: bazarr-internal
annotations:
kubernetes.io/ingress.class: traefik-internal
spec:
entryPoints:
- web
routes:
- match: Host(`bazarr.{{ .Values.internalDomain }}`)
kind: Rule
services:
- name: bazarr
port: 6767
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: bazarr-internal-tls
annotations:
kubernetes.io/ingress.class: traefik-internal
spec:
entryPoints:
- websecure
routes:
- match: Host(`bazarr.{{ .Values.internalDomain }}`)
kind: Rule
services:
- name: bazarr
port: 6767
tls: {}

3
charts/media/templates/plex.yaml Normal file → Executable file
View File

@ -17,8 +17,7 @@ spec:
labels:
app: plex
spec:
nodeSelector:
homelab/node-role: worker
{{- include "media.requireMacWorker" . | nindent 6 }}
containers:
- name: plex
image: {{ .Values.plex.image }}

53
charts/media/templates/prowlarr.yaml Normal file → Executable file
View File

@ -1,17 +1,3 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prowlarr-config
labels:
app: prowlarr
{{- include "media.labels" . | nindent 4 }}
spec:
accessModes: [ReadWriteOnce]
storageClassName: {{ .Values.storageClass }}
resources:
requests:
storage: {{ .Values.prowlarr.configSize }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
@ -31,6 +17,7 @@ spec:
labels:
app: prowlarr
spec:
{{- include "media.requireMacWorker" . | nindent 6 }}
containers:
- name: prowlarr
image: {{ .Values.prowlarr.image }}
@ -43,8 +30,9 @@ spec:
mountPath: /config
volumes:
- name: config
persistentVolumeClaim:
claimName: prowlarr-config
hostPath:
path: {{ .Values.serviceData }}/prowlarr/config
type: DirectoryOrCreate
---
apiVersion: v1
kind: Service
@ -58,3 +46,36 @@ spec:
ports:
- port: 9696
targetPort: 9696
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: prowlarr-internal
annotations:
kubernetes.io/ingress.class: traefik-internal
spec:
entryPoints:
- web
routes:
- match: Host(`prowlarr.{{ .Values.internalDomain }}`)
kind: Rule
services:
- name: prowlarr
port: 9696
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: prowlarr-internal-tls
annotations:
kubernetes.io/ingress.class: traefik-internal
spec:
entryPoints:
- websecure
routes:
- match: Host(`prowlarr.{{ .Values.internalDomain }}`)
kind: Rule
services:
- name: prowlarr
port: 9696
tls: {}

55
charts/media/templates/qbittorrent.yaml Normal file → Executable file
View File

@ -1,17 +1,3 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: qbittorrent-config
labels:
app: qbittorrent
{{- include "media.labels" . | nindent 4 }}
spec:
accessModes: [ReadWriteOnce]
storageClassName: {{ .Values.storageClass }}
resources:
requests:
storage: {{ .Values.qbittorrent.configSize }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
@ -31,6 +17,7 @@ spec:
labels:
app: qbittorrent
spec:
{{- include "media.requireMacWorker" . | nindent 6 }}
containers:
- name: qbittorrent
image: {{ .Values.qbittorrent.image }}
@ -52,11 +39,12 @@ spec:
mountPath: /data
volumes:
- name: config
persistentVolumeClaim:
claimName: qbittorrent-config
hostPath:
path: {{ .Values.serviceData }}/qbittorrent/config
type: DirectoryOrCreate
- name: data
hostPath:
path: {{ .Values.dogstore }}/sonarr/data
path: /dogstore/sonarr/data
type: DirectoryOrCreate
---
apiVersion: v1
@ -80,3 +68,36 @@ spec:
port: 34034
targetPort: 34034
protocol: UDP
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: qbittorrent-internal
annotations:
kubernetes.io/ingress.class: traefik-internal
spec:
entryPoints:
- web
routes:
- match: Host(`qbittorrent.{{ .Values.internalDomain }}`)
kind: Rule
services:
- name: qbittorrent
port: {{ .Values.qbittorrent.webuiPort }}
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: qbittorrent-internal-tls
annotations:
kubernetes.io/ingress.class: traefik-internal
spec:
entryPoints:
- websecure
routes:
- match: Host(`qbittorrent.{{ .Values.internalDomain }}`)
kind: Rule
services:
- name: qbittorrent
port: {{ .Values.qbittorrent.webuiPort }}
tls: {}

67
charts/media/templates/radarr.yaml Normal file → Executable file
View File

@ -1,17 +1,3 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: radarr-config
labels:
app: radarr
{{- include "media.labels" . | nindent 4 }}
spec:
accessModes: [ReadWriteOnce]
storageClassName: {{ .Values.storageClass }}
resources:
requests:
storage: {{ .Values.radarr.configSize }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
@ -31,6 +17,7 @@ spec:
labels:
app: radarr
spec:
{{- include "media.requireMacWorker" . | nindent 6 }}
containers:
- name: radarr
image: {{ .Values.radarr.image }}
@ -45,11 +32,12 @@ spec:
mountPath: /data
volumes:
- name: config
persistentVolumeClaim:
claimName: radarr-config
hostPath:
path: {{ .Values.serviceData }}/radarr/config
type: DirectoryOrCreate
- name: data
hostPath:
path: {{ .Values.dogstore }}/sonarr/data
path: /dogstore/sonarr/data # Media
type: DirectoryOrCreate
---
apiVersion: v1
@ -65,22 +53,35 @@ spec:
- port: 7878
targetPort: 7878
---
apiVersion: networking.k8s.io/v1
kind: Ingress
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: radarr
name: radarr-internal
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls.certresolver: {{ .Values.certResolver }}
kubernetes.io/ingress.class: traefik-internal
spec:
rules:
- host: radarr.{{ .Values.domain }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: radarr
port:
number: 7878
entryPoints:
- web
routes:
- match: Host(`radarr.{{ .Values.internalDomain }}`)
kind: Rule
services:
- name: radarr
port: 7878
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: radarr-internal-tls
annotations:
kubernetes.io/ingress.class: traefik-internal
spec:
entryPoints:
- websecure
routes:
- match: Host(`radarr.{{ .Values.internalDomain }}`)
kind: Rule
services:
- name: radarr
port: 7878
tls: {}

View File

@ -1,22 +1,10 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: seerr-config
labels:
app: seerr
spec:
accessModes: [ReadWriteOnce]
storageClassName: {{ .Values.storageClass }}
resources:
requests:
storage: {{ .Values.seerr.storageSize }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: seerr
labels:
app: seerr
{{- include "media.labels" . | nindent 4 }}
spec:
replicas: 1
strategy:
@ -29,14 +17,15 @@ spec:
labels:
app: seerr
spec:
nodeSelector:
node-role.kubernetes.io/control-plane: "true"
containers:
- name: seerr
image: {{ .Values.seerr.image }}
ports:
- containerPort: 5055
env:
- name: TZ
value: {{ .Values.tz | quote }}
{{- include "media.commonEnv" . | nindent 12 }}
readinessProbe:
httpGet:
path: /api/v1/settings/public
@ -56,13 +45,16 @@ spec:
mountPath: /app/config
volumes:
- name: config
persistentVolumeClaim:
claimName: seerr-config
hostPath:
path: {{ .Values.dogboxServiceData }}/seerr/config
type: Directory
---
apiVersion: v1
kind: Service
metadata:
name: seerr
labels:
app: seerr
spec:
selector:
app: seerr
@ -89,3 +81,36 @@ spec:
name: seerr
port:
number: 5055
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: seerr-internal
annotations:
kubernetes.io/ingress.class: traefik-internal
spec:
entryPoints:
- web
routes:
- match: Host(`seerr.{{ .Values.internalDomain }}`)
kind: Rule
services:
- name: seerr
port: 5055
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: seerr-internal-tls
annotations:
kubernetes.io/ingress.class: traefik-internal
spec:
entryPoints:
- websecure
routes:
- match: Host(`seerr.{{ .Values.internalDomain }}`)
kind: Rule
services:
- name: seerr
port: 5055
tls: {}

67
charts/media/templates/sonarr.yaml Normal file → Executable file
View File

@ -1,17 +1,3 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sonarr-config
labels:
app: sonarr
{{- include "media.labels" . | nindent 4 }}
spec:
accessModes: [ReadWriteOnce]
storageClassName: {{ .Values.storageClass }}
resources:
requests:
storage: {{ .Values.sonarr.configSize }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
@ -31,6 +17,7 @@ spec:
labels:
app: sonarr
spec:
{{- include "media.requireMacWorker" . | nindent 6 }}
containers:
- name: sonarr
image: {{ .Values.sonarr.image }}
@ -45,11 +32,12 @@ spec:
mountPath: /data
volumes:
- name: config
persistentVolumeClaim:
claimName: sonarr-config
hostPath:
path: {{ .Values.serviceData }}/sonarr/config
type: DirectoryOrCreate
- name: data
hostPath:
path: {{ .Values.dogstore }}/sonarr/data
path: /dogstore/sonarr/data # TV + Movies Library
type: DirectoryOrCreate
---
apiVersion: v1
@ -65,22 +53,35 @@ spec:
- port: 8989
targetPort: 8989
---
apiVersion: networking.k8s.io/v1
kind: Ingress
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: sonarr
name: sonarr-internal
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls.certresolver: {{ .Values.certResolver }}
kubernetes.io/ingress.class: traefik-internal
spec:
rules:
- host: sonarr.{{ .Values.domain }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: sonarr
port:
number: 8989
entryPoints:
- web
routes:
- match: Host(`sonarr.{{ .Values.internalDomain }}`)
kind: Rule
services:
- name: sonarr
port: 8989
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: sonarr-internal-tls
annotations:
kubernetes.io/ingress.class: traefik-internal
spec:
entryPoints:
- websecure
routes:
- match: Host(`sonarr.{{ .Values.internalDomain }}`)
kind: Rule
services:
- name: sonarr
port: 8989
tls: {}

5
charts/media/templates/unpackerr.yaml Normal file → Executable file
View File

@ -17,6 +17,7 @@ spec:
labels:
app: unpackerr
spec:
{{- include "media.requireMacWorker" . | nindent 6 }}
containers:
- name: unpackerr
image: {{ .Values.unpackerr.image }}
@ -85,9 +86,9 @@ spec:
volumes:
- name: data
hostPath:
path: {{ .Values.dogstore }}/sonarr/data
path: /dogstore/sonarr/data
type: DirectoryOrCreate
- name: logs
hostPath:
path: {{ .Values.dogstore }}/logs/unpackerr
path: {{ .Values.serviceData }}/unpackerr/logs
type: DirectoryOrCreate

14
charts/media/values.yaml Normal file → Executable file
View File

@ -5,13 +5,11 @@ tz: America/Los_Angeles
puid: "1000"
pgid: "1000"
dogstore: /dogstore
dogboxServiceData: /home/alvin/service-data
serviceData: /service-data
secretName: media-secrets
storageClass: longhorn
configStorageSize: 2Gi
plex:
image: plexinc/pms-docker:latest
advertiseIp: "https://plex.ratboo.me:443"
@ -19,24 +17,22 @@ plex:
sonarr:
image: ghcr.io/hotio/sonarr:latest
configSize: 2Gi
radarr:
image: ghcr.io/hotio/radarr:latest
configSize: 2Gi
bazarr:
image: lscr.io/linuxserver/bazarr:latest
configSize: 1Gi
prowlarr:
image: ghcr.io/hotio/prowlarr:latest
configSize: 1Gi
qbittorrent:
image: ghcr.io/hotio/qbittorrent:latest
configSize: 1Gi
webuiPort: 9191
unpackerr:
image: golift/unpackerr
seerr:
image: ghcr.io/seerr-team/seerr:latest

0
charts/metallb/Chart.lock Normal file → Executable file
View File

0
charts/metallb/Chart.yaml Normal file → Executable file
View File

6
charts/metallb/templates/pool.yaml Normal file → Executable file
View File

@ -3,6 +3,9 @@ apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: {{ .Values.pool.name }}
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "0"
spec:
autoAssign: false
addresses:
@ -14,6 +17,9 @@ apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: {{ .Values.pool.name }}
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "1"
spec:
ipAddressPools:
- {{ .Values.pool.name }}

0
charts/metallb/values.yaml Normal file → Executable file
View File

0
charts/paperless/Chart.yaml Normal file → Executable file
View File

0
charts/paperless/templates/paperless-ingressroute.yaml Normal file → Executable file
View File

0
charts/paperless/templates/postgres.yaml Normal file → Executable file
View File

0
charts/paperless/templates/redis.yaml Normal file → Executable file
View File

0
charts/paperless/templates/webserver.yaml Normal file → Executable file
View File

0
charts/paperless/values.yaml Normal file → Executable file
View File

0
charts/traefik-config/Chart.yaml Normal file → Executable file
View File

View File

4
charts/traefik-config/templates/traefik-config.yaml Normal file → Executable file
View File

@ -41,8 +41,8 @@ spec:
additionalVolumes:
- name: acme
hostPath:
path: /home/alvin/docker-volumes/.letsencrypt
type: DirectoryOrCreate
path: /dogstore/service-data/.letsencrypt
type: Directory
additionalVolumeMounts:
- name: acme
mountPath: /letsencrypt

0
charts/traefik-config/values.yaml Normal file → Executable file
View File

0
charts/traefik-internal/Chart.yaml Normal file → Executable file
View File

0
charts/traefik-internal/templates/tls-store.yaml Normal file → Executable file
View File

View File

0
charts/traefik-internal/values.yaml Normal file → Executable file
View File

2
charts/utils/Chart.yaml Normal file → Executable file
View File

@ -1,5 +1,5 @@
apiVersion: v2
name: utils
description: Utility services -- Zerobyte backup and Seerr media requests
description: Utility services -- Zerobyte backup
version: 0.1.0
type: application

View File

@ -0,0 +1,32 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: zerobyte
annotations:
kubernetes.io/ingress.class: traefik-internal
spec:
entryPoints:
- web
routes:
- match: Host(`zerobyte.{{ .Values.internalDomain }}`)
kind: Rule
services:
- name: zerobyte
port: 4096
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: zerobyte-tls
annotations:
kubernetes.io/ingress.class: traefik-internal
spec:
entryPoints:
- websecure
routes:
- match: Host(`zerobyte.{{ .Values.internalDomain }}`)
kind: Rule
services:
- name: zerobyte
port: 4096
tls: {}

22
charts/utils/templates/zerobyte.yaml Normal file → Executable file
View File

@ -1,16 +1,3 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: zerobyte-data
labels:
app: zerobyte
spec:
accessModes: [ReadWriteOnce]
storageClassName: {{ .Values.storageClass }}
resources:
requests:
storage: {{ .Values.zerobyte.storageSize }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
@ -38,12 +25,12 @@ spec:
- name: TZ
value: {{ .Values.tz | quote }}
- name: BASE_URL
value: http://{{ .Values.hostIp }}:4096
value: http://zerobyte.dog
- name: APP_SECRET
valueFrom:
secretKeyRef:
name: {{ .Values.zerobyte.secretName }}
key: APP_SECRET
key: ZEROBYTE_APP_SECRET
volumeMounts:
- name: data
mountPath: /var/lib/zerobyte
@ -52,8 +39,9 @@ spec:
readOnly: true
volumes:
- name: data
persistentVolumeClaim:
claimName: zerobyte-data
hostPath:
path: /home/alvin/service-data/zerobyte
type: DirectoryOrCreate
- name: localtime
hostPath:
path: /etc/localtime

6
charts/utils/values.yaml Normal file → Executable file
View File

@ -1,14 +1,10 @@
domain: ratboo.me
internalDomain: dog
certResolver: myresolver
tz: America/Los_Angeles
hostIp: "10.0.1.2"
storageClass: longhorn
zerobyte:
image: ghcr.io/nicotsx/zerobyte:v0.33
storageSize: 1Gi
secretName: zerobyte-secrets
seerr:
image: ghcr.io/seerr-team/seerr:latest
storageSize: 1Gi

0
secrets/secrets.enc.yaml Normal file → Executable file
View File

5
tasks.TODO Normal file
View File

@ -0,0 +1,5 @@
[] Backup zerobyte restic
[] setup litestream vs litefs https://chatgpt.com/c/69e93964-aa84-83ea-83f1-2cbd0125b748
[]
migrate sqllite to postgres