adding new git-ops structure

This commit is contained in:
2026-09-02 19:53:17 -03:00
parent 39bda3c129
commit 7a560f6f97
50 changed files with 4693 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
chart: true
metadata:
name: alloy
namespace: argocd
finalizers: [resources-finalizer.argocd.argoproj.io]
spec:
project: default
destination:
server: https://kubernetes.default.svc
namespace: alloy
syncPolicy:
automated: { prune: true, selfHeal: true }
syncOptions: [CreateNamespace=true, ServerSideApply=true]
source:
repoURL: https://grafana.github.io/helm-charts
chart: alloy
targetRevision: 1.12.1
helm:
valuesObject:
controller:
type: daemonset
alloy:
clustering: { enabled: false }
mounts:
varlog: true
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
configMap:
create: true
content: |-
discovery.kubernetes "all_pods" {
role = "pod"
selectors {
role = "pod"
field = "spec.nodeName=" + coalesce(env("HOSTNAME"), constants.hostname)
}
}
discovery.relabel "all_pods" {
targets = discovery.kubernetes.all_pods.targets
rule {
source_labels = ["__meta_kubernetes_namespace"]
target_label = "namespace"
}
rule {
source_labels = ["__meta_kubernetes_pod_name"]
target_label = "pod"
}
rule {
source_labels = ["__meta_kubernetes_pod_container_name"]
target_label = "container"
}
rule {
source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_name"]
target_label = "app"
}
}
loki.source.kubernetes "all_logs" {
targets = discovery.relabel.all_pods.output
forward_to = [loki.write.main.receiver]
}
discovery.kubernetes "shared_pods" {
role = "pod"
selectors {
role = "pod"
field = "metadata.namespace=chacal"
}
}
discovery.relabel "shared_pods" {
targets = discovery.kubernetes.shared_pods.targets
rule {
source_labels = ["__meta_kubernetes_namespace"]
target_label = "namespace"
}
rule {
source_labels = ["__meta_kubernetes_pod_name"]
target_label = "pod"
}
rule {
source_labels = ["__meta_kubernetes_pod_container_name"]
target_label = "container"
}
rule {
source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_name"]
target_label = "app"
}
}
loki.source.kubernetes "shared_logs" {
targets = discovery.relabel.shared_pods.output
forward_to = [loki.write.shared.receiver]
}
loki.write "main" {
endpoint {
url = "http://loki.monitoring.svc.cluster.local:3100/loki/api/v1/push"
headers = {
"X-Scope-OrgID" = "main",
}
}
}
loki.write "shared" {
endpoint {
url = "http://loki.monitoring.svc.cluster.local:3100/loki/api/v1/push"
headers = {
"X-Scope-OrgID" = "chacal",
}
}
}
+99
View File
@@ -0,0 +1,99 @@
# Auth (Authentik + Kyverno SSO annotation policy)
Draft manifests only — nothing applied yet. Convention: any Ingress annotated
with `use-sso-auth: "true"` gets the Authentik forward-auth annotations injected
by the Kyverno ClusterPolicy (`auth/sso-auth-policy.yaml`).
## Deploy order
1. **Authentik** (`auth/authentik.yaml`) — IdP + embedded outpost. Needs a
Postgres user/DB on postgresql.haven first (see below), plus secrets from
Vaultwarden/ESO.
2. **Proxy provider** — create in Authentik UI (or via API) once it's up:
one provider per app you want header-auth'd, mode "forward_auth (single
app)", or a domain-level provider with the embedded outpost.
3. **Kyverno** (`auth/kyverno-helm.yaml`) — policy engine.
4. **SSO policy** (`auth/sso-auth-policy.yaml`) — expands `use-sso-auth: "true"`
into `auth-url` / `auth-signin` / `auth-response-headers` annotations.
5. Annotate ingresses: `kubectl annotate ingress <name> -n <ns> use-sso-auth=true`
and mirror the change in this repo.
## Postgres bootstrap (one-time, on postgresql.haven)
```sql
CREATE USER authentik WITH PASSWORD '<from-vaultwarden>';
CREATE DATABASE authentik OWNER authentik;
```
## Notes
- Outpost URL used everywhere is `https://auth.haven` — add DNS + ingress for
the outpost before annotating anything.
- Apps that should *consume* the injected `X-Authentik-*` headers (Grafana auth
proxy, Paperless remote-user, OpenWebUI trusted headers) need their own env
changes; those are app-side, not covered by the policy.
- API/WebSocket-heavy apps (arr stack, qBittorrent) should NOT get the
annotation on API paths — either skip the annotation or exclude paths.
## Which apps get the annotation (scoping)
Three tiers, applied per ingress — never blanket:
1. **OIDC tier** (no annotation, wire native OIDC in the app instead):
grafana, paperless, affine, openwebui, vaultwarden, beszel, slink.
Proper logout, group mapping, zero API breakage.
2. **Forward-auth tier** (`use-sso-auth: "true"`): browser-only utilities with
no real auth — it-tools, notepad, searxng, homepage, archivebox,
stirlingpdf, file-nginx, code-config, havenllo, own apps (chacal,
mindforge) if wanted.
3. **Hands off** (built-in auth is fine, forward-auth breaks clients):
sonarr, radarr, prowlarr, qbittorrent, adguard, changedetection,
uptimekuma, cloudreve (WebDAV), jellyfin (clients can't do redirects —
only the community SSO plugin route exists).
## Pre-annotation checklist (per new app)
Forward-auth breakage is loud and immediate if you look in the right places.
Before annotating any real hostname:
1. **Ask: what talks to this app that isn't a human in a browser?**
Mobile/desktop apps with own login, API-key consumers, push/webhook
receivers, cronjobs curling through the ingress, WebDAV/RSS → any of
those = hands off (tier 3) or exclude paths. "Just me in a browser" =
safe.
2. **Canary first:** create a scratch ingress for the same service
(`<app>-test.haven`) with the annotation; leave the original untouched
for ~a week. Forgotten integrations keep hitting the original and only
break the canary — instant rollback
(`kubectl annotate ingress <n> -n <ns> use-sso-auth-`).
3. **Grep the docs** (30s): "reverse proxy", "trusted header", "API key",
"webhook", "basic auth". A "running behind a reverse proxy" doc section
is where landmines are documented.
4. **Header-trust hygiene:** apps consuming `X-Authentik-*` must be
reachable ONLY through the ingress (no NodePort/exposed port, otherwise
LAN clients can forge headers straight to the pod). Keep built-in local
login enabled; never set SSO-only mode.
## Files
- `kyverno-helm.yaml` — Kyverno install values
- `sso-auth-policy.yaml` — ClusterPolicy: `use-sso-auth` → nginx auth annotations
- `authentik.yaml` — Authentik server/worker + outpost, Postgres PVC, ingress
## Status / when to actually deploy this (decision from 2026-08-28)
NOT deployed — deliberate. Sole user, low login frequency, everything
reachable via wg-easy, so SSO solves a problem that doesn't exist. The
`*.ivanch.me` public ingresses were reviewed instead: exposure without usage
is pure risk, and the right fix is removing exposure (VPN-only), not adding
an IdP.
Pull the trigger only if one of these becomes true:
1. A second person uses the homelab regularly (SSO value scales with users).
2. Something must be exposed that can't sit behind VPN (third-party
webhooks/callbacks) — then public tier gets `use-sso-auth` + 2FA.
3. Login friction on the OIDC apps (grafana/paperless/affine/openwebui/
vaultwarden/beszel/slink) actually annoys on a weekly basis.
Until then this folder is a shelf-ready draft; cost of keeping it is zero.
+244
View File
@@ -0,0 +1,244 @@
# Authentik — IdP + embedded outpost (proxy provider forward-auth).
# Draft — do NOT apply as-is:
# - secret values are placeholders; create them from Vaultwarden/ESO first
# - requires the authentik user/DB on postgresql.haven (see README.md)
# - requires DNS record for auth.haven -> ingress IP (user manages DNS)
---
apiVersion: v1
kind: Secret
metadata:
name: authentik-secrets
namespace: auth
type: Opaque
stringData:
postgres_password: "CHANGE_ME" # Vaultwarden -> ESO later
secret_key: "CHANGE_ME" # `openssl rand -base64 60`
bootstrap_password: "CHANGE_ME" # initial akadmin password
bootstrap_token: "CHANGE_ME" # outpost token
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: authentik-media
namespace: auth
spec:
accessModes: [ReadWriteOnce]
storageClassName: nfs-fast-client # verified: exists in cluster (nfs-client is also default)
resources:
requests:
storage: 2Gi
---
# Server (web + API)
apiVersion: apps/v1
kind: Deployment
metadata:
name: authentik-server
namespace: auth
spec:
replicas: 1
selector:
matchLabels: { app: authentik, component: server }
template:
metadata:
labels: { app: authentik, component: server }
spec:
containers:
- name: server
image: ghcr.io/goauthentik/server:2026.8
args: [server]
env:
- name: AUTHENTIK_SECRET_KEY
valueFrom:
{ secretKeyRef: { name: authentik-secrets, key: secret_key } }
- name: AUTHENTIK_POSTGRESQL__HOST
value: postgresql.haven
- name: AUTHENTIK_POSTGRESQL__NAME
value: authentik
- name: AUTHENTIK_POSTGRESQL__USER
value: authentik
- name: AUTHENTIK_POSTGRESQL__PASSWORD
valueFrom:
{
secretKeyRef:
{ name: authentik-secrets, key: postgres_password },
}
- name: AUTHENTIK_BOOTSTRAP_PASSWORD
valueFrom:
{
secretKeyRef:
{ name: authentik-secrets, key: bootstrap_password },
}
- name: AUTHENTIK_BOOTSTRAP_TOKEN
valueFrom:
{
secretKeyRef:
{ name: authentik-secrets, key: bootstrap_token },
}
- name: AUTHENTIK_ERROR_REPORTING__ENABLED
value: "false"
ports:
- { containerPort: 9000, name: http }
- { containerPort: 9443, name: https }
readinessProbe:
httpGet: { path: /-/health/ready/, port: 9000 }
initialDelaySeconds: 20
periodSeconds: 10
livenessProbe:
httpGet: { path: /-/health/live/, port: 9000 }
initialDelaySeconds: 40
periodSeconds: 20
resources:
requests: { cpu: 250m, memory: 512Mi }
limits: { memory: 1Gi, cpu: 1000m }
volumeMounts:
- { name: media, mountPath: /media }
volumes:
- name: media
persistentVolumeClaim: { claimName: authentik-media }
---
# Worker (policies, outpost management, scheduled tasks)
apiVersion: apps/v1
kind: Deployment
metadata:
name: authentik-worker
namespace: auth
spec:
replicas: 1
selector:
matchLabels: { app: authentik, component: worker }
template:
metadata:
labels: { app: authentik, component: worker }
spec:
containers:
- name: worker
image: ghcr.io/goauthentik/server:2026.8
args: [worker]
env: # same env as server; kept duplicated for a flat draft manifest
- name: AUTHENTIK_SECRET_KEY
valueFrom:
{ secretKeyRef: { name: authentik-secrets, key: secret_key } }
- name: AUTHENTIK_POSTGRESQL__HOST
value: postgresql.haven
- name: AUTHENTIK_POSTGRESQL__NAME
value: authentik
- name: AUTHENTIK_POSTGRESQL__USER
value: authentik
- name: AUTHENTIK_POSTGRESQL__PASSWORD
valueFrom:
{
secretKeyRef:
{ name: authentik-secrets, key: postgres_password },
}
- name: AUTHENTIK_BOOTSTRAP_PASSWORD
valueFrom:
{
secretKeyRef:
{ name: authentik-secrets, key: bootstrap_password },
}
- name: AUTHENTIK_BOOTSTRAP_TOKEN
valueFrom:
{
secretKeyRef:
{ name: authentik-secrets, key: bootstrap_token },
}
- name: AUTHENTIK_ERROR_REPORTING__ENABLED
value: "false"
resources:
requests: { cpu: 200m, memory: 512Mi }
limits: { memory: 1Gi, cpu: 1000m }
---
apiVersion: v1
kind: Service
metadata:
name: authentik
namespace: auth
spec:
selector: { app: authentik, component: server }
ports:
- { name: http, port: 80, targetPort: 9000 }
---
# Embedded outpost — runs the proxy providers; this is what ingress-nginx
# calls for /auth/nginx on every use-sso-auth ingress.
apiVersion: apps/v1
kind: Deployment
metadata:
name: authentik-outpost
namespace: auth
spec:
replicas: 1
selector:
matchLabels: { app: authentik, component: outpost }
template:
metadata:
labels: { app: authentik, component: outpost }
spec:
containers:
- name: outpost
image: ghcr.io/goauthentik/proxy:2026.8
env:
- name: AUTHENTIK_HOST
value: http://authentik.auth.svc.cluster.local
- name: AUTHENTIK_INSECURE
value: "true"
- name: AUTHENTIK_TOKEN
valueFrom:
{
secretKeyRef:
{ name: authentik-secrets, key: bootstrap_token },
}
ports:
- { containerPort: 9000, name: http }
readinessProbe:
httpGet: { path: /outpost.goauthentik.io/ping, port: 9000 }
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests: { cpu: 200m, memory: 512Mi }
limits: { memory: 1Gi, cpu: 1000m }
---
apiVersion: v1
kind: Service
metadata:
name: authentik-outpost
namespace: auth
spec:
selector: { app: authentik, component: outpost }
ports:
- { name: http, port: 80, targetPort: 9000 }
---
# SSO portal (user-facing login) — auth.haven
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: authentik
namespace: auth
spec:
ingressClassName: traefik
rules:
- host: auth.haven
http:
paths:
- path: /
pathType: Prefix
backend: { service: { name: authentik, port: { number: 80 } } }
---
# Outpost route — the /outpost.goauthentik.io path must resolve on the same
# host the protected apps redirect to. Kept as a separate ingress so it can
# also be attached to other hosts later if needed (do NOT give it
# use-sso-auth — that would create an auth loop).
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: authentik-outpost
namespace: auth
spec:
ingressClassName: traefik
rules:
- host: auth.haven
http:
paths:
- path: /outpost.goauthentik.io
pathType: Prefix
backend:
{ service: { name: authentik-outpost, port: { number: 80 } } }
+24
View File
@@ -0,0 +1,24 @@
# Kyverno — policy engine. Install with:
# helm repo add kyverno https://kyverno.github.io/kyverno
# helm upgrade --install kyverno kyverno/kyverno -n kyverno --create-namespace -f auth/kyverno-helm.yaml
# The only consumer of Kyverno here is the use-sso-auth mutation policy;
# if you later drop it, Kyverno can be removed with no other impact.
admissionController:
replicas: 1
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
backgroundController:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 128Mi
cleanupController:
enabled: false
reportsController:
enabled: false
+39
View File
@@ -0,0 +1,39 @@
# ClusterPolicy: expands the single `use-sso-auth: "true"` Ingress annotation
# into the ingress-nginx forward-auth annotations pointed at the Authentik
# embedded outpost.
#
# Test before applying to real ingresses:
# kubectl apply -f auth/sso-auth-policy.yaml
# kubectl annotate ingress -n default homepage use-sso-auth=true --dry-run=server -o yaml # check annotations get injected
# # remove the test annotation afterwards
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: inject-sso-auth
spec:
validationFailureAction: Audit
background: false
rules:
- name: authentik-forward-auth
match:
any:
- resources:
kinds: [Ingress]
preconditions:
all:
- key: "{{ request.object.metadata.annotations.\"use-sso-auth\" || '' }}"
operator: Equals
value: "true"
mutate:
patchStrategicMerge:
metadata:
annotations:
nginx.ingress.kubernetes.io/auth-url: https://auth.haven/outpost.goauthentik.io/auth/nginx
nginx.ingress.kubernetes.io/auth-signin: https://auth.haven/outpost.goauthentik.io/start?rd=$scheme://$http_host$escaped_request_uri
# NOTE: no auth-snippet used — ingress-nginx 1.15 rejects snippet
# annotations by default (allow-snippet-annotations=false, your
# controller ConfigMap is empty so it uses the default).
# ingress-nginx already forwards X-Original-URL to the auth
# backend automatically, which is all Authentik needs.
nginx.ingress.kubernetes.io/auth-response-headers: X-Authentik-Username,X-Authentik-Email,X-Authentik-Groups,X-Authentik-Name,X-Authentik-Metadata
+128
View File
@@ -0,0 +1,128 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: affine
namespace: cloud
labels:
app: affine
spec:
replicas: 1
selector:
matchLabels:
app: affine
strategy:
type: Recreate
template:
metadata:
labels:
app: affine
spec:
containers:
- name: affine
image: ghcr.io/toeverything/affine:stable
imagePullPolicy: Always
command:
[
"sh",
"-c",
"node ./scripts/self-host-predeploy.js && node ./dist/main.js",
]
env:
- name: AFFINE_CONFIG_PATH
value: "/root/.affine/config"
- name: AFFINE_SERVER_EXTERNAL_URL
value: "http://affine.haven"
- name: AFFINE_SERVER_HOST
value: "0.0.0.0"
- name: AFFINE_SERVER_PORT
value: "3010"
- name: AFFINE_SERVER_HTTPS
value: "false"
- name: AFFINE_SERVER_SUBPATH
value: "/"
- name: AFFINE_ENABLE_SYNC_FROM_STARTUP
value: "true"
- name: DATABASE_URL
value: "postgres://affine:affine@postgresql.haven:5432/affine"
- name: REDIS_SERVER_HOST
value: "redis.haven"
- name: REDIS_SERVER_PORT
value: "6379"
ports:
- containerPort: 3010
name: http
readinessProbe:
httpGet:
path: /
port: 3010
initialDelaySeconds: 20
periodSeconds: 10
failureThreshold: 6
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 2000m
memory: 2Gi
volumeMounts:
- name: config
mountPath: /root/.affine/config
volumes:
- name: config
persistentVolumeClaim:
claimName: affine-config
terminationGracePeriodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
name: affine
namespace: cloud
labels:
app: affine
spec:
type: ClusterIP
selector:
app: affine
ports:
- name: http
port: 3010
protocol: TCP
targetPort: http
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: affine-config
namespace: cloud
annotations:
nfs.io/storage-path: "affine-config"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: affine
namespace: cloud
labels:
app: affine
spec:
rules:
- host: affine.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: affine
port:
number: 3010
+170
View File
@@ -0,0 +1,170 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudreve
namespace: cloud
spec:
replicas: 1
selector:
matchLabels:
app: cloudreve
template:
metadata:
labels:
app: cloudreve
spec:
containers:
- name: cloudreve
image: cloudreve/cloudreve:v4
imagePullPolicy: Always
ports:
- containerPort: 5212
name: http
- containerPort: 6888
name: slave-tcp
protocol: TCP
- containerPort: 6888
name: slave-udp
protocol: UDP
env:
- name: CR_CONF_Database.Type
value: "postgres"
- name: CR_CONF_Database.Host
value: "postgresql.haven"
- name: CR_CONF_Database.Port
value: "5432"
- name: CR_CONF_Database.User
valueFrom:
secretKeyRef:
name: cloudreve-secret
key: DB_USER
- name: CR_CONF_Database.Name
valueFrom:
secretKeyRef:
name: cloudreve-secret
key: DB_NAME
- name: CR_CONF_Database.Password
valueFrom:
secretKeyRef:
name: cloudreve-secret
key: DB_PASSWORD
- name: CR_CONF_Database.SSLMode
value: "disable"
- name: CR_CONF_Redis.Server
value: ""
- name: CR_CONF_Redis.Password
valueFrom:
secretKeyRef:
name: cloudreve-secret
key: REDIS_PASSWORD
volumeMounts:
- name: cloudreve-data
mountPath: /cloudreve/data
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "1Gi"
volumes:
- name: cloudreve-data
nfs:
server: 192.168.15.99
path: /export/Storage/Cloud
---
apiVersion: v1
kind: Service
metadata:
name: cloudreve
namespace: cloud
spec:
type: ClusterIP
selector:
app: cloudreve
ports:
- port: 5212
targetPort: 5212
name: http
---
apiVersion: v1
kind: Service
metadata:
name: cloudreve-slave
namespace: cloud
spec:
type: ClusterIP
selector:
app: cloudreve
ports:
- port: 6888
targetPort: 6888
name: slave-tcp
protocol: TCP
- port: 6888
targetPort: 6888
name: slave-udp
protocol: UDP
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: cloudreve-data
namespace: cloud
annotations:
nfs.io/storage-path: "cloudreve-data"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
limits:
storage: 50Gi
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cloudreve
namespace: cloud
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
spec:
rules:
- host: cloud.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: cloudreve
port:
number: 5212
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cloudreve-public
namespace: cloud
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
spec:
rules:
- host: cloud.ivanch.me
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: cloudreve
port:
number: 5212
+135
View File
@@ -0,0 +1,135 @@
# 1) Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: slink
namespace: cloud
spec:
replicas: 1
selector:
matchLabels:
app: slink
template:
metadata:
labels:
app: slink
spec:
containers:
- name: slink
image: anirdev/slink:latest
imagePullPolicy: Always
securityContext:
runAsUser: 1000
runAsGroup: 1000
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: ORIGIN
value: "http://slink.haven"
- name: TZ
value: "America/Sao_Paulo"
- name: USER_APPROVAL_REQUIRED
value: "true"
- name: USER_PASSWORD_MIN_LENGTH
value: "8"
- name: USER_PASSWORD_REQUIREMENTS
value: "15"
- name: ADMIN_USERNAME
valueFrom:
secretKeyRef:
name: slink-secret
key: ADMIN_USERNAME
- name: ADMIN_EMAIL
valueFrom:
secretKeyRef:
name: slink-secret
key: ADMIN_EMAIL
- name: ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: slink-secret
key: ADMIN_PASSWORD
- name: IMAGE_MAX_SIZE
value: "50M"
- name: IMAGE_STRIP_EXIF_METADATA
value: "true"
- name: IMAGE_COMPRESSION_QUALITY
value: "90"
- name: STORAGE_PROVIDER
value: "local"
ports:
- containerPort: 3000
name: slink-port
resources:
requests:
cpu: "100m"
memory: "64Mi"
limits:
cpu: "500m"
memory: "512Mi"
volumeMounts:
- name: slink-data
mountPath: /app/var/data
- name: slink-data
mountPath: /app/slink/images
volumes:
- name: slink-data
persistentVolumeClaim:
claimName: slink-data
---
# 2) Service
apiVersion: v1
kind: Service
metadata:
name: slink
namespace: cloud
spec:
type: ClusterIP
selector:
app: slink
ports:
- port: 3000
targetPort: slink-port
---
# 3) PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: slink-data
namespace: cloud
annotations:
nfs.io/storage-path: "slink-data"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
limits:
storage: 15Gi
---
# 4) Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: slink
namespace: cloud
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
spec:
rules:
- host: slink.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: slink
port:
number: 3000
+39
View File
@@ -0,0 +1,39 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: renovate
namespace: cronjobs
spec:
schedule: "0 12 * * 0" # every Sunday 12:00
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: renovate
image: renovate/renovate:44.39.3
args:
- ivanch/haven
env:
- name: LOG_LEVEL
value: debug
- name: RENOVATE_AUTODISCOVER
value: "false"
- name: RENOVATE_PLATFORM
value: "gitea"
- name: RENOVATE_ENDPOINT
value: "https://git.ivanch.me"
- name: RENOVATE_GIT_AUTHOR
value: "Renovate Bot <bot@renovateapp.com>"
- name: RENOVATE_TOKEN
valueFrom:
secretKeyRef:
name: renovate-bot
key: RENOVATE_TOKEN
- name: RENOVATE_GITHUB_COM_TOKEN
valueFrom:
secretKeyRef:
name: renovate-bot
key: RENOVATE_GITHUB_COM_TOKEN
restartPolicy: Never
+160
View File
@@ -0,0 +1,160 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sonic
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: sonic
template:
metadata:
labels:
app: sonic
spec:
containers:
- name: sonic
image: archivebox/sonic:latest
imagePullPolicy: Always
ports:
- containerPort: 1491
env:
- name: SEARCH_BACKEND_PASSWORD
valueFrom:
secretKeyRef:
name: password
key: password
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "200m"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: archivebox
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: archivebox
template:
metadata:
labels:
app: archivebox
spec:
containers:
- name: archivebox
image: archivebox/archivebox:latest
imagePullPolicy: Always
ports:
- containerPort: 8000
env:
- name: SONIC_HOST
value: "sonic.default.svc.cluster.local"
- name: SONIC_PORT
value: "1491"
- name: SEARCH_BACKEND_ENGINE
value: "sonic"
- name: SONIC_PASSWORD
valueFrom:
secretKeyRef:
name: archivebox-password
key: password
- name: ADMIN_USERNAME
valueFrom:
secretKeyRef:
name: archivebox-password
key: password
- name: ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: archivebox-password
key: password
- name: CSRF_TRUSTED_ORIGINS
value: "archive.haven"
- name: ALLOWED_HOSTS
value: "*"
- name: PUBLIC_ADD_VIEW
value: "false"
volumeMounts:
- name: archivebox-data
mountPath: /data
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "2Gi"
cpu: "3000m"
volumes:
- name: archivebox-data
persistentVolumeClaim:
claimName: archivebox-data
---
apiVersion: v1
kind: Service
metadata:
name: sonic-svc
namespace: default
spec:
selector:
app: sonic
ports:
- protocol: TCP
port: 1491
targetPort: 1491
---
apiVersion: v1
kind: Service
metadata:
name: archivebox-svc
namespace: default
spec:
selector:
app: archivebox
ports:
- protocol: TCP
port: 8000
targetPort: 8000
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: archivebox-data
namespace: default
annotations:
nfs.io/storage-path: "archivebox-data"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
limits:
storage: 30Gi
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: archivebox-ingress
namespace: default
spec:
rules:
- host: "archive.haven"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: archivebox-svc
port:
number: 8000
+153
View File
@@ -0,0 +1,153 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: changedetection
namespace: default
labels:
app.kubernetes.io/name: changedetection
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app.kubernetes.io/name: changedetection
strategy:
type: Recreate
template:
metadata:
labels:
app.kubernetes.io/name: changedetection
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- iris
containers:
- name: changedetection
image: lscr.io/linuxserver/changedetection.io:latest
imagePullPolicy: Always
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: TZ
value: "Etc/UTC"
- name: BASE_URL
value: "http://change.haven/"
- name: PLAYWRIGHT_DRIVER_URL
value: "ws://localhost:3000"
ports:
- containerPort: 5000
name: http
protocol: TCP
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 2000m
memory: 1Gi
securityContext:
allowPrivilegeEscalation: false
runAsUser: 0
volumeMounts:
- name: config
mountPath: /config
- name: browser-sockpuppet-chrome
image: dgtlmoon/sockpuppetbrowser:latest
imagePullPolicy: Always
env:
- name: SCREEN_WIDTH
value: "1920"
- name: SCREEN_HEIGHT
value: "1024"
- name: SCREEN_DEPTH
value: "16"
- name: MAX_CONCURRENT_CHROME_PROCESSES
value: "10"
ports:
- containerPort: 3000
name: ws
protocol: TCP
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 2000m
memory: 4Gi
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- SYS_ADMIN
drop:
- ALL
volumes:
- name: config
persistentVolumeClaim:
claimName: changedetection-config
dnsPolicy: ClusterFirst
restartPolicy: Always
terminationGracePeriodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
name: changedetection
namespace: default
labels:
app.kubernetes.io/name: changedetection
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: changedetection
ports:
- name: http
port: 5000
protocol: TCP
targetPort: http
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: changedetection-config
namespace: default
annotations:
nfs.io/storage-path: "changedetection-config"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
limits:
storage: 2Gi
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: changedetection
namespace: default
labels:
app.kubernetes.io/name: changedetection
spec:
rules:
- host: change.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: changedetection
port:
number: 5000
+205
View File
@@ -0,0 +1,205 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: homepage
namespace: default
labels:
app.kubernetes.io/name: homepage
secrets:
- name: homepage
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: homepage
namespace: default
labels:
app.kubernetes.io/name: homepage
annotations:
kubernetes.io/service-account.name: homepage
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: homepage
labels:
app.kubernetes.io/name: homepage
rules:
- apiGroups:
- ""
resources:
- namespaces
- pods
- nodes
verbs:
- get
- list
- apiGroups:
- extensions
- networking.k8s.io
resources:
- ingresses
verbs:
- get
- list
- apiGroups:
- traefik.io
resources:
- ingressroutes
verbs:
- get
- list
- apiGroups:
- gateway.networking.k8s.io
resources:
- httproutes
- gateways
verbs:
- get
- list
- apiGroups:
- metrics.k8s.io
resources:
- nodes
- pods
verbs:
- get
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: homepage
labels:
app.kubernetes.io/name: homepage
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: homepage
subjects:
- kind: ServiceAccount
name: homepage
namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: homepage
namespace: default
labels:
app.kubernetes.io/name: homepage
spec:
revisionHistoryLimit: 3
replicas: 1
strategy:
type: RollingUpdate
selector:
matchLabels:
app.kubernetes.io/name: homepage
template:
metadata:
labels:
app.kubernetes.io/name: homepage
annotations:
configmap.reloader/checksum: '{{ include (print $.Template.BasePath "/app/config/services.yaml") . | sha256sum }}'
spec:
serviceAccountName: homepage
automountServiceAccountToken: true
enableServiceLinks: true
containers:
- name: homepage
image: "ghcr.io/gethomepage/homepage:latest"
imagePullPolicy: Always
env:
- name: HOMEPAGE_ALLOWED_HOSTS
value: "*"
ports:
- name: http
containerPort: 3000
protocol: TCP
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
volumeMounts:
- name: logs
mountPath: /app/config/logs
- name: homepage-config
mountPath: /app/config
- name: homepage-config
mountPath: /app/public/images
subPath: images
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: homepage-config
persistentVolumeClaim:
claimName: homepage-config
- name: logs
emptyDir: {}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: homepage-config
namespace: default
annotations:
nfs.io/storage-path: "homepage-config"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: homepage
namespace: default
labels:
app.kubernetes.io/name: homepage
spec:
type: ClusterIP
ports:
- port: 3000
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: homepage
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: homepage
namespace: default
labels:
app.kubernetes.io/name: homepage
spec:
rules:
- host: "homepage.haven"
http:
paths:
- path: "/"
pathType: Prefix
backend:
service:
name: homepage
port:
number: 3000
+66
View File
@@ -0,0 +1,66 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: it-tools
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: it-tools
template:
metadata:
labels:
app: it-tools
spec:
containers:
- name: it-tools
image: corentinth/it-tools:latest
imagePullPolicy: Always
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
name: it-tools-svc
namespace: default
spec:
selector:
app: it-tools
ports:
- protocol: TCP
port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: it-tools-ingress
namespace: default
spec:
rules:
- host: "tools.haven"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: it-tools-svc
port:
number: 80
+90
View File
@@ -0,0 +1,90 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: notepad
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: notepad
template:
metadata:
labels:
app: notepad
spec:
containers:
- name: notepad
image: jdreinhardt/minimalist-web-notepad:latest
imagePullPolicy: Always
command:
- sh
- -c
- >-
mkdir -p /var/www/html/_tmp &&
cp -n /var/www/html/notes.htaccess /var/www/html/_tmp/.htaccess 2>/dev/null;
exec docker-php-entrypoint apache2-foreground
ports:
- containerPort: 80
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
volumeMounts:
- name: notepad-data
mountPath: /var/www/html/_tmp
volumes:
- name: notepad-data
persistentVolumeClaim:
claimName: notepad-data
---
apiVersion: v1
kind: Service
metadata:
name: notepad
namespace: default
spec:
type: ClusterIP
selector:
app: notepad
ports:
- port: 80
targetPort: 80
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: notepad-data
namespace: default
annotations:
nfs.io/storage-path: "notepad-data"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: notepad
namespace: default
spec:
rules:
- host: notepad.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: notepad
port:
number: 80
+107
View File
@@ -0,0 +1,107 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: openwebui
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: openwebui
template:
metadata:
labels:
app: openwebui
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- iris
containers:
- name: openwebui
image: ghcr.io/open-webui/open-webui:main-slim
imagePullPolicy: Always
ports:
- containerPort: 8080
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: TZ
value: "America/Sao_Paulo"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: openwebui-secret
key: DATABASE_URL
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "2Gi"
volumeMounts:
- name: openwebui-data
mountPath: /app/backend/data
volumes:
- name: openwebui-data
persistentVolumeClaim:
claimName: openwebui-data
---
apiVersion: v1
kind: Service
metadata:
name: openwebui
namespace: default
spec:
type: ClusterIP
selector:
app: openwebui
ports:
- port: 8080
targetPort: 8080
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: openwebui-data
namespace: default
annotations:
nfs.io/storage-path: "openwebui-data"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
limits:
storage: 10Gi
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: openwebui
namespace: default
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
rules:
- host: openwebui.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: openwebui
port:
number: 8080
+150
View File
@@ -0,0 +1,150 @@
---
# 1) Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: paperless
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: paperless
template:
metadata:
labels:
app: paperless
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- iris
containers:
- name: paperless
image: ghcr.io/paperless-ngx/paperless-ngx:latest
imagePullPolicy: Always
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: PAPERLESS_URL
value: "http://paperless.haven"
- name: PAPERLESS_TIME_ZONE
value: "America/Sao_Paulo"
- name: PAPERLESS_OCR_LANGUAGE
value: "por"
- name: PAPERLESS_OCR_LANGUAGES
value: "por"
- name: PAPERLESS_OCR_USER_ARGS
value: '{"invalidate_digital_signatures": true}'
- name: PAPERLESS_DBHOST
value: postgresql.haven
- name: PAPERLESS_DBNAME
valueFrom:
secretKeyRef:
name: paperless-secret
key: PAPERLESS_DBNAME
- name: PAPERLESS_DBUSER
valueFrom:
secretKeyRef:
name: paperless-secret
key: PAPERLESS_DBUSER
- name: PAPERLESS_DBPASSWORD
valueFrom:
secretKeyRef:
name: paperless-secret
key: PAPERLESS_DBPASSWORD
- name: PAPERLESS_REDIS
value: "redis://redis.haven:6379"
- name: PAPERLESS_PORT
value: "8000"
- name: PAPERLESS_SECRET_KEY
valueFrom:
secretKeyRef:
name: paperless-secret
key: PAPERLESS_SECRET_KEY
ports:
- containerPort: 8000
name: paperless-port
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "4000m"
memory: "1Gi"
volumeMounts:
- name: paperless-data
subPath: data
mountPath: /usr/src/paperless/data
- name: paperless-data
subPath: media
mountPath: /usr/src/paperless/media
- name: paperless-data
subPath: export
mountPath: /usr/src/paperless/export
- name: paperless-data
subPath: consume
mountPath: /usr/src/paperless/consume
volumes:
- name: paperless-data
persistentVolumeClaim:
claimName: paperless-data
---
# 2) Service
apiVersion: v1
kind: Service
metadata:
name: paperless
namespace: default
spec:
type: ClusterIP
selector:
app: paperless
ports:
- port: 8000
targetPort: paperless-port
---
# 3) PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: paperless-data
namespace: default
annotations:
nfs.io/storage-path: "paperless-data"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
limits:
storage: 15Gi
---
# 4) Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: paperless
namespace: default
spec:
rules:
- host: paperless.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: paperless
port:
number: 8000
+130
View File
@@ -0,0 +1,130 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: playwright
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: playwright
strategy:
type: Recreate
template:
metadata:
labels:
app: playwright
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- iris
weight: 100
containers:
- command:
- npx
- -y
- playwright@1.58.0
- run-server
- --port
- "3000"
- --host
- 0.0.0.0
env:
- name: TZ
value: America/Sao_Paulo
image: mcr.microsoft.com/playwright:v1.62.1-noble
imagePullPolicy: Always
name: playwright
ports:
- containerPort: 3000
protocol: TCP
resources:
limits:
cpu: "4"
memory: 4Gi
requests:
cpu: 500m
memory: 512Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /dev/shm
name: dshm
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
volumes:
- emptyDir:
medium: Memory
name: dshm
status:
availableReplicas: 1
conditions:
- lastTransitionTime: "2026-05-29T15:54:24Z"
lastUpdateTime: "2026-07-22T10:14:35Z"
message: ReplicaSet "playwright-86c74d7c78" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
- lastTransitionTime: "2026-08-28T06:04:14Z"
lastUpdateTime: "2026-08-28T06:04:14Z"
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
observedGeneration: 95
readyReplicas: 1
replicas: 1
terminatingReplicas: 0
updatedReplicas: 1
---
apiVersion: v1
kind: Service
metadata:
name: playwright
namespace: default
spec:
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- port: 3000
protocol: TCP
targetPort: 3000
selector:
app: playwright
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: playwright
namespace: default
spec:
rules:
- host: playwright.haven
http:
paths:
- backend:
service:
name: playwright
port:
number: 3000
path: /
pathType: Prefix
status:
loadBalancer:
ingress:
- ip: 192.168.20.204
+93
View File
@@ -0,0 +1,93 @@
---
# 1) Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: searxng
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: searxng
template:
metadata:
labels:
app: searxng
spec:
enableServiceLinks: false
containers:
- name: searxng
image: searxng/searxng:latest
imagePullPolicy: Always
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
ports:
- containerPort: 8080
name: searxng-port
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
volumeMounts:
- name: searxng-config
mountPath: /etc/searxng
volumes:
- name: searxng-config
persistentVolumeClaim:
claimName: searxng-config
---
# 2) Service
apiVersion: v1
kind: Service
metadata:
name: searxng
namespace: default
spec:
type: ClusterIP
selector:
app: searxng
ports:
- port: 8080
targetPort: searxng-port
---
# 3) PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: searxng-config
namespace: default
annotations:
nfs.io/storage-path: "searxng-config"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
---
# 4) Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: searxng
namespace: default
spec:
rules:
- host: search.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: searxng
port:
number: 8080
+112
View File
@@ -0,0 +1,112 @@
---
# 1) Deployment - Stirling-PDF
apiVersion: apps/v1
kind: Deployment
metadata:
name: stirlingpdf
namespace: default
labels:
app: stirlingpdf
spec:
replicas: 1
selector:
matchLabels:
app: stirlingpdf
template:
metadata:
labels:
app: stirlingpdf
spec:
containers:
- name: stirlingpdf
image: stirlingtools/stirling-pdf:latest
imagePullPolicy: Always
env:
- name: TZ
value: "America/Sao_Paulo"
- name: DOCKER_ENABLE_SECURITY
value: "false"
- name: SECURITY_ENABLELOGIN
value: "false"
ports:
- containerPort: 8080
name: http
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 20
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 1Gi
limits:
cpu: 2000m
memory: 2Gi
securityContext:
allowPrivilegeEscalation: false
runAsUser: 0
volumeMounts:
- name: config
mountPath: /configs
volumes:
- name: config
persistentVolumeClaim:
claimName: stirlingpdf-config
terminationGracePeriodSeconds: 30
---
# 2) Service
apiVersion: v1
kind: Service
metadata:
name: stirlingpdf
namespace: default
labels:
app: stirlingpdf
spec:
type: ClusterIP
selector:
app: stirlingpdf
ports:
- name: http
port: 8080
protocol: TCP
targetPort: http
---
# 3) PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: stirlingpdf-config
namespace: default
annotations:
nfs.io/storage-path: "stirlingpdf-config"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
# 4) Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: stirlingpdf
namespace: default
labels:
app: stirlingpdf
spec:
rules:
- host: stirling.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: stirlingpdf
port:
number: 8080
+106
View File
@@ -0,0 +1,106 @@
---
# 1) Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: uptimekuma
namespace: default
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: uptimekuma
template:
metadata:
labels:
app: uptimekuma
spec:
containers:
- name: uptimekuma
image: louislam/uptime-kuma:2
imagePullPolicy: Always
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
ports:
- containerPort: 3001
name: uptimekuma-port
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /
port: 3001
initialDelaySeconds: 30
periodSeconds: 60
readinessProbe:
httpGet:
path: /
port: 3001
initialDelaySeconds: 5
periodSeconds: 5
volumeMounts:
- name: uptimekuma-config
mountPath: /app/data
volumes:
- name: uptimekuma-config
persistentVolumeClaim:
claimName: uptimekuma-config
---
# 2) Service
apiVersion: v1
kind: Service
metadata:
name: uptimekuma
namespace: default
spec:
type: ClusterIP
selector:
app: uptimekuma
ports:
- port: 3001
targetPort: uptimekuma-port
---
# 3) PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: uptimekuma-config
namespace: default
annotations:
nfs.io/storage-path: "uptimekuma-config"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
---
# 4) Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: uptimekuma
namespace: default
spec:
rules:
- host: uptimekuma.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: uptimekuma
port:
number: 3001
+146
View File
@@ -0,0 +1,146 @@
---
# 1) Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: vaultwarden
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: vaultwarden
template:
metadata:
labels:
app: vaultwarden
spec:
containers:
- name: vaultwarden
image: vaultwarden/server:latest
imagePullPolicy: Always
env:
- name: DOMAIN
value: "https://vault.haven"
- name: ADMIN_TOKEN
valueFrom:
secretKeyRef:
name: vaultwarden-admin-token
key: ADMIN_TOKEN
ports:
- containerPort: 80
name: vault-port
# /alive requires no authentication and verifies the Vaultwarden process
startupProbe:
httpGet:
path: /alive
port: vault-port
scheme: HTTP
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30
readinessProbe:
httpGet:
path: /alive
port: vault-port
scheme: HTTP
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
livenessProbe:
httpGet:
path: /alive
port: vault-port
scheme: HTTP
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 6
volumeMounts:
- name: vaultwarden-data
mountPath: /data
resources:
requests:
cpu: 250m
memory: 64Mi
limits:
cpu: 250m
memory: 256Mi
volumes:
- name: vaultwarden-data
persistentVolumeClaim:
claimName: vaultwarden-data
---
# 2) Service
apiVersion: v1
kind: Service
metadata:
name: vaultwarden
namespace: default
spec:
type: ClusterIP
selector:
app: vaultwarden
ports:
- port: 80
targetPort: vault-port
---
# 3) PersistentVolumeClaim (for /data)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: vaultwarden-data
namespace: default
annotations:
nfs.io/storage-path: "vaultwarden-data"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
---
# 4) Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vaultwarden
namespace: default
annotations:
cert-manager.io/cluster-issuer: internal-ca
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
tls:
- hosts:
- vault.haven
secretName: vaultwarden-tls
rules:
- host: vault.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vaultwarden
port:
number: 80
---
# 4) Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vaultwarden-public
namespace: default
spec:
rules:
- host: vault.ivanch.me
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vaultwarden
port:
number: 80
+202
View File
@@ -0,0 +1,202 @@
# --- ConfigMap for the AMD64 Runner ---
apiVersion: v1
kind: ConfigMap
metadata:
name: gitea-runner-amd64-config
namespace: dev
data:
config.yaml: |
# Registration token and Gitea instance URL should be managed via secrets
runner:
capacity: 4
timeout: 1h
labels:
- "ubuntu-amd64:docker://docker.gitea.com/runner-images:ubuntu-latest"
- "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest"
- "ubuntu-slim:docker://docker.gitea.com/runner-images:ubuntu-latest-slim"
- "runner-full:docker://git.ivanch.me/ivanch/runner-images:full"
- "runner-slim:docker://git.ivanch.me/ivanch/runner-images:slim"
- "runner-full-amd64:docker://git.ivanch.me/ivanch/runner-images:full"
- "runner-slim-amd64:docker://git.ivanch.me/ivanch/runner-images:slim"
---
# --- ConfigMap for the ARM64 Runner ---
apiVersion: v1
kind: ConfigMap
metadata:
name: gitea-runner-arm64-config
namespace: dev
data:
config.yaml: |
runner:
capacity: 4
timeout: 1h
labels:
- "ubuntu-arm64:docker://docker.gitea.com/runner-images:ubuntu-latest"
- "runner-full-arm64:docker://git.ivanch.me/ivanch/runner-images:full"
- "runner-slim-arm64:docker://git.ivanch.me/ivanch/runner-images:slim"
---
# PersistentVolumeClaim for AMD64
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gitea-runner-amd64-pvc
namespace: dev
annotations:
nfs.io/storage-path: "gitea-runner-amd64-pvc"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 8Mi
---
# PersistentVolumeClaim for ARM64
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gitea-runner-arm64-pvc
namespace: dev
annotations:
nfs.io/storage-path: "gitea-runner-arm64-pvc"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 8Mi
---
# --- Deployment for the AMD64 Runner ---
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitea-runner-amd64
namespace: dev
spec:
replicas: 1
selector:
matchLabels:
app: gitea-runner-amd64
template:
metadata:
labels:
app: gitea-runner-amd64
spec:
containers:
- name: gitea-runner
image: gitea/act_runner:latest
imagePullPolicy: Always
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "4000m"
memory: "4Gi"
volumeMounts:
- name: config-volume
mountPath: /etc/gitea-runner/config.yaml
subPath: config.yaml
- name: docker-socket
mountPath: /var/run/docker.sock
- name: gitea-runner-amd64-pvc
mountPath: /data
env:
- name: GITEA_RUNNER_REGISTRATION_TOKEN
valueFrom:
secretKeyRef:
name: gitea-runner-token
key: REGISTRATION_TOKEN
- name: GITEA_INSTANCE_URL
value: https://git.ivanch.me
- name: GITEA_RUNNER_NAME
value: k8s-runner-amd64
- name: CONFIG_FILE
value: /etc/gitea-runner/config.yaml
volumes:
- name: config-volume
configMap:
name: gitea-runner-amd64-config
- name: docker-socket
hostPath:
path: /var/run/docker.sock
- name: gitea-runner-amd64-pvc
persistentVolumeClaim:
claimName: gitea-runner-amd64-pvc
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- iris
---
# --- Deployment for the ARM64 Runner ---
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitea-runner-arm64
namespace: dev
spec:
replicas: 1
selector:
matchLabels:
app: gitea-runner-arm64
template:
metadata:
labels:
app: gitea-runner-arm64
spec:
containers:
- name: gitea-runner
image: gitea/act_runner:latest
imagePullPolicy: Always
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "4000m"
memory: "4Gi"
volumeMounts:
- name: config-volume
mountPath: /etc/gitea-runner/config.yaml
subPath: config.yaml
- name: docker-socket
mountPath: /var/run/docker.sock
- name: gitea-runner-arm64-pvc
mountPath: /data
env:
- name: GITEA_RUNNER_REGISTRATION_TOKEN
valueFrom:
secretKeyRef:
name: gitea-runner-token
key: REGISTRATION_TOKEN
- name: GITEA_INSTANCE_URL
value: https://git.ivanch.me
- name: GITEA_RUNNER_NAME
value: k8s-runner-arm64
- name: CONFIG_FILE
value: /etc/gitea-runner/config.yaml
volumes:
- name: config-volume
configMap:
name: gitea-runner-arm64-config
- name: docker-socket
hostPath:
path: /var/run/docker.sock
- name: gitea-runner-arm64-pvc
persistentVolumeClaim:
claimName: gitea-runner-arm64-pvc
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- nexus
+117
View File
@@ -0,0 +1,117 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: adguardsync-pvc
namespace: dns
annotations:
nfs.io/storage-path: "adguardsync-config"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Mi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: adguardsync
namespace: dns
spec:
strategy:
type: Recreate
replicas: 1
selector:
matchLabels:
app: adguardsync
template:
metadata:
labels:
app: adguardsync
spec:
containers:
- name: adguardsync
image: ghcr.io/bakito/adguardhome-sync:latest
imagePullPolicy: Always
ports:
- containerPort: 8080
protocol: TCP
name: web-port
env:
- name: CRON
value: "0 * * * *"
- name: RUN_ON_START
value: "true"
- name: LOG_LEVEL
value: "info"
- name: ORIGIN_URL
value: "http://adguard.haven"
- name: ORIGIN_USERNAME
valueFrom:
secretKeyRef:
name: adguardhome-password
key: username
- name: ORIGIN_PASSWORD
valueFrom:
secretKeyRef:
name: adguardhome-password
key: password
- name: REPLICA1_URL
value: "http://adguard2.haven"
- name: REPLICA1_USERNAME
valueFrom:
secretKeyRef:
name: adguardhome-password
key: username
- name: REPLICA1_PASSWORD
valueFrom:
secretKeyRef:
name: adguardhome-password
key: password
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
volumeMounts:
- name: adguardsync-storage
mountPath: /config
volumes:
- name: adguardsync-storage
persistentVolumeClaim:
claimName: adguardsync-pvc
---
apiVersion: v1
kind: Service
metadata:
name: adguardsync-svc
namespace: dns
spec:
type: ClusterIP
selector:
app: adguardsync
ports:
- name: web
port: 8080
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: adguardsync-ingress
namespace: dns
spec:
rules:
- host: adguardsync.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: adguardsync-svc
port:
number: 8080
+171
View File
@@ -0,0 +1,171 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: adguardhome-pvc
namespace: dns
annotations:
nfs.io/storage-path: "adguardhome-config"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: adguardhome
namespace: dns
spec:
strategy:
type: Recreate
replicas: 1
selector:
matchLabels:
app: adguardhome
template:
metadata:
labels:
app: adguardhome
spec:
containers:
- name: adguardhome
image: adguard/adguardhome:latest
imagePullPolicy: Always
ports:
- containerPort: 53
protocol: TCP
- containerPort: 53
protocol: UDP
- containerPort: 3000
protocol: TCP
name: install-port
- containerPort: 80
protocol: TCP
name: web-port
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 1000m
memory: 2Gi
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
volumeMounts:
- name: adguardhome-storage
mountPath: /opt/adguardhome/work
- name: adguardhome-storage
mountPath: /opt/adguardhome/conf
volumes:
- name: adguardhome-storage
persistentVolumeClaim:
claimName: adguardhome-pvc
---
apiVersion: v1
kind: Service
metadata:
name: adguardhome-svc
namespace: dns
spec:
type: LoadBalancer
selector:
app: adguardhome
loadBalancerIP: 192.168.20.200
ports:
- name: dns-tcp
port: 53
targetPort: 53
protocol: TCP
- name: dns-udp
port: 53
targetPort: 53
protocol: UDP
- name: web
port: 80
targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: adguardhome-lan-svc
namespace: dns
spec:
type: LoadBalancer
selector:
app: adguardhome
loadBalancerIP: 192.168.15.200
ports:
- name: dns-tcp
port: 53
targetPort: 53
protocol: TCP
- name: dns-udp
port: 53
targetPort: 53
protocol: UDP
- name: web
port: 80
targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: adguard-install-svc
namespace: dns
spec:
type: ClusterIP
selector:
app: adguardhome
ports:
- name: install
port: 3000
targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: adguardhome-ingress
namespace: dns
spec:
rules:
- host: adguard.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: adguardhome-svc
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: adguardhome-install-ingress
namespace: dns
spec:
rules:
- host: install.adguard.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: adguard-install-svc
port:
number: 3000
+38
View File
@@ -0,0 +1,38 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: beszel-agent
namespace: infra
spec:
selector:
matchLabels:
app: beszel-agent
template:
metadata:
labels:
app: beszel-agent
spec:
hostNetwork: true
containers:
- env:
- name: PORT
value: "45876"
- name: KEY
valueFrom:
secretKeyRef:
name: beszel-key
key: SECRET-KEY
image: henrygd/beszel-agent:0.18.8
imagePullPolicy: Always
name: beszel-agent
ports:
- containerPort: 45876
hostPort: 45876
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "200m"
restartPolicy: Always
+96
View File
@@ -0,0 +1,96 @@
---
# 1) Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: beszel
namespace: infra
spec:
replicas: 1
selector:
matchLabels:
app: beszel
template:
metadata:
labels:
app: beszel
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- amd64
containers:
- name: beszel
image: ghcr.io/henrygd/beszel/beszel:0.18.8
imagePullPolicy: Always
ports:
- containerPort: 8090
name: beszel-port
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
volumeMounts:
- name: beszel-config
mountPath: /beszel_data
volumes:
- name: beszel-config
persistentVolumeClaim:
claimName: beszel-config
---
# 2) Service
apiVersion: v1
kind: Service
metadata:
name: beszel
namespace: infra
spec:
type: ClusterIP
selector:
app: beszel
ports:
- port: 80
targetPort: beszel-port
---
# 3) PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: beszel-config
namespace: infra
annotations:
nfs.io/storage-path: "beszel-config"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
---
# 4) Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: beszel
namespace: infra
spec:
rules:
- host: beszel.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: beszel
port:
number: 80
+144
View File
@@ -0,0 +1,144 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: bitwarden-cli
namespace: infra
labels:
app.kubernetes.io/name: bitwarden-cli
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: bitwarden-cli
template:
metadata:
labels:
app.kubernetes.io/name: bitwarden-cli
spec:
nodeSelector:
kubernetes.io/arch: amd64
containers:
- name: bitwarden-cli
image: ghcr.io/charlesthomas/bitwarden-cli:2026.7.0
imagePullPolicy: IfNotPresent
# Override the baked entrypoint so --disable-origin-protection is
# actually passed (it was commented out in the image's entrypoint.sh,
# which made bw serve reject all cross-pod requests -> connection refused).
command: ["/bin/bash", "-lc"]
args:
- |
set -e
bw config server "${BW_HOST}"
# Authenticate. Prefer the API key if client creds are present.
login() {
if [ -n "$BW_CLIENTID" ] && [ -n "$BW_CLIENTSECRET" ]; then
echo "Using apikey to log in"
BW_SESSION=$(bw login --apikey --raw) || return 1
else
echo "Using password to log in"
BW_SESSION=$(bw login "${BW_USER}" --passwordenv BW_PASSWORD --raw) || return 1
fi
export BW_SESSION
}
login
# Warm the vault cache once at startup so bw serve has data immediately.
bw sync
bw status
# Keep the session alive
echo "Starting periodic bw login+sync loop (every 5m)"
(
while true; do
sleep 300
login || true
echo "[$(date -u +%FT%TZ)] bw sync"
bw sync >/dev/null 2>&1 || echo "[$(date -u +%FT%TZ)] bw sync failed"
done
) &
echo 'Running `bw serve` on port 8087'
bw serve --hostname 0.0.0.0 --disable-origin-protection
env:
- name: BW_HOST
valueFrom:
secretKeyRef:
name: bitwarden-cli
key: BW_HOST
- name: BW_USER
valueFrom:
secretKeyRef:
name: bitwarden-cli
key: BW_USERNAME
- name: BW_PASSWORD
valueFrom:
secretKeyRef:
name: bitwarden-cli
key: BW_PASSWORD
ports:
- name: http
containerPort: 8087
protocol: TCP
startupProbe:
tcpSocket: { port: 8087 }
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 30
readinessProbe:
tcpSocket: { port: 8087 }
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
livenessProbe:
tcpSocket: { port: 8087 }
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 6
resources:
limits:
cpu: 400m
memory: 512Mi
requests:
cpu: 50m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: bitwarden-cli
namespace: infra
labels:
app.kubernetes.io/name: bitwarden-cli
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: bitwarden-cli
ports:
- name: http
port: 8087
targetPort: http
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: bw-cli
namespace: infra
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: bitwarden-cli
policyTypes:
- Ingress
ingress:
- from:
# ESO pods in the external-secrets namespace.
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: external-secrets
podSelector:
matchLabels:
app.kubernetes.io/name: external-secrets
+112
View File
@@ -0,0 +1,112 @@
---
# 1) Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: code-config
namespace: infra
spec:
replicas: 1
selector:
matchLabels:
app: code-config
template:
metadata:
labels:
app: code-config
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- iris
containers:
- name: code-config
image: lscr.io/linuxserver/code-server:latest
imagePullPolicy: Always
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: PROXY_DOMAIN
value: "code-config.haven"
- name: DEFAULT_WORKSPACE
value: "/k8s-config"
resources:
requests:
memory: 512Mi
cpu: 200m
limits:
memory: 1Gi
cpu: 2000m
ports:
- containerPort: 8443
name: code-port
volumeMounts:
- name: code-config
mountPath: /config
- name: k8s-config
mountPath: /k8s-config
volumes:
- name: code-config
persistentVolumeClaim:
claimName: code-config
- name: k8s-config
nfs:
server: nfs-config.haven
path: /export/config
---
# 2) Service
apiVersion: v1
kind: Service
metadata:
name: code-config
namespace: infra
spec:
type: ClusterIP
selector:
app: code-config
ports:
- port: 8443
targetPort: code-port
---
# 3) PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: code-config
namespace: infra
annotations:
nfs.io/storage-path: "code-config"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
# 4) Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: code-config
namespace: infra
spec:
rules:
- host: code-config.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: code-config
port:
number: 8443
+26
View File
@@ -0,0 +1,26 @@
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
name: csi-driver-nfs
namespace: infra
spec:
repo: https://kubernetes-csi.github.io/csi-driver-nfs
chart: csi-driver-nfs
version: 4.13.4
targetNamespace: infra
valuesContent: |-
controller:
replicas: 1
logLevel: 5
defaultOnDeletePolicy: retain
storageClasses:
- name: nfs-client
annotations:
storageclass.kubernetes.io/is-default-class: "true"
parameters:
server: nfs-config.haven
share: /export/config
subDir: ${pvc.metadata.namespace}/${pvc.metadata.name}
onDelete: retain
reclaimPolicy: Retain
volumeBindingMode: Immediate
+11
View File
@@ -0,0 +1,11 @@
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
name: external-secrets
namespace: kube-system
spec:
repo: https://charts.external-secrets.io
chart: external-secrets
version: 2.7.0
targetNamespace: external-secrets
createNamespace: true
+128
View File
@@ -0,0 +1,128 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: file-nginx
namespace: infra
labels:
app: file-nginx
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
selector:
matchLabels:
app: file-nginx
template:
metadata:
labels:
app: file-nginx
spec:
containers:
- name: nginx
image: nginx:alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
securityContext:
allowPrivilegeEscalation: false
runAsUser: 0
volumeMounts:
- name: html
mountPath: /usr/share/nginx/data
- name: nginx-conf
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
startupProbe:
httpGet:
path: /
port: 80
failureThreshold: 30
periodSeconds: 5
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
volumes:
- name: html
nfs:
server: vega.haven
path: /export/Fast
- name: nginx-conf
configMap:
name: file-nginx-conf
---
apiVersion: v1
kind: ConfigMap
metadata:
name: file-nginx-conf
namespace: infra
data:
default.conf: |
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/data/file-nginx;
index index.html;
autoindex on;
location / {
try_files $uri $uri/ =404;
}
}
---
apiVersion: v1
kind: Service
metadata:
name: file-nginx
namespace: infra
spec:
selector:
app: file-nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: file-nginx
namespace: infra
annotations:
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "*"
spec:
rules:
- host: file-nginx.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: file-nginx
port:
number: 80
+85
View File
@@ -0,0 +1,85 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: haven-notify
namespace: infra
labels:
app: haven-notify
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
selector:
matchLabels:
app: haven-notify
template:
metadata:
labels:
app: haven-notify
spec:
containers:
- name: haven-notify
image: git.ivanch.me/ivanch/haven-notify:latest
imagePullPolicy: Always
ports:
- containerPort: 8080
env:
- name: WEBHOOK_URL
valueFrom:
secretKeyRef:
name: discord-webhook
key: HAVEN_WEBHOOK_URL
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: haven-notify
namespace: infra
spec:
selector:
app: haven-notify
ports:
- protocol: TCP
port: 8080
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: haven-notify
namespace: infra
spec:
rules:
- host: notify.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: haven-notify
port:
number: 8080
+32
View File
@@ -0,0 +1,32 @@
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
name: traefik
namespace: kube-system
spec:
repo: https://traefik.github.io/charts
chart: traefik
version: 41.2.0
targetNamespace: traefik
createNamespace: true
valuesContent: |-
deployment:
replicas: 2
ingressClass:
enabled: true
isDefaultClass: true
name: traefik
service:
annotations:
metallb.io/ip-allocated-from-pool: default-pool
metallb.io/loadBalancerIPs: "192.168.20.204"
spec:
type: LoadBalancer
externalTrafficPolicy: Local
resources:
requests:
cpu: 100m
memory: 90Mi
limits:
cpu: 1000m
memory: 256Mi
+126
View File
@@ -0,0 +1,126 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wg-easy-pvc
namespace: infra
annotations:
nfs.io/storage-path: "wg-easy-config"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wg-easy
namespace: infra
spec:
strategy:
type: Recreate
replicas: 1
selector:
matchLabels:
app: wg-easy
template:
metadata:
labels:
app: wg-easy
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- nexus
weight: 100
containers:
- name: wg-easy
image: ghcr.io/wg-easy/wg-easy:latest
imagePullPolicy: Always
ports:
- containerPort: 51820
protocol: UDP
name: wg-port
- containerPort: 51821
protocol: TCP
name: web-port
env:
- name: LANG
value: en
- name: WG_HOST
value: vpn.ivanch.me
- name: WG_MTU
value: "1420"
- name: UI_TRAFFIC_STATS
value: "true"
- name: UI_CHART_TYPE
value: "0"
- name: WG_ENABLE_ONE_TIME_LINKS
value: "true"
- name: UI_ENABLE_SORT_CLIENTS
value: "true"
securityContext:
capabilities:
add:
- NET_ADMIN
- SYS_MODULE
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 2000m
memory: 1Gi
volumeMounts:
- name: wg-easy-volume
mountPath: /etc/wireguard
restartPolicy: Always
volumes:
- name: wg-easy-volume
persistentVolumeClaim:
claimName: wg-easy-pvc
---
apiVersion: v1
kind: Service
metadata:
name: wg-easy-svc
namespace: infra
spec:
type: LoadBalancer
selector:
app: wg-easy
loadBalancerIP: 192.168.20.203
ports:
- name: wg-port
port: 51820
targetPort: 51820
protocol: UDP
- name: web-port
port: 51821
targetPort: 51821
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wg-easy-ingress
namespace: infra
spec:
rules:
- host: vpn.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: wg-easy-svc
port:
number: 51821
+10
View File
@@ -0,0 +1,10 @@
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
name: metallb
namespace: kube-system
spec:
repo: https://metallb.github.io/metallb
chart: metallb
version: 0.15.2
targetNamespace: metallb-system
+125
View File
@@ -0,0 +1,125 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: grafana
name: grafana
namespace: monitoring
spec:
selector:
matchLabels:
app: grafana
template:
metadata:
labels:
app: grafana
spec:
securityContext:
fsGroup: 472
supplementalGroups:
- 0
containers:
- name: grafana
image: grafana/grafana:latest
imagePullPolicy: Always
ports:
- containerPort: 3000
name: http-grafana
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /robots.txt
port: 3000
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 2
livenessProbe:
failureThreshold: 3
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: 3000
timeoutSeconds: 1
resources:
requests:
cpu: 250m
memory: 750Mi
limits:
memory: 1Gi
cpu: 500m
volumeMounts:
- mountPath: /var/lib/grafana
name: grafana-pv
volumes:
- name: grafana-pv
persistentVolumeClaim:
claimName: grafana-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: grafana-pvc
namespace: monitoring
annotations:
nfs.io/storage-path: "grafana-data"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
namespace: monitoring
name: grafana
spec:
ports:
- port: 3000
protocol: TCP
targetPort: http-grafana
selector:
app: grafana
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: monitoring
name: grafana
spec:
rules:
- host: grafana.haven
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: grafana
port:
number: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: monitoring
name: grafana-public
spec:
rules:
- host: grafanah.ivanch.me
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: grafana
port:
number: 3000
+145
View File
@@ -0,0 +1,145 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: kube-state-metrics
namespace: monitoring
labels:
app: kube-state-metrics
app.kubernetes.io/component: exporter
app.kubernetes.io/name: kube-state-metrics
app.kubernetes.io/version: 2.19.1
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kube-state-metrics
labels:
app: kube-state-metrics
app.kubernetes.io/component: exporter
app.kubernetes.io/name: kube-state-metrics
app.kubernetes.io/version: 2.19.1
rules:
- apiGroups: [""]
resources:
- nodes
- pods
- persistentvolumeclaims
verbs: ["list", "watch"]
- apiGroups: ["apps"]
resources:
- statefulsets
- daemonsets
- deployments
- replicasets
verbs: ["list", "watch"]
- apiGroups: ["batch"]
resources:
- cronjobs
- jobs
verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kube-state-metrics
labels:
app: kube-state-metrics
app.kubernetes.io/component: exporter
app.kubernetes.io/name: kube-state-metrics
app.kubernetes.io/version: 2.19.1
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kube-state-metrics
subjects:
- kind: ServiceAccount
name: kube-state-metrics
namespace: monitoring
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kube-state-metrics
namespace: monitoring
labels:
app: kube-state-metrics
app.kubernetes.io/component: exporter
app.kubernetes.io/name: kube-state-metrics
app.kubernetes.io/version: 2.19.1
spec:
replicas: 1
selector:
matchLabels:
app: kube-state-metrics
template:
metadata:
labels:
app: kube-state-metrics
app.kubernetes.io/component: exporter
app.kubernetes.io/name: kube-state-metrics
app.kubernetes.io/version: 2.19.1
spec:
automountServiceAccountToken: true
serviceAccountName: kube-state-metrics
nodeSelector:
kubernetes.io/os: linux
containers:
- name: kube-state-metrics
image: registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.19.1
imagePullPolicy: IfNotPresent
args:
- --resources=cronjobs,daemonsets,deployments,jobs,nodes,persistentvolumeclaims,pods,replicasets,statefulsets
ports:
- name: http-metrics
containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: /livez
port: http-metrics
initialDelaySeconds: 5
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /readyz
port: 8081
initialDelaySeconds: 5
timeoutSeconds: 5
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65534
seccompProfile:
type: RuntimeDefault
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: kube-state-metrics
namespace: monitoring
labels:
app: kube-state-metrics
app.kubernetes.io/component: exporter
app.kubernetes.io/name: kube-state-metrics
app.kubernetes.io/version: 2.19.1
spec:
type: ClusterIP
selector:
app: kube-state-metrics
ports:
- name: http-metrics
port: 8080
targetPort: http-metrics
protocol: TCP
+108
View File
@@ -0,0 +1,108 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: loki
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: loki
template:
metadata:
labels:
app: loki
spec:
containers:
- name: loki
image: grafana/loki:3
args: ["-config.file=/etc/loki/config/config.yaml"]
ports:
- containerPort: 3100
volumeMounts:
- name: config
mountPath: /etc/loki/config
- name: loki-storage
mountPath: /tmp/loki
resources:
requests:
cpu: 100m
memory: 1Gi
limits:
cpu: 200m
memory: 1Gi
volumes:
- name: config
configMap:
name: loki-config
- name: loki-storage
emptyDir:
medium: Memory
---
apiVersion: v1
kind: ConfigMap
metadata:
name: loki-config
namespace: monitoring
data:
config.yaml: |
auth_enabled: true
server:
http_listen_port: 3100
common:
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory
replication_factor: 1
path_prefix: /tmp/loki
querier:
multi_tenant_queries_enabled: true
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: /tmp/loki/index
cache_location: /tmp/loki/cache
filesystem:
directory: /tmp/loki/chunks
limits_config:
allow_structured_metadata: true
retention_period: 0
ingester:
lifecycler:
ring:
kvstore:
store: inmemory
replication_factor: 1
chunk_idle_period: 1m
max_chunk_age: 5m
chunk_target_size: 1536000
compactor:
retention_enabled: false
---
apiVersion: v1
kind: Service
metadata:
name: loki
namespace: monitoring
spec:
ports:
- port: 3100
targetPort: 3100
name: http
selector:
app: loki
+56
View File
@@ -0,0 +1,56 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: monitoring
labels:
app: node-exporter
spec:
selector:
matchLabels:
app: node-exporter
template:
metadata:
labels:
app: node-exporter
spec:
hostNetwork: true
containers:
- name: node-exporter
image: prom/node-exporter:latest
imagePullPolicy: Always
args:
- "--path.rootfs=/host"
ports:
- containerPort: 9100
hostPort: 9100
name: metrics
protocol: TCP
resources:
requests:
memory: "50Mi"
cpu: "100m"
limits:
memory: "100Mi"
cpu: "200m"
volumeMounts:
- name: host
mountPath: /host
readOnly: true
volumes:
- name: host
hostPath:
path: /
---
apiVersion: v1
kind: Service
metadata:
name: node-exporter
namespace: monitoring
spec:
selector:
app: node-exporter
ports:
- name: metrics
port: 9100
targetPort: metrics
+130
View File
@@ -0,0 +1,130 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
namespace: monitoring
labels:
app: prometheus
spec:
replicas: 1
selector:
matchLabels:
app: prometheus
template:
metadata:
labels:
app: prometheus
spec:
serviceAccountName: prometheus
containers:
- name: prometheus
image: prom/prometheus:latest
args:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--storage.tsdb.retention.time=1d"
- "--web.enable-lifecycle"
ports:
- containerPort: 9090
name: web
volumeMounts:
- name: prometheus-config-volume
mountPath: /etc/prometheus
- name: prometheus-storage
mountPath: /prometheus
resources:
requests:
memory: "500Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "500m"
volumes:
- name: prometheus-config-volume
persistentVolumeClaim:
claimName: prometheus-pvc
- name: prometheus-storage
emptyDir:
medium: Memory
sizeLimit: 256Mi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prometheus-pvc
namespace: monitoring
annotations:
nfs.io/storage-path: "prometheus-config"
spec:
storageClassName: "nfs-client"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
# Service URL - http://prometheus.monitoring.svc.cluster.local:9090
apiVersion: v1
kind: Service
metadata:
name: prometheus
namespace: monitoring
labels:
app: prometheus
spec:
ports:
- name: web
port: 9090
targetPort: web
selector:
app: prometheus
type: ClusterIP
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: prometheus
namespace: monitoring
labels:
app: prometheus
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: prometheus
namespace: monitoring
labels:
app: prometheus
rules:
- apiGroups: [""]
resources:
- nodes
- nodes/proxy
- services
- endpoints
- pods
verbs: ["get", "list", "watch"]
- apiGroups: ["extensions"]
resources:
- ingresses
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources:
- ingresses
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: prometheus
namespace: monitoring
labels:
app: prometheus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus
subjects:
- kind: ServiceAccount
name: prometheus
namespace: monitoring
+46
View File
@@ -0,0 +1,46 @@
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: haven-apps
namespace: argocd
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: https://git.ivanch.me/ivanch/haven.git
revision: main
files:
- path: "apps/*/*.yaml"
- path: "apps/root/*.yaml"
exclude: true
template:
metadata:
name: '{{ .path.filename | trimSuffix ".yaml" }}'
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://git.ivanch.me/ivanch/haven.git
targetRevision: main
path: "{{ .path.path }}"
directory:
include: "{{ .path.filename }}"
destination:
server: https://kubernetes.default.svc
namespace: "{{ index .path.segments 1 }}"
syncPolicy:
automated:
prune: true
selfHeal: true
templatePatch: |
{{- if hasKey . "chart" }}
spec:
source:
$patch: replace
{{- toYaml .spec.source | nindent 4 }}
syncPolicy:
syncOptions: [CreateNamespace=true, ServerSideApply=true]
{{- end }}
+5
View File
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- applicationset.yaml