Compare commits
37
Commits
a48787f39e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0af896ab02 | ||
|
|
4238d82b40 | ||
|
|
adb5b9524b | ||
|
|
36ad140f66 | ||
|
|
36aba16de3 | ||
|
|
038b3396ab | ||
|
|
ffd5bde344 | ||
|
|
2f0e10a10b | ||
|
|
f3c0cf7060 | ||
|
|
b45f9a19a2 | ||
|
|
e9b199aca0 | ||
|
|
c1437d9199 | ||
|
|
9b5dff602a | ||
|
|
1f26627488 | ||
|
|
db3c8e4694 | ||
|
|
a8431d15c6 | ||
|
|
0663196fa0 | ||
|
|
5101df740d | ||
|
|
f56c6719da | ||
|
|
019039126a | ||
|
|
aa1bb9d838 | ||
|
|
faebef6c58 | ||
|
|
f79691f838 | ||
|
|
c7fce94b04 | ||
|
|
6f43b5e343 | ||
|
|
fc195c5379 | ||
|
|
e9574e3317 | ||
|
|
2ba48aa313 | ||
|
|
fbae9b0fc3 | ||
|
|
029ff24479 | ||
|
|
0b13c02899 | ||
|
|
4ddc5e0151 | ||
|
|
918435ac7c | ||
|
|
2d6e6a69e5 | ||
|
|
19ec6a37b2 | ||
|
|
397c4ec5f1 | ||
|
|
6e49cc860c |
@@ -0,0 +1,32 @@
|
||||
# Authentik is deployed from its official Helm chart, while this repository's
|
||||
# wrapper chart also owns its PostgreSQL cluster, backups, and persistent data.
|
||||
# The database is created first with Authentik disabled so the existing Docker
|
||||
# database can be restored before any new server or worker touches it.
|
||||
#
|
||||
# Deliberately NO resources-finalizer.argocd.argoproj.io here. This Application
|
||||
# owns identity data, and deleting its Git entry must not cascade into deleting
|
||||
# PostgreSQL or Authentik's uploaded files.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: authentik
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
|
||||
source:
|
||||
repoURL: https://tea.ahmza.com/ahmza.com/k8s-starter-pack.git
|
||||
targetRevision: main
|
||||
path: apps/authentik
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: authentik
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,16 @@
|
||||
# The upstream Authentik chart is initially disabled. This wrapper first
|
||||
# creates PostgreSQL, backup configuration, and persistent file storage so the
|
||||
# existing installation can be restored before Authentik starts.
|
||||
apiVersion: v2
|
||||
name: authentik-app
|
||||
description: Deploys Authentik with CloudNativePG and off-cluster backups
|
||||
version: 0.1.0
|
||||
type: application
|
||||
|
||||
dependencies:
|
||||
- name: authentik
|
||||
# Pin each release explicitly: Authentik database migrations require
|
||||
# sequential, verified upgrades through every release train.
|
||||
version: 2026.8.5
|
||||
repository: https://charts.goauthentik.io
|
||||
condition: authentikDeployment.enabled
|
||||
@@ -0,0 +1,18 @@
|
||||
# Authentik keeps uploaded icons and branding under /data. The PostgreSQL dump
|
||||
# does not include these files, so this separate Longhorn volume is restored
|
||||
# from the source VM's data/ directory before Authentik is enabled.
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: authentik-data
|
||||
namespace: {{ .Release.Namespace }}
|
||||
annotations:
|
||||
# Uploaded files are identity-service state. Never let Argo CD prune the
|
||||
# volume automatically because a template was renamed or removed.
|
||||
argocd.argoproj.io/sync-options: Prune=false
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
@@ -0,0 +1,103 @@
|
||||
# Backups live outside the cluster so a Longhorn or cluster-wide failure does
|
||||
# not remove both PostgreSQL and its recovery data. Credentials are produced by
|
||||
# the encrypted SopsSecret in this chart.
|
||||
apiVersion: barmancloud.cnpg.io/v1
|
||||
kind: ObjectStore
|
||||
metadata:
|
||||
name: authentik-backups
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
retentionPolicy: 30d
|
||||
configuration:
|
||||
destinationPath: s3://ahmza-talos-cnpg-backups/authentik/
|
||||
endpointURL: https://s3.eu-central-003.backblazeb2.com
|
||||
s3Credentials:
|
||||
accessKeyId:
|
||||
name: backblaze-b2-credentials
|
||||
key: ACCESS_KEY_ID
|
||||
secretAccessKey:
|
||||
name: backblaze-b2-credentials
|
||||
key: ACCESS_SECRET_KEY
|
||||
region:
|
||||
name: backblaze-b2-credentials
|
||||
key: AWS_REGION
|
||||
wal:
|
||||
compression: gzip
|
||||
# Require Backblaze's S3-compatible SSE-B2 encryption rather than
|
||||
# relying only on the bucket's default encryption policy.
|
||||
encryption: AES256
|
||||
maxParallel: 2
|
||||
data:
|
||||
compression: gzip
|
||||
encryption: AES256
|
||||
jobs: 2
|
||||
# Backblaze implements the S3 API but does not require every optional AWS
|
||||
# checksum behavior. These settings avoid incompatible optional checksums.
|
||||
instanceSidecarConfiguration:
|
||||
env:
|
||||
- name: AWS_REQUEST_CHECKSUM_CALCULATION
|
||||
value: when_required
|
||||
- name: AWS_RESPONSE_CHECKSUM_VALIDATION
|
||||
value: when_required
|
||||
---
|
||||
# One PostgreSQL instance is enough initially: Longhorn replicates its volume
|
||||
# across workers and can reattach it after a node failure. A second database
|
||||
# instance can be added later if shorter failover time justifies another full
|
||||
# copy of the data and WAL.
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: authentik-postgres
|
||||
namespace: {{ .Release.Namespace }}
|
||||
annotations:
|
||||
# Argo CD may update this database but must never delete it automatically
|
||||
# merely because a manifest or parent Application was removed from Git.
|
||||
argocd.argoproj.io/sync-options: Prune=false
|
||||
spec:
|
||||
instances: 1
|
||||
imageName: >-
|
||||
ghcr.io/cloudnative-pg/postgresql:16.13-202603020816-standard-trixie
|
||||
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: authentik
|
||||
owner: authentik
|
||||
|
||||
storage:
|
||||
# The default StorageClass is Longhorn. Omitting storageClass here keeps the
|
||||
# database on that established replicated storage path.
|
||||
size: 10Gi
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
|
||||
plugins:
|
||||
- name: barman-cloud.cloudnative-pg.io
|
||||
# CloudNativePG defaults this to true. Keeping it explicit prevents Argo
|
||||
# CD from reporting harmless drift between Git and the live resource.
|
||||
enabled: true
|
||||
isWALArchiver: true
|
||||
parameters:
|
||||
barmanObjectName: authentik-backups
|
||||
---
|
||||
# CloudNativePG cron expressions have six fields; the leading zero is seconds.
|
||||
# WAL is archived continuously, while this creates a full base backup nightly.
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: ScheduledBackup
|
||||
metadata:
|
||||
name: authentik-postgres-daily
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
schedule: "0 0 2 * * *"
|
||||
immediate: false
|
||||
backupOwnerReference: self
|
||||
cluster:
|
||||
name: authentik-postgres
|
||||
method: plugin
|
||||
pluginConfiguration:
|
||||
name: barman-cloud.cloudnative-pg.io
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: isindir.github.com/v1alpha3
|
||||
kind: SopsSecret
|
||||
metadata:
|
||||
name: authentik-secrets
|
||||
namespace: authentik
|
||||
spec:
|
||||
secretTemplates:
|
||||
- name: authentik-secrets
|
||||
data:
|
||||
AUTHENTIK_SECRET_KEY: ENC[AES256_GCM,data:xwd512Zty1VmOcc79UK2MxgLG5PaSApHhhwZRkqNz2w6HbLMSx+QvbODTfIZ0pq+HfQHxQMTY5FeX7t6TKR9toI5tG6B5202Bf85U9Lph4ssVfGC7fU/2WBsdELmtJIxxPjrzpcoQBfybOaR,iv:/J5e8Ej1KmuiEaOm8lZkikx7eStmScUA6lc0civVpdY=,tag:+gxczl/b3K4KnF8LcSkHKQ==,type:str]
|
||||
- name: backblaze-b2-credentials
|
||||
data:
|
||||
ACCESS_KEY_ID: ENC[AES256_GCM,data:4FjHGgdeDwbA22ekQr1jv2qDn+5xxEMqgB1wWanL8nXvt8EV,iv:csE6cZ0ifFFC0bq12eMGBwSuchH0u1lqbXflywoR9FY=,tag:xMhjAvxicV1nUpfBfJAKBw==,type:str]
|
||||
ACCESS_SECRET_KEY: ENC[AES256_GCM,data:XWHpbgZWbfl3REQDraRMUemcRvoACdaE2hjgkcYFgpZGkSZQ3jO7/fg4tDo=,iv:bGvTZy9N/ooxykqki0WkaUMI5qORbceSjprLnUu2k1A=,tag:67LlMIj+wvhj++Li/WtEAw==,type:str]
|
||||
AWS_REGION: ENC[AES256_GCM,data:TL4IGaKv2/LD1/PTL1JY0l/J+N8=,iv:JZtM2V0nwimMJaR5o7qK9Rx1FC6jTuFbknU/2qBrwYs=,tag:tUgyYeAYjEGxTAlnky679w==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4WDgwRkExNHhXa05hSkcy
|
||||
ZVVUam44a0pVR2hTZk5oOTdWdzkvSGlWODJVCnp6REpkUWVBV2g0MXNVKzdCVG1t
|
||||
aE9YQ25icitBWGJqQ3NtUjd1cCtRUk0KLS0tIHNPeFVkeFVGUlYxT3Nhd2UrVnl2
|
||||
NlhTZFV1YVRSM2JJYk1WRnRiUFppMXMKtqNp3OzKTeBKWXos0+wUmn6YGEDXubtc
|
||||
UPX7lOkLI6qaKg8+3HZV3x9yKgtaT32WF9faMBQhvxFMrhRR3UTTlQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1udejf5qa66rd6yvj5r8dz0fjnqfh0quf4t339c3cpqr26mkx2ydq3zel07
|
||||
encrypted_regex: ^(data|stringData)$
|
||||
lastmodified: "2026-10-16T15:59:11Z"
|
||||
mac: ENC[AES256_GCM,data:bqT+3/x/1DAgRRxjIXlxwNLiXT2f0rD7WxUk6RGBikOD0sZKBvCZVh4qTEVUIf2U6siZBgue39Gqa5eBlCCzx+rq/JxbKdsNd5klythOzmuaKSlOaIimSZLR9Ye1KJMGPkZI4G7eE5CAM17WJ5MMZDkhP9CtscRPEQ/AgHbVUSg=,iv:JqtI7n6uMkQ9lKr4oGGEA6czFtvoDrG510aLmltCdmI=,tag:1LU4a4Mlrxh8btQw4MY0eQ==,type:str]
|
||||
version: 3.13.3
|
||||
@@ -0,0 +1,135 @@
|
||||
# The trial server is enabled only after the source database and files have
|
||||
# been restored. Its worker and Ingress remain disabled until cutover, which
|
||||
# prevents background jobs and production traffic reaching this clone.
|
||||
authentikDeployment:
|
||||
enabled: true
|
||||
|
||||
authentik:
|
||||
global:
|
||||
# Server and worker share one ReadWriteOnce Longhorn volume. Recreate stops
|
||||
# the old Pods before replacements start, allowing the volume to move nodes
|
||||
# instead of leaving a rolling update blocked by a multi-attach error.
|
||||
deploymentStrategy:
|
||||
type: Recreate
|
||||
|
||||
# CloudNativePG generates this connection Secret. Referencing individual
|
||||
# keys avoids duplicating its generated database password in Git or SOPS.
|
||||
env:
|
||||
- name: AUTHENTIK_POSTGRESQL__HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-postgres-app
|
||||
key: host
|
||||
- name: AUTHENTIK_POSTGRESQL__PORT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-postgres-app
|
||||
key: port
|
||||
- name: AUTHENTIK_POSTGRESQL__NAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-postgres-app
|
||||
key: dbname
|
||||
- name: AUTHENTIK_POSTGRESQL__USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-postgres-app
|
||||
key: username
|
||||
- name: AUTHENTIK_POSTGRESQL__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-postgres-app
|
||||
key: password
|
||||
# `require` encrypts traffic to PostgreSQL without depending on a public
|
||||
# CA; CloudNativePG rotates its private in-cluster certificates itself.
|
||||
- name: AUTHENTIK_POSTGRESQL__SSLMODE
|
||||
value: require
|
||||
# Authentik 2026.8 only honors forwarded HTTPS and client-address headers
|
||||
# from explicit trusted proxies. This is the smallest aggregate covering
|
||||
# all current node Pod subnets, so ingress-nginx stays trusted if moved.
|
||||
- name: AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS
|
||||
value: 10.244.0.0/21
|
||||
|
||||
# The same Longhorn claim carries three restored source directories. Each
|
||||
# subPath is mounted where the upstream container expects it.
|
||||
volumes:
|
||||
- name: authentik-data
|
||||
persistentVolumeClaim:
|
||||
claimName: authentik-data
|
||||
volumeMounts:
|
||||
- name: authentik-data
|
||||
mountPath: /data
|
||||
subPath: data
|
||||
- name: authentik-data
|
||||
mountPath: /templates
|
||||
subPath: custom-templates
|
||||
- name: authentik-data
|
||||
mountPath: /certs
|
||||
subPath: certs
|
||||
|
||||
authentik:
|
||||
# This SOPS-managed Secret contains the source installation's exact secret
|
||||
# key, preserving encrypted configuration and existing identity semantics.
|
||||
existingSecret:
|
||||
secretName: authentik-secrets
|
||||
|
||||
# CloudNativePG supplies PostgreSQL; do not deploy the bundled database.
|
||||
postgresql:
|
||||
enabled: false
|
||||
|
||||
server:
|
||||
# One server is sufficient for this homelab; Kubernetes recreates it if the
|
||||
# Pod fails, while PostgreSQL and uploaded files live on persistent storage.
|
||||
replicas: 1
|
||||
# UID/GID 1000 matches the restored Docker files and satisfies this
|
||||
# namespace's restricted Pod Security policy without running as root.
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
containerSecurityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
# Route the LAN hostname through the cluster's shared ingress-nginx IP.
|
||||
# DNS-01 issuance and HTTPS routing were proven against Let's Encrypt's
|
||||
# staging service before switching to this browser-trusted issuer.
|
||||
ingress:
|
||||
enabled: true
|
||||
ingressClassName: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
hosts:
|
||||
- auth.ahmza.com
|
||||
tls:
|
||||
- secretName: authentik-tls
|
||||
hosts:
|
||||
- auth.ahmza.com
|
||||
|
||||
worker:
|
||||
# One worker handles scheduled jobs, source synchronization, email, and
|
||||
# management of Kubernetes-native outposts.
|
||||
replicas: 1
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
containerSecurityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
@@ -37,20 +37,28 @@ spec:
|
||||
volumeMounts:
|
||||
- name: bazarr-config
|
||||
mountPath: /config
|
||||
- name: cryo-media
|
||||
# Mount each media directory directly instead of using subPath.
|
||||
# The NFS export root is not listable by root-squashed clients, so
|
||||
# kubelet cannot safely prepare subPath mounts from that root.
|
||||
- name: movies
|
||||
mountPath: /movies
|
||||
subPath: Movies
|
||||
- name: cryo-media
|
||||
- name: tv
|
||||
mountPath: /tv
|
||||
subPath: TVShows
|
||||
- name: cryo-media
|
||||
- name: anime
|
||||
mountPath: /anime
|
||||
subPath: Anime
|
||||
volumes:
|
||||
- name: bazarr-config
|
||||
persistentVolumeClaim:
|
||||
claimName: bazarr-data
|
||||
- name: cryo-media
|
||||
- name: movies
|
||||
nfs:
|
||||
server: nas.ahmza.com
|
||||
path: /srv/media
|
||||
path: /srv/media/Movies
|
||||
- name: tv
|
||||
nfs:
|
||||
server: nas.ahmza.com
|
||||
path: /srv/media/TVShows
|
||||
- name: anime
|
||||
nfs:
|
||||
server: nas.ahmza.com
|
||||
path: /srv/media/Anime
|
||||
|
||||
@@ -5,7 +5,7 @@ metadata:
|
||||
name: bazarr
|
||||
namespace: bazarr
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-staging
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
|
||||
@@ -7,3 +7,11 @@ cert-manager:
|
||||
# separate manual step. `installCRDs` is the older, deprecated name for
|
||||
# this same setting.
|
||||
enabled: true
|
||||
|
||||
# Technitium serves a private, split-horizon ahmza.com zone, while DNS-01
|
||||
# challenge TXT records are created in the public Cloudflare zone. Use
|
||||
# public resolvers for ACME self-checks so cert-manager sees the same TXT
|
||||
# record Let's Encrypt sees; ordinary Pods still use normal cluster DNS.
|
||||
extraArgs:
|
||||
- --dns01-recursive-nameservers-only
|
||||
- --dns01-recursive-nameservers=1.1.1.1:53,8.8.8.8:53
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# CloudNativePG manages PostgreSQL databases as Kubernetes resources. The
|
||||
# companion Barman Cloud plugin sends database backups and transaction logs to
|
||||
# object storage; installing both here gives future apps one shared database
|
||||
# operator without forcing them to share one PostgreSQL server.
|
||||
#
|
||||
# Deliberately NO resources-finalizer.argocd.argoproj.io here. PostgreSQL
|
||||
# clusters managed by this operator hold application data, so removing this
|
||||
# Application must not cascade into a surprise deletion of database machinery.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: cloudnative-pg
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
|
||||
source:
|
||||
repoURL: https://tea.ahmza.com/ahmza.com/k8s-starter-pack.git
|
||||
targetRevision: main
|
||||
path: apps/cloudnative-pg
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: cnpg-system
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
# Both dependencies install cluster-wide CRDs. Server-side apply avoids
|
||||
# annotation-size limits and lets Kubernetes handle CRD fields safely.
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,19 @@
|
||||
# One umbrella chart installs the PostgreSQL operator and its current backup
|
||||
# plugin together. Applications create their own independent PostgreSQL
|
||||
# Cluster resources; these shared controllers hold no application data.
|
||||
apiVersion: v2
|
||||
name: cloudnative-pg-app
|
||||
description: >-
|
||||
Installs CloudNativePG and the Barman Cloud object-storage backup plugin
|
||||
version: 0.1.0
|
||||
type: application
|
||||
|
||||
dependencies:
|
||||
- name: cloudnative-pg
|
||||
# Pin versions so an unrelated Git sync cannot silently upgrade the
|
||||
# controller responsible for every PostgreSQL database in the cluster.
|
||||
version: 0.29.1
|
||||
repository: https://cloudnative-pg.github.io/charts
|
||||
- name: plugin-barman-cloud
|
||||
version: 0.8.1
|
||||
repository: https://cloudnative-pg.github.io/charts
|
||||
@@ -0,0 +1,27 @@
|
||||
# This Argo CD Application deploys Lidarr to manage the music library.
|
||||
# It uses Hotio's lightweight Lidarr container image.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: lidarr
|
||||
namespace: argocd
|
||||
# Omit the cascading finalizer so deleting this Application does not
|
||||
# automatically delete Lidarr's config-bearing PersistentVolumeClaim.
|
||||
spec:
|
||||
project: default
|
||||
|
||||
source:
|
||||
repoURL: https://tea.ahmza.com/ahmza.com/k8s-starter-pack.git
|
||||
targetRevision: main
|
||||
path: apps/lidarr
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: lidarr
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -0,0 +1,70 @@
|
||||
# Lidarr image maintained by Hotio: ghcr.io/hotio/lidarr.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: lidarr
|
||||
namespace: lidarr
|
||||
spec:
|
||||
# not critical infrastructure a single pod should be sufficient
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: lidarr
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: lidarr
|
||||
spec:
|
||||
containers:
|
||||
- name: lidarr
|
||||
image: ghcr.io/hotio/lidarr:release
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: PUID
|
||||
value: "1000"
|
||||
- name: PGID
|
||||
value: "1000"
|
||||
- name: UMASK
|
||||
value: "002"
|
||||
- name: TZ
|
||||
value: "Europe/London"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8686
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- name: lidarr-config
|
||||
mountPath: /config
|
||||
# Mount each media directory directly instead of using subPath.
|
||||
# The NFS export root is not listable by root-squashed clients, so
|
||||
# kubelet cannot safely prepare subPath mounts from that root.
|
||||
- name: music
|
||||
mountPath: /music
|
||||
- name: torrents
|
||||
mountPath: /data/torrents
|
||||
- name: usenet
|
||||
mountPath: /data/usenet
|
||||
- name: import
|
||||
mountPath: /data/import
|
||||
volumes:
|
||||
- name: lidarr-config
|
||||
persistentVolumeClaim:
|
||||
claimName: lidarr-data
|
||||
- name: music
|
||||
nfs:
|
||||
server: nas.ahmza.com
|
||||
path: /srv/media/Music
|
||||
- name: torrents
|
||||
nfs:
|
||||
server: nas.ahmza.com
|
||||
path: /srv/media/torrents
|
||||
- name: usenet
|
||||
nfs:
|
||||
server: nas.ahmza.com
|
||||
path: /srv/media/usenet
|
||||
- name: import
|
||||
nfs:
|
||||
server: nas.ahmza.com
|
||||
path: /srv/media/import
|
||||
@@ -0,0 +1,25 @@
|
||||
# Route lidarr.ahmza.com through ingress-nginx (docs/11-ingress.md).
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: lidarr
|
||||
namespace: lidarr
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- lidarr.ahmza.com
|
||||
# cert-manager keeps this up to date. nothing to do by hand
|
||||
secretName: lidarr-tls
|
||||
rules:
|
||||
- host: lidarr.ahmza.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: lidarr
|
||||
port:
|
||||
number: 8686
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: lidarr
|
||||
@@ -0,0 +1,13 @@
|
||||
# Store Lidarr's config on a Longhorn volume (see docs/06-longhorn.md).
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: lidarr-data
|
||||
namespace: lidarr
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
# Album artwork and the music database outgrew the original 2Gi volume.
|
||||
storage: 10Gi
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: lidarr
|
||||
namespace: lidarr
|
||||
spec:
|
||||
selector:
|
||||
app: lidarr
|
||||
ports:
|
||||
- name: http
|
||||
port: 8686
|
||||
targetPort: http
|
||||
@@ -47,7 +47,7 @@ spec:
|
||||
# The publishing workflow replaces this bootstrap tag with an
|
||||
# immutable OCI digest. Never deploy the mutable latest tag after
|
||||
# bootstrap: recording the digest makes rollbacks and audits reliable.
|
||||
image: tea.ahmza.com/ahmza.com/mcpacks-site@sha256:30a98b5835fb11ee183077cef0a638602a8b975c7e377abb125159f0eaed197e
|
||||
image: tea.ahmza.com/ahmza.com/mcpacks-site@sha256:c9983dd9c31562f6518388f4d66f308e00cc229264b248536db8b4be7e7845db
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# This ArgoCD application deploys soulsync to manage media discovery.
|
||||
# The Deployment pins a reviewed SoulSync release for repeatable GitOps syncs.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: soulsync
|
||||
namespace: argocd
|
||||
# skipping resources-finalizer.argocd.argoproj.io as soulsync will create configs
|
||||
spec:
|
||||
project: default
|
||||
|
||||
source:
|
||||
repoURL: https://tea.ahmza.com/ahmza.com/k8s-starter-pack.git
|
||||
targetRevision: main
|
||||
path: apps/soulsync
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: soulsync
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -0,0 +1,117 @@
|
||||
# Pin SoulSync so an upstream `latest` update cannot change the running app
|
||||
# without a reviewed Git commit.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: soulsync
|
||||
namespace: soulsync
|
||||
spec:
|
||||
# not critical infra, a single pod should suffice
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: soulsync
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: soulsync
|
||||
spec:
|
||||
containers:
|
||||
- name: soulsync
|
||||
image: boulderbadgedad/soulsync:3.5.9
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: PUID
|
||||
value: "1000"
|
||||
- name: PGID
|
||||
value: "1000"
|
||||
- name: UMASK
|
||||
value: "022"
|
||||
- name: PYTHONPATH
|
||||
value: "/app"
|
||||
- name: SOULSYNC_CONFIG_PATH
|
||||
value: "/app/config/config.json"
|
||||
- name: TZ
|
||||
value: "Europe/London"
|
||||
ports:
|
||||
# HTTPS terminates at ingress-nginx, which forwards the UI and both
|
||||
# OAuth callback paths to SoulSync's main web server on this port.
|
||||
- name: http
|
||||
containerPort: 8008
|
||||
protocol: TCP
|
||||
# Kubernetes ignores the Docker image's HEALTHCHECK, so define probes
|
||||
# here to keep traffic away until SoulSync has actually started and to
|
||||
# restart it if its web server later stops responding.
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 24
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
# Match the upstream Compose defaults: scheduling reserves enough for
|
||||
# normal operation while limits protect the rest of the cluster.
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
volumeMounts:
|
||||
- name: soulsync-db
|
||||
mountPath: /app/data
|
||||
- name: soulsync-config
|
||||
mountPath: /app/config
|
||||
- name: soulsync-logs
|
||||
mountPath: /app/logs
|
||||
- name: usenet-downloads
|
||||
mountPath: /data/usenet
|
||||
- name: torrent-downloads
|
||||
mountPath: /data/torrents
|
||||
- name: downloads
|
||||
mountPath: /app/downloads
|
||||
- name: music
|
||||
mountPath: /app/Transfer
|
||||
volumes:
|
||||
- name: soulsync-db
|
||||
persistentVolumeClaim:
|
||||
claimName: soulsync-db
|
||||
- name: soulsync-config
|
||||
persistentVolumeClaim:
|
||||
claimName: soulsync-config
|
||||
- name: soulsync-logs
|
||||
persistentVolumeClaim:
|
||||
claimName: soulsync-logs
|
||||
- name: usenet-downloads
|
||||
nfs:
|
||||
server: nas.ahmza.com
|
||||
path: /srv/media/usenet
|
||||
- name: torrent-downloads
|
||||
nfs:
|
||||
server: nas.ahmza.com
|
||||
path: /srv/media/torrents
|
||||
- name: downloads
|
||||
nfs:
|
||||
server: nas.ahmza.com
|
||||
path: /srv/media/torrents
|
||||
- name: music
|
||||
nfs:
|
||||
server: nas.ahmza.com
|
||||
path: /srv/media/Music
|
||||
@@ -0,0 +1,28 @@
|
||||
# routes for soulsync.ahmza.com through ingress-nginx (docs/11-ingress.md)
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: soulsync
|
||||
namespace: soulsync
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
spec:
|
||||
# Select ingress-nginx explicitly rather than relying on whichever ingress
|
||||
# controller happens to be configured as the cluster-wide default.
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- hosts:
|
||||
- soulsync.ahmza.com
|
||||
# cert-manager keeps this up to date. nothing to do by hand
|
||||
secretName: soulsync-tls
|
||||
rules:
|
||||
- host: soulsync.ahmza.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: soulsync
|
||||
port:
|
||||
number: 8008
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: soulsync
|
||||
@@ -0,0 +1,41 @@
|
||||
# Keep SoulSync's databases and downloaded artwork on Longhorn so they survive
|
||||
# Pod replacement (see docs/06-longhorn.md).
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: soulsync-db
|
||||
namespace: soulsync
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
---
|
||||
# Configuration can contain connection settings and OAuth tokens, so persist it
|
||||
# without putting those credentials in this public Git repository.
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: soulsync-config
|
||||
namespace: soulsync
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
---
|
||||
# Persistent logs remain available when the container is recreated and make
|
||||
# failures that happened before a restart easier to diagnose.
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: soulsync-logs
|
||||
namespace: soulsync
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: soulsync
|
||||
namespace: soulsync
|
||||
spec:
|
||||
selector:
|
||||
app: soulsync
|
||||
ports:
|
||||
- name: http
|
||||
port: 8008
|
||||
targetPort: http
|
||||
@@ -0,0 +1,41 @@
|
||||
# Cluster metrics collection for the external VictoriaMetrics and Grafana host.
|
||||
# This Application installs only collectors and Kubernetes exporters: metrics
|
||||
# are sent over NetBird to prd-vps-hel-01 and are not stored in this cluster.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: victoria-metrics
|
||||
namespace: argocd
|
||||
# The in-cluster pieces are stateless. Cascading deletion is safe because the
|
||||
# durable metrics database lives on the external VPS, not in this namespace.
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
|
||||
source:
|
||||
repoURL: https://tea.ahmza.com/ahmza.com/k8s-starter-pack.git
|
||||
targetRevision: main
|
||||
path: apps/victoria-metrics
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: monitoring
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
# node-exporter reads host kernel and filesystem metrics through hostPath,
|
||||
# hostNetwork, and hostPID. Scope the required privileged Pod Security
|
||||
# profile to this dedicated monitoring namespace.
|
||||
managedNamespaceMetadata:
|
||||
labels:
|
||||
pod-security.kubernetes.io/enforce: privileged
|
||||
pod-security.kubernetes.io/audit: privileged
|
||||
pod-security.kubernetes.io/warn: privileged
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
# VictoriaMetrics Operator CRDs are large. Server-side apply avoids the
|
||||
# client-side last-applied annotation size limit and handles CRDs first.
|
||||
- ServerSideApply=true
|
||||
@@ -0,0 +1,13 @@
|
||||
# Umbrella chart for the official VictoriaMetrics Kubernetes monitoring stack.
|
||||
# Argo CD renders this dependency from Git just like the existing Longhorn and
|
||||
# MetalLB wrapper charts.
|
||||
apiVersion: v2
|
||||
name: victoria-metrics-app
|
||||
description: Forwards Kubernetes and node metrics to external VictoriaMetrics
|
||||
version: 0.1.0
|
||||
dependencies:
|
||||
- name: victoria-metrics-k8s-stack
|
||||
# Pin the exact chart version so monitoring upgrades are explicit and
|
||||
# reviewable rather than changing during an unrelated Argo CD sync.
|
||||
version: 0.96.0
|
||||
repository: oci://ghcr.io/victoriametrics/helm-charts
|
||||
@@ -0,0 +1,30 @@
|
||||
# Scrapes Longhorn's own metrics (volume robustness, node and disk capacity)
|
||||
# so the external Grafana can alert when a volume becomes degraded or faulted.
|
||||
#
|
||||
# Every longhorn-manager Pod serves /metrics on its `manager` port (9500)
|
||||
# behind the longhorn-backend Service. A VMServiceScrape scrapes each Pod
|
||||
# behind that Service, so metrics from managers on both storage workers are
|
||||
# collected, not just whichever one a load-balanced request would hit.
|
||||
#
|
||||
# This lives in the victoria-metrics wrapper chart rather than the Longhorn
|
||||
# app because it is monitoring configuration, and it depends on the
|
||||
# VMServiceScrape CRD installed by this chart's operator. Sync wave "1" makes
|
||||
# Argo CD apply it only after that CRD exists on a fresh install.
|
||||
apiVersion: operator.victoriametrics.com/v1beta1
|
||||
kind: VMServiceScrape
|
||||
metadata:
|
||||
name: longhorn
|
||||
namespace: monitoring
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
spec:
|
||||
namespaceSelector:
|
||||
matchNames:
|
||||
- longhorn-system
|
||||
selector:
|
||||
matchLabels:
|
||||
app: longhorn-manager
|
||||
endpoints:
|
||||
- port: manager
|
||||
path: /metrics
|
||||
interval: 30s
|
||||
@@ -0,0 +1,89 @@
|
||||
# Values for the upstream victoria-metrics-k8s-stack dependency. This cluster
|
||||
# collects metrics only; durable storage and visualization run on the NetBird-
|
||||
# connected VPS prd-vps-hel-01.ahmza.com (NetBird address 100.71.24.9).
|
||||
victoria-metrics-k8s-stack:
|
||||
# Identify this source if more clusters write to the same database later.
|
||||
vmagent:
|
||||
enabled: true
|
||||
spec:
|
||||
scrapeInterval: 30s
|
||||
externalLabels:
|
||||
cluster: gitops-starter
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
|
||||
external:
|
||||
vm:
|
||||
# Queries are served from the VictoriaMetrics base URL. Remote write uses
|
||||
# its Prometheus-compatible ingestion endpoint. NetBird encrypts the path
|
||||
# between the Talos nodes and VPS, so no public endpoint is involved.
|
||||
#
|
||||
# The name resolves only through internal Technitium DNS (10.10.10.10),
|
||||
# where an authoritative zone maps it to the VPS's NetBird address. If
|
||||
# that address changes, update Technitium rather than this file.
|
||||
read:
|
||||
url: http://prd-vps-hel-01.ahmza.com:8428
|
||||
write:
|
||||
url: http://prd-vps-hel-01.ahmza.com:8428/api/v1/write
|
||||
|
||||
# Storage and UI already live on prd-vps-hel-01; do not duplicate them here.
|
||||
vmsingle:
|
||||
enabled: false
|
||||
vmcluster:
|
||||
enabled: false
|
||||
vmdistributed:
|
||||
enabled: false
|
||||
grafana:
|
||||
enabled: false
|
||||
|
||||
# Alert delivery is a separate follow-up once notification destinations are
|
||||
# chosen. Disabling it also avoids deploying rules with nowhere to notify.
|
||||
vmalert:
|
||||
enabled: false
|
||||
alertmanager:
|
||||
enabled: false
|
||||
defaultRules:
|
||||
enabled: false
|
||||
defaultDashboards:
|
||||
enabled: false
|
||||
|
||||
# One node-exporter Pod runs on every node, including tainted control planes.
|
||||
# Talos exposes the host root filesystem read-only; mount propagation is not
|
||||
# needed and can be rejected by immutable hosts, so keep it disabled.
|
||||
prometheus-node-exporter:
|
||||
enabled: true
|
||||
hostRootFsMount:
|
||||
enabled: true
|
||||
mountPropagation: None
|
||||
resources:
|
||||
requests:
|
||||
cpu: 20m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
memory: 128Mi
|
||||
|
||||
# Talos binds the scheduler and controller-manager metrics endpoints to
|
||||
# 127.0.0.1, and runs etcd as a host service rather than a Pod. Scraping them
|
||||
# from vmagent would require exposing those control-plane ports on the LAN,
|
||||
# so skip them; API server, kubelet, CoreDNS, and node metrics remain.
|
||||
kubeScheduler:
|
||||
enabled: false
|
||||
kubeControllerManager:
|
||||
enabled: false
|
||||
kubeEtcd:
|
||||
enabled: false
|
||||
|
||||
# kube-state-metrics reports desired/current Kubernetes object state (for
|
||||
# example unavailable Deployment replicas), complementing host metrics.
|
||||
kube-state-metrics:
|
||||
enabled: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: 20m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
+24
-3
@@ -32,6 +32,22 @@ on every node.
|
||||
| `10.10.10.204` | `talos-vba-ood` — worker02 (Proxmox VM on Titus) | 50000 | `https://10.10.10.204:50000` |
|
||||
| `10.10.10.210` | Cluster VIP (use this one, not a node's own address) | 6443 | `https://10.10.10.210:6443` |
|
||||
|
||||
## NetBird overlay
|
||||
|
||||
These addresses exist only inside the private NetBird overlay and are subject
|
||||
to NetBird access policies. All Talos nodes use the official
|
||||
`siderolabs/netbird` system extension.
|
||||
|
||||
| Address | What | Port(s) | URL(s) |
|
||||
|---|---|---|---|
|
||||
| `100.71.24.9` | `prd-vps-hel-01` — external Grafana and VictoriaMetrics host; internal name `prd-vps-hel-01.ahmza.com` (authoritative zone in Technitium) | 3000, 8428 | `http://prd-vps-hel-01.ahmza.com:3000`, `http://prd-vps-hel-01.ahmza.com:8428` |
|
||||
| `100.71.236.121` | `ingress1` — Caddy HTTP origin gateway used by NetBird Reverse Proxy; the WireGuard overlay encrypts this leg | 80 | `http://100.71.236.121` |
|
||||
| `100.71.121.67` | `talos-455-i3b` — control plane NetBird address | 50000 | `https://100.71.121.67:50000` |
|
||||
| `100.71.157.217` | `talos-9xy-iuk` — control plane NetBird address | 50000 | `https://100.71.157.217:50000` |
|
||||
| `100.71.242.40` | `talos-k1b-7j5` — control plane NetBird address | 50000 | `https://100.71.242.40:50000` |
|
||||
| `100.71.3.210` | `talos-rss-v5l` — worker01 NetBird address | 50000 | `https://100.71.3.210:50000` |
|
||||
| `100.71.29.51` | `talos-vba-ood` — worker02 NetBird address | 50000 | `https://100.71.29.51:50000` |
|
||||
|
||||
## MetalLB pool
|
||||
|
||||
This dedicated service range is advertised to the MikroTik at
|
||||
@@ -49,6 +65,7 @@ This dedicated service range is advertised to the MikroTik at
|
||||
| Address | What | Namespace | Port(s) | URL | Source |
|
||||
|---|---|---|---|---|---|
|
||||
| `10.10.60.10` | Argo CD UI/API (`argocd-server`) | `argocd` | 443 | `https://10.10.60.10` | Patched to `LoadBalancer` by hand — see [`docs/05-metallb.md`](05-metallb.md) |
|
||||
| `10.10.60.11` | Reserved for the Authentik LDAP Outpost used by Jellyfin | `authentik` | 636 | `ldaps://10.10.60.11:636` | Planned Authentik deployment; verify the address and TLS certificate before migrating Jellyfin |
|
||||
| `10.10.60.12` | ingress-nginx (shared, routes by hostname) | `ingress-nginx` | 80, 443 | `http://10.10.60.12`, `https://10.10.60.12` | [`apps/ingress-nginx/values.yaml`](../apps/ingress-nginx/values.yaml) — see [`docs/11-ingress.md`](11-ingress.md) |
|
||||
|
||||
Add a row here every time you add a new app with its own dedicated
|
||||
@@ -61,13 +78,17 @@ only when it's the protocol default (80 for `http://`, 443 for `https://`)
|
||||
|
||||
## Hostnames routed through ingress-nginx
|
||||
|
||||
Every real DNS record pointing at ingress-nginx's shared address
|
||||
(`10.10.60.12`), and which app it reaches. All hosted on Cloudflare.
|
||||
Every real hostname routed to ingress-nginx's shared address
|
||||
(`10.10.60.12`), and which app it reaches. Public DNS is hosted on
|
||||
Cloudflare; internal overrides are served by Technitium where noted.
|
||||
|
||||
| Hostname | App | Namespace | TLS | Source |
|
||||
|---|---|---|---|---|
|
||||
| `demo.ahmza.com` | Demo app | `demo` | Yes — `letsencrypt-prod`, real trusted cert (see [`docs/12-cert-manager.md`](12-cert-manager.md)) | [`apps/demo/ingress.yaml`](../apps/demo/ingress.yaml) |
|
||||
| `auth.ahmza.com` | Authentik identity provider | `authentik` | Yes — `letsencrypt-prod`, trusted certificate and browser login verified; Technitium routes LAN clients to `10.10.60.12`. For public traffic, NetBird terminates public TLS and proxies HTTP through its encrypted WireGuard overlay to Caddy on `ingress1`; Caddy then uses certificate-validated HTTPS with `auth.ahmza.com` as SNI and the HTTP `Host` when connecting to ingress-nginx at `10.10.60.12:443` | [`apps/authentik/values.yaml`](../apps/authentik/values.yaml) |
|
||||
| `mc.ahmza.com` | Packwiz modpack files | `mcpacks` | Yes — `letsencrypt-prod`, real trusted certificate | [`apps/mcpacks/ingress.yaml`](../apps/mcpacks/ingress.yaml) |
|
||||
| `bazarr.ahmza.com` | Bazarr subtitle manager | `bazarr` | Yes — `letsencrypt-prod`, real trusted certificate; Technitium overrides Cloudflare's NetBird route on the LAN | [`apps/bazarr/ingress.yaml`](../apps/bazarr/ingress.yaml) |
|
||||
| `soulsync.ahmza.com` | SoulSync media discovery | `soulsync` | Configured for `letsencrypt-prod`; verify the certificate after the first deployment; Technitium routes LAN clients to `10.10.60.12` | [`apps/soulsync/ingress.yaml`](../apps/soulsync/ingress.yaml) |
|
||||
|
||||
Add a row here every time you point a new hostname at this cluster (see
|
||||
[`docs/11-ingress.md`](11-ingress.md)). The DNS record itself lives in
|
||||
@@ -79,7 +100,7 @@ Addresses from the range above that aren't spoken for yet. Update this
|
||||
alongside the table above so it's obvious at a glance what's safe to hand
|
||||
to the next app.
|
||||
|
||||
- `10.10.60.11`, `10.10.60.13-10.10.60.250`
|
||||
- `10.10.60.13-10.10.60.250`
|
||||
|
||||
(`whoami`'s former `10.10.60.12` was reclaimed here after that app was
|
||||
removed as the docs/08 exercise — it now belongs to ingress-nginx instead.
|
||||
|
||||
+49
-42
@@ -5,18 +5,14 @@ This is the detailed, copy-pasteable companion to
|
||||
first for the explanations — this one is here so you have every exact
|
||||
command in one place once you understand what they do.
|
||||
|
||||
Throughout, replace `<NODE1_IP>`, `<NODE2_IP>`, `<NODE3_IP>` with your three
|
||||
machines' real IP addresses, and `<VIP_IP>` with the shared address you
|
||||
picked (with Claude's help) for the cluster's VIP — see
|
||||
[`docs/02-talos-bootstrap.md`](../docs/02-talos-bootstrap.md#the-vip-one-address-for-a-cluster-that-moves).
|
||||
Give each machine a DHCP reservation (a fixed IP tied to its MAC address)
|
||||
in your router or hypervisor before you start, so none of these addresses
|
||||
change under the cluster. Talos bakes them into certificates during setup,
|
||||
and changing one later means starting over for that node.
|
||||
Throughout, use the three control-plane and two worker addresses recorded in
|
||||
[`docs/network.md`](../docs/network.md), plus the cluster's shared VIP. Give
|
||||
each machine a DHCP reservation (a fixed IP tied to its MAC address) before
|
||||
installation so its address remains stable.
|
||||
|
||||
Steps 1–2 below run **once, total**. Steps 3 runs **once per node** (three
|
||||
times). Steps 4–6 run **once, total**, against whichever node you like
|
||||
(or, once it exists, the VIP).
|
||||
Steps 1–2 below run **once, total**. Step 3 runs **once per node** (five
|
||||
times, using the matching role configuration). Steps 4–6 run **once, total**;
|
||||
Talos API commands target a control-plane node, while Kubernetes uses the VIP.
|
||||
|
||||
All commands assume you're running them from the root of this repo.
|
||||
|
||||
@@ -26,7 +22,10 @@ All commands assume you're running them from the root of this repo.
|
||||
talosctl gen config gitops-starter https://<VIP_IP>:6443 \
|
||||
--output-dir _out \
|
||||
--config-patch-control-plane @talos/patch-controlplane.yaml \
|
||||
--config-patch-control-plane @talos/patch-longhorn.yaml
|
||||
--config-patch-control-plane @talos/patch-netbird.yaml \
|
||||
--config-patch-worker @talos/patch-worker.yaml \
|
||||
--config-patch-worker @talos/patch-longhorn.yaml \
|
||||
--config-patch-worker @talos/patch-netbird.yaml
|
||||
```
|
||||
|
||||
Note the cluster endpoint here is the **VIP**, not any one node's address
|
||||
@@ -38,13 +37,12 @@ This writes three files into `_out/` (already covered by this repo's
|
||||
`.gitignore` — they contain private keys and must never be committed):
|
||||
|
||||
- `controlplane.yaml` — the machine configuration for a control-plane
|
||||
node, with both patches baked in. Applied identically to all three
|
||||
nodes — there's no separate worker config in this setup, since every
|
||||
node is both.
|
||||
- `worker.yaml` — a plain worker config, generated automatically but
|
||||
unused here since every node in this repo's setup is both control plane
|
||||
and worker. Kept around for [`docs/09-growing-up.md`](../docs/09-growing-up.md),
|
||||
where a fourth-and-beyond node uses exactly this file.
|
||||
node, with the Raspberry Pi/VIP and non-secret NetBird patches baked in.
|
||||
It is applied identically to all three control-plane nodes.
|
||||
- `worker.yaml` — the machine configuration for the two Proxmox workers,
|
||||
with their role, Longhorn data-disk, and non-secret NetBird patches baked
|
||||
in. The NetBird setup key is deliberately absent from both generated files;
|
||||
enrollment uses a short-lived key applied separately and revoked afterward.
|
||||
- `talosconfig` — *your* client credentials for talking to the Talos API on
|
||||
this cluster (equivalent in spirit to a kubeconfig, but for Talos itself
|
||||
rather than Kubernetes).
|
||||
@@ -68,34 +66,39 @@ separate.
|
||||
|
||||
## 3. Apply the configuration — once per node
|
||||
|
||||
Boot each machine from your custom Talos ISO (see
|
||||
Boot each machine from its architecture-appropriate custom Talos image (see
|
||||
[docs/01-prerequisites.md](../docs/01-prerequisites.md)). Each boots into
|
||||
"maintenance mode" — alive enough to accept a configuration, with nothing
|
||||
installed yet. For **each** node in turn:
|
||||
"maintenance mode" — alive enough to accept configuration, with nothing
|
||||
installed yet. Inspect its disks first:
|
||||
|
||||
```sh
|
||||
talosctl get disks --insecure --nodes <NODE_IP>
|
||||
```
|
||||
|
||||
Confirm you see two disks: the one Talos will install itself to, and the
|
||||
≥900GB one `talos/patch-longhorn.yaml`'s `UserVolumeConfig` will claim for
|
||||
Longhorn. Then:
|
||||
For each Raspberry Pi control plane, confirm `/dev/mmcblk0` is the intended
|
||||
system disk, then apply the control-plane configuration:
|
||||
|
||||
```sh
|
||||
talosctl apply-config --insecure \
|
||||
--nodes <NODE_IP> \
|
||||
--nodes <CONTROL_PLANE_IP> \
|
||||
--file _out/controlplane.yaml
|
||||
```
|
||||
|
||||
`--insecure` is required for this one command only: the node has no
|
||||
certificates yet, so talosctl can't authenticate to it normally. This is
|
||||
the one moment during setup where you're trusting the network — do this on
|
||||
a network you control. The node will reboot, install Talos to its system
|
||||
disk, format and mount its second disk for Longhorn, and come back up
|
||||
running the configuration you gave it — now *with* certificates, so every
|
||||
command after this uses the secure path.
|
||||
For each Proxmox worker, confirm `/dev/sda` is the Talos system disk and
|
||||
`/dev/sdb` is the dedicated Longhorn disk selected by
|
||||
`talos/patch-longhorn.yaml`, then apply the worker configuration:
|
||||
|
||||
Repeat this for `<NODE1_IP>`, `<NODE2_IP>`, and `<NODE3_IP>` before moving on.
|
||||
```sh
|
||||
talosctl apply-config --insecure \
|
||||
--nodes <WORKER_IP> \
|
||||
--file _out/worker.yaml
|
||||
```
|
||||
|
||||
`--insecure` is required for this one command only: the node has no
|
||||
certificates yet, so talosctl cannot authenticate to it normally. This is
|
||||
the one moment during setup where you trust the local network. After applying
|
||||
configuration, the node installs Talos and returns with authenticated API
|
||||
access. Configure all three control planes and both workers before continuing.
|
||||
|
||||
## 4. Bootstrap etcd — once, total
|
||||
|
||||
@@ -115,12 +118,14 @@ existing one. Running it again against an existing cluster can corrupt it.
|
||||
## 5. Get your kubeconfig
|
||||
|
||||
```sh
|
||||
talosctl kubeconfig --nodes <VIP_IP> .
|
||||
talosctl kubeconfig --nodes <CONTROL_PLANE_IP> .
|
||||
```
|
||||
|
||||
Downloads a kubeconfig into the current directory (also gitignored —
|
||||
it grants full cluster access), pointed at the VIP rather than any one
|
||||
node. Merge it with `~/.kube/config` or point `KUBECONFIG` at it directly:
|
||||
Downloads a kubeconfig through a control-plane node's Talos API into the
|
||||
current directory (also gitignored — it grants full cluster access). The
|
||||
resulting Kubernetes configuration points at the cluster VIP rather than one
|
||||
specific control plane. Merge it with `~/.kube/config` or point `KUBECONFIG`
|
||||
at it directly:
|
||||
|
||||
```sh
|
||||
export KUBECONFIG=$PWD/kubeconfig
|
||||
@@ -132,15 +137,17 @@ export KUBECONFIG=$PWD/kubeconfig
|
||||
kubectl get nodes
|
||||
```
|
||||
|
||||
Expect to see **three** nodes, `Ready`, within a couple of minutes (it
|
||||
takes a moment for the default CNI — Talos ships Flannel out of the box —
|
||||
to come up and clear each node's networking checks).
|
||||
Expect to see **five** nodes, `Ready`, within a couple of minutes (it takes
|
||||
a moment for the default CNI — Talos ships Flannel out of the box — to clear
|
||||
each node's networking checks).
|
||||
|
||||
```
|
||||
```text
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
talos-... Ready control-plane 90s v1.3x.x
|
||||
talos-... Ready control-plane 85s v1.3x.x
|
||||
talos-... Ready control-plane 80s v1.3x.x
|
||||
talos-... Ready worker 75s v1.3x.x
|
||||
talos-... Ready worker 70s v1.3x.x
|
||||
```
|
||||
|
||||
If any node is stuck on `NotReady`, give it another minute before
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
# --- install image ---
|
||||
# All three control planes are Raspberry Pi 4 systems currently installed on
|
||||
# their microSD (`mmcblk0`). The Image Factory schematic contains the
|
||||
# `rpi_generic` ARM64 overlay needed to boot this hardware. Keep this custom
|
||||
# installer reference during upgrades or the board-specific boot support may
|
||||
# be lost.
|
||||
# `rpi_generic` ARM64 overlay needed to boot this hardware and the NetBird
|
||||
# system extension used for private overlay connectivity. Keep this custom
|
||||
# installer reference during upgrades or the board-specific boot support and
|
||||
# NetBird binary may be lost.
|
||||
#
|
||||
# --- vip ---
|
||||
# With three control-plane nodes, "the cluster's API server" isn't one
|
||||
@@ -38,7 +39,8 @@ cluster:
|
||||
machine:
|
||||
install:
|
||||
disk: /dev/mmcblk0
|
||||
image: factory.talos.dev/metal-installer/ee21ef4a5ef808a9b7484cc0dda0f25075021691c8c09a276591eedb638ea1f9:v1.13.8
|
||||
image: "factory.talos.dev/metal-installer/\
|
||||
d093bb68c464b87cc637765535dde5f68fbb040da44280617dd11a42d6c03988:v1.13.8"
|
||||
network:
|
||||
interfaces:
|
||||
# Raspberry Pi 4's built-in Ethernet interface is named `end0` by
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Persistent, non-secret configuration for the official SideroLabs NetBird
|
||||
# system extension. Apply this patch to every Talos node alongside its normal
|
||||
# role-specific patches.
|
||||
#
|
||||
# Enrollment still requires NB_SETUP_KEY, but that short-lived credential must
|
||||
# never be committed to this public repository. Supply it separately from a
|
||||
# silent shell prompt during first enrollment, revoke it after all nodes join,
|
||||
# then remove it from the live ExtensionServiceConfig.
|
||||
apiVersion: v1alpha1
|
||||
kind: ExtensionServiceConfig
|
||||
name: netbird
|
||||
environment:
|
||||
# Both endpoints use this installation's combined self-hosted NetBird URL.
|
||||
- NB_MANAGEMENT_URL=https://netbird.ahmza.com
|
||||
- NB_ADMIN_URL=https://netbird.ahmza.com
|
||||
# Talos mounts /etc read-only. Prevent NetBird from repeatedly trying to
|
||||
# rewrite resolv.conf and the OpenSSH client configuration there. Overlay
|
||||
# networking remains active; NetBird-managed DNS is deliberately disabled.
|
||||
- NB_DISABLE_DNS=true
|
||||
- NB_DISABLE_SSH_CONFIG=true
|
||||
@@ -8,9 +8,9 @@
|
||||
# claiming the wrong virtual disk.
|
||||
#
|
||||
# The custom Image Factory installer contains Longhorn's `iscsi-tools` and
|
||||
# `util-linux-tools` extensions plus `qemu-guest-agent`. Keep this schematic
|
||||
# reference during future upgrades; switching to the stock installer would
|
||||
# silently remove those extensions.
|
||||
# `util-linux-tools` extensions plus `qemu-guest-agent` and NetBird. Keep this
|
||||
# schematic reference during future upgrades; switching to the stock installer
|
||||
# would silently remove those extensions.
|
||||
#
|
||||
# Both VMs use `ens18`. Their DHCP-reserved MAC addresses keep worker01 at
|
||||
# 10.10.10.203 and worker02 at 10.10.10.204 while DHCP supplies their gateway
|
||||
@@ -26,7 +26,8 @@ machine:
|
||||
node.longhorn.io/create-default-disk: "true"
|
||||
install:
|
||||
disk: /dev/sda
|
||||
image: factory.talos.dev/metal-installer/88d1f7a5c4f1d3aba7df787c448c1d3d008ed29cfb34af53fa0df4336a56040b:v1.13.8
|
||||
image: "factory.talos.dev/metal-installer/\
|
||||
81fb1af7c03952593ba459c6c8b7fcc1268b6fe0cc80e6035ac629a741c183cc:v1.13.8"
|
||||
network:
|
||||
interfaces:
|
||||
- interface: ens18
|
||||
|
||||
Reference in New Issue
Block a user