Compare commits

...
Author SHA1 Message Date
Aleksandr BogomiakovandClaude ff4e87aca0 NG: furumi player pinned to the music node
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 1m19s
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-06 16:18:36 +01:00
Aleksandr BogomiakovandClaude 01561ea2cd NG: furumi database in main PostgreSQL cluster
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-06 16:18:36 +01:00
Aleksandr Bogomiakov 7db427f332 NG: secret stores with lookup by path or id
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 20s
Move the Vaultwarden bridge and the ClusterSecretStores out of external-secrets
into secret-stores (wave 4, after vaultwarden). A store key may be an item UUID
or an item name used as a path (k8s/<namespace>/<what>), matched exactly.
2026-10-06 15:13:09 +01:00
Aleksandr Bogomiakov 41729bf256 NG: enable sso for VW
Keycloak Terraform / Terraform (push) Failing after 39s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 19s
2026-10-06 00:42:40 +01:00
Aleksandr Bogomiakov fe914e2de4 NG: enable sso for VW
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 28s
2026-10-06 00:18:56 +01:00
Aleksandr Bogomiakov 0348329829 NG: enable sso for argocd
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 15s
2026-10-06 00:11:02 +01:00
Aleksandr Bogomiakov e257e3895b NG: added Keycloak
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 10s
2026-10-05 23:29:35 +01:00
Aleksandr Bogomiakov 4fe2a2b5f3 NG: added reloader
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 22s
2026-10-05 19:17:41 +01:00
Aleksandr Bogomiakov 690ff2a19c NG: added http -> https redir
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 17s
2026-10-05 19:00:28 +01:00
Aleksandr Bogomiakov 99e1818572 NG: app-of-apps CORE
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 24s
2026-10-05 18:49:42 +01:00
Aleksandr Bogomiakov cf88753ff5 NG: secret manager and VW
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Failing after 14m33s
2026-10-05 18:40:46 +01:00
Aleksandr Bogomiakov 862a7619e2 Track main instead of k8s-ng in the new cluster's applications
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 22s
2026-10-05 18:31:58 +01:00
Aleksandr Bogomiakov 5a29dcc236 Stop tracking rendered Helm charts in k8s-ng 2026-10-05 18:31:52 +01:00
Aleksandr Bogomiakov bcfbfcd362 Moved PSQL to NG 2026-10-05 18:30:17 +01:00
Aleksandr Bogomiakov 2676ecef5d Moved ArgoCD to NG 2026-10-05 18:13:48 +01:00
Ultradesu b4de55f3c0 Change AmneziaWG port to 5848
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 12s
Check with kubeconform / lint (push) Successful in 10s
Auto-update README / Generate README and Create MR (push) Successful in 11s
2026-09-30 14:00:07 +01:00
Ultradesu de6c9562f1 Update AmneziaWG port to 443, refresh obfuscation params and update fellow image to 1.0.2
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 17s
Check with kubeconform / lint (push) Failing after 7m19s
Auto-update README / Generate README and Create MR (push) Successful in 4m32s
2026-09-30 13:53:38 +01:00
ab 0c333375a5 Fixed AWG scripts
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 5s
Check with kubeconform / lint (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 5s
2026-09-17 17:48:53 +03:00
ab 5783547bd3 Fixed AWG scripts
Check with kubeconform / lint (push) Successful in 12s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 9s
Auto-update README / Generate README and Create MR (push) Successful in 6s
2026-09-17 17:38:30 +03:00
ab 29c5b00c16 Added airtrail deployment
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 9s
2026-09-12 07:18:56 +03:00
ab 76b2090ccc Added airtrail psql user
Check with kubeconform / lint (push) Successful in 9s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 10s
Auto-update README / Generate README and Create MR (push) Successful in 8s
2026-09-12 07:11:22 +03:00
ab bc83f7dd24 Update k8s/apps/pasarguard/deployment.yaml
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 20s
Check with kubeconform / lint (push) Successful in 14s
Auto-update README / Generate README and Create MR (push) Successful in 21s
2026-09-02 08:48:55 +00:00
Aleksandr Bogomiakov 5768a4ff3b fix grafana datasource
Check with kubeconform / lint (push) Successful in 7s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 9s
2026-09-01 23:06:49 +01:00
Aleksandr Bogomiakov 7ce98c4982 fix loki helm chart path
Check with kubeconform / lint (push) Successful in 5s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 12s
2026-09-01 22:35:02 +01:00
Aleksandr Bogomiakov 18f2654ff1 updated prom 84.3.0 -> 88.6.2, loki 6.29.0 -> 18.11.7, promtail 6.16.6 -> 6.17.1
Auto-update README / Generate README and Create MR (push) Successful in 11s
Check with kubeconform / lint (push) Successful in 6s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
2026-09-01 22:30:43 +01:00
Aleksandr Bogomiakov 2fb63dded8 updated longhorn 1.12.0 -> 1.12.1
Check with kubeconform / lint (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 8s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 5s
2026-09-01 22:25:34 +01:00
Aleksandr Bogomiakov 46bf1dc41b updated keycloak 7.1.11 -> 7.3.1
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 5s
Check with kubeconform / lint (push) Successful in 5s
Auto-update README / Generate README and Create MR (push) Successful in 8s
2026-09-01 22:23:48 +01:00
Aleksandr Bogomiakov eda408aaea updated external-secrets 1.1.0 -> 2.10.0
Check with kubeconform / lint (push) Successful in 15s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 9s
Auto-update README / Generate README and Create MR (push) Successful in 12s
2026-09-01 22:21:37 +01:00
Aleksandr Bogomiakov 7b9da9236f updated cert-manager 1.20.0 -> 1.21.1
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 20s
2026-09-01 22:19:01 +01:00
Aleksandr Bogomiakov f2a607fda1 Updated k3s v1.36.1+k3s1 -> v1.36.4+k3s1
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 9s
Check with kubeconform / lint (push) Successful in 7s
2026-09-01 22:14:55 +01:00
Aleksandr Bogomiakov d57bb01608 Updated argoCD
Check with kubeconform / lint (push) Successful in 7s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 12s
2026-09-01 22:06:58 +01:00
Aleksandr Bogomiakov 76f77fbc56 Fixed GITEA__webhook__ALLOWED_HOST_LIST -> GITEA__security__ALLOWED_HOST_LIST
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 23s
Check with kubeconform / lint (push) Successful in 10s
Auto-update README / Generate README and Create MR (push) Successful in 8s
2026-09-01 21:51:08 +01:00
Ultradesu e90efbd016 Added proxy
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
Check with kubeconform / lint (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 7s
2026-08-14 10:55:52 +01:00
Ultradesu b0378ffb40 Added macos exporter
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 8s
Check with kubeconform / lint (push) Successful in 8s
Auto-update README / Generate README and Create MR (push) Successful in 9s
2026-08-11 11:30:32 +01:00
Ultradesu 5c2e7d0e33 Removed nodeselector for web-petting
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 11s
Check with kubeconform / lint (push) Successful in 11s
Auto-update README / Generate README and Create MR (push) Successful in 30s
2026-08-08 11:59:07 +01:00
Ultradesu b9052a99f1 Fixed matrix secrets
Check with kubeconform / lint (push) Successful in 6s
Auto-update README / Generate README and Create MR (push) Successful in 7s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 5s
2026-08-07 11:17:50 +01:00
Ultradesu d41171955b Moved traefik-ai to kube-system-custom, fixed lan-play ingress
Check with kubeconform / lint (push) Successful in 7s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 7s
Auto-update README / Generate README and Create MR (push) Successful in 9s
2026-08-05 13:44:30 +01:00
Ultradesu f038016d46 Added UK lan-play instance
Keycloak Terraform / Terraform (push) Successful in 26s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 15s
Check with kubeconform / lint (push) Successful in 11s
Auto-update README / Generate README and Create MR (push) Successful in 18s
2026-08-05 12:56:42 +01:00
Aleksandr Bogomiakov 1091d6bd14 Enabled auto sync openbanking
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 10s
Check with kubeconform / lint (push) Successful in 9s
Auto-update README / Generate README and Create MR (push) Successful in 7s
2026-08-01 14:43:48 +01:00
Aleksandr Bogomiakov 9f56045009 Enabled auto sync openbanking
Check with kubeconform / lint (push) Successful in 8s
Auto-update README / Generate README and Create MR (push) Successful in 7s
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 6s
2026-08-01 13:30:39 +01:00
Aleksandr Bogomiakov 2583236f6d Enabled auto sync openbanking
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 15s
Check with kubeconform / lint (push) Successful in 10s
Auto-update README / Generate README and Create MR (push) Successful in 8s
2026-08-01 13:24:23 +01:00
92 changed files with 2880 additions and 60 deletions

No files matched your search

+3
View File
@@ -59,3 +59,6 @@ tags
[._]*.un~
.DS_Store
# Helm charts pulled by kustomize (--enable-helm) when rendering locally
k8s-ng/**/charts/
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Brings the whole core state up on an empty cluster:
# k8s-ng/bootstrap/bootstrap.sh <kube-context>
# Installs Argo CD (the only thing that cannot install itself) and applies the
# root application; Argo CD then pulls every core application in wave order.
set -euo pipefail
ctx=${1:?usage: bootstrap.sh <kube-context>}
here=$(cd "$(dirname "$0")" && pwd)
k="kubectl --context $ctx"
# Render in a scratch copy so kustomize does not leave downloaded charts in the repo.
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
cp -r "$here/../core/argocd" "$tmp/argocd"
kubectl kustomize --enable-helm "$tmp/argocd" > "$tmp/argocd.yaml"
$k create namespace argocd --dry-run=client -o yaml | $k apply -f -
# CRDs must exist before the Argo CD objects that use them: apply twice.
$k apply --server-side --force-conflicts -f "$tmp/argocd.yaml" >/dev/null 2>&1 || true
sleep 8
$k apply --server-side --force-conflicts -f "$tmp/argocd.yaml"
$k -n argocd rollout status deploy --timeout=300s
$k -n argocd rollout status statefulset --timeout=300s
$k apply --server-side --force-conflicts -f "$here/root.yaml"
echo "Core state requested. Watch it with: $k -n argocd get app"
+29
View File
@@ -0,0 +1,29 @@
# Root of the core state. Applying this one object makes Argo CD pull every core
# application in wave order (see k8s-ng/core-state/values.yaml).
# Note: there are deliberately no finalizers; deleting this Application keeps all
# deployed services and data.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: core-state
namespace: argocd
spec:
project: core
destination:
server: https://kubernetes.default.svc
namespace: argocd
source:
repoURL: https://gt.hexor.cy/ab/homelab.git
targetRevision: main
path: k8s-ng/core-state
helm:
# Per-cluster overrides of core-state/values.yaml, for example:
# valuesObject:
# targetRevision: some-branch
valuesObject: {}
syncPolicy:
automated:
selfHeal: true
prune: true
syncOptions:
- ServerSideApply=true
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v2
name: core-state
description: App of apps for the core services of the cluster
version: 0.1.0
@@ -0,0 +1,29 @@
{{- range .Values.apps }}
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: {{ .name }}
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: {{ .wave | quote }}
spec:
project: {{ .project | default $.Values.project }}
destination:
server: {{ $.Values.destinationServer }}
namespace: {{ .namespace }}
source:
repoURL: {{ $.Values.repoURL }}
targetRevision: {{ $.Values.targetRevision }}
path: {{ .path }}
syncPolicy:
automated:
selfHeal: true
prune: {{ if hasKey . "prune" }}{{ .prune }}{{ else }}true{{ end }}
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
{{- if .skipDryRunOnMissingResource }}
- SkipDryRunOnMissingResource=true
{{- end }}
{{- end }}
+65
View File
@@ -0,0 +1,65 @@
# Per-cluster variables. Override them in k8s-ng/bootstrap/root.yaml
# (spec.source.helm.valuesObject) when the state is deployed to another cluster.
repoURL: https://gt.hexor.cy/ab/homelab.git
targetRevision: main
destinationServer: https://kubernetes.default.svc
project: core
# Core applications. Lower waves are synced first and must be Healthy before the
# next wave starts. Every path is a kustomize (or plain manifest) directory.
# wave 0: Argo CD itself and its projects
# wave 1: operators (their CRDs are used by the later waves)
# wave 2: the database cluster and its databases/users
# wave 3: services that need the database and secrets
apps:
- name: argocd
path: k8s-ng/core/argocd
namespace: argocd
wave: 0
- name: cnpg
path: k8s-ng/core/cnpg
namespace: cnpg-system
wave: 1
- name: traefik-config
path: k8s-ng/core/traefik-config
namespace: kube-system
wave: 1
- name: reloader
path: k8s-ng/core/reloader
namespace: reloader
wave: 1
- name: cert-manager
path: k8s-ng/core/cert-manager
namespace: cert-manager
wave: 1
skipDryRunOnMissingResource: true # ClusterIssuers need the cert-manager CRDs
- name: external-secrets
path: k8s-ng/core/external-secrets
namespace: external-secrets
wave: 1
- name: postgresql
path: k8s-ng/core/postgresql
namespace: psql
wave: 2
prune: false # never delete databases because of a manifest change
skipDryRunOnMissingResource: true # CRDs come from the cnpg and external-secrets apps
- name: vaultwarden
path: k8s-ng/core/vaultwarden
namespace: vaultwarden
wave: 3
skipDryRunOnMissingResource: true
- name: keycloak
path: k8s-ng/core/keycloak
namespace: keycloak
wave: 3
skipDryRunOnMissingResource: true # ExternalSecret/Certificate CRDs come from earlier waves
- name: furumi
path: k8s-ng/core/furumi
namespace: furumi
wave: 5 # pinned to the music node; must not hold back the other waves
skipDryRunOnMissingResource: true
- name: secret-stores
path: k8s-ng/core/secret-stores
namespace: external-secrets
wave: 4 # reads the Vaultwarden vault, so it comes after vaultwarden
skipDryRunOnMissingResource: true # ClusterSecretStore CRD comes from the external-secrets app
+66
View File
@@ -0,0 +1,66 @@
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: core
namespace: argocd
spec:
clusterResourceWhitelist:
- group: '*'
kind: '*'
description: Hexor Home Lab Core
destinations:
- namespace: '*'
server: https://kubernetes.default.svc
sourceRepos:
- https://gt.hexor.cy/ab/homelab.git
---
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: apps
namespace: argocd
spec:
clusterResourceWhitelist:
- group: '*'
kind: '*'
description: Hexor Home Lab Apps
destinations:
- namespace: '*'
server: https://kubernetes.default.svc
sourceRepos:
- https://gt.hexor.cy/ab/homelab.git
---
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: games
namespace: argocd
spec:
clusterResourceWhitelist:
- group: '*'
kind: '*'
description: Hexor Home Lab Games
destinations:
- namespace: '*'
server: https://kubernetes.default.svc
sourceRepos:
- https://gt.hexor.cy/ab/homelab.git
---
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: desktop
namespace: argocd
spec:
clusterResourceWhitelist:
- group: '*'
kind: '*'
description: Hexor Home Lab Desktop Apps
destinations:
- namespace: '*'
server: https://kubernetes.default.svc
sourceRepos:
- https://gt.hexor.cy/ab/homelab.git
+26
View File
@@ -0,0 +1,26 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-dex-server
namespace: argocd
spec:
template:
spec:
containers:
- name: dex-server
readinessProbe:
tcpSocket:
port: 5556
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
livenessProbe:
tcpSocket:
port: 5556
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
+25
View File
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd
namespace: argocd
annotations:
cert-manager.io/cluster-issuer: letsencrypt
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
tls:
- hosts:
- ag.btwiusearch.net
secretName: argocd-server-tls
rules:
- host: ag.btwiusearch.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 80
+18
View File
@@ -0,0 +1,18 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ingress.yaml
- appprojects.yaml
helmCharts:
- name: argo-cd
repo: https://argoproj.github.io/argo-helm
version: 10.6.0
releaseName: argocd
namespace: argocd
valuesFile: values.yaml
includeCRDs: true
patches:
- path: dex-probes-patch.yaml
+115
View File
@@ -0,0 +1,115 @@
# Argo CD Helm Chart Values (k8s-ng cluster)
global:
domain: ag.btwiusearch.net
logging:
format: text
level: info
crds:
install: true
keep: true # Recommended to keep true so CRDs are not deleted on uninstall
configs:
params:
server.insecure: "true" # TLS is terminated by the ingress
cm:
create: true
exec.enabled: true
kustomize.buildOptions: --enable-helm
application.instanceLabelKey: argocd.argoproj.io/instance
# Local admin account, kept as a break-glass login next to SSO; the initial
# password is generated by Argo CD into the argocd-initial-admin-secret secret.
# Set to false once SSO is trusted.
admin.enabled: true
statusbadge.enabled: true
timeout.reconciliation: 60s
# Lets sync waves in an app of apps wait for child Applications to be Healthy.
resource.customizations.health.argoproj.io_Application: |
hs = {}
hs.status = "Progressing"
hs.message = ""
if obj.status ~= nil then
if obj.status.health ~= nil then
hs.status = obj.status.health.status
if obj.status.health.message ~= nil then
hs.message = obj.status.health.message
end
end
end
return hs
# SSO through Dex -> Keycloak. The client id/secret come from the oidc-creds
# Secret (labelled app.kubernetes.io/part-of=argocd), created by hand until
# Vaultwarden + External Secrets are the source.
dex.config: |
connectors:
- type: oidc
id: keycloak
name: Keycloak
config:
issuer: https://auth.hexor.cy/auth/realms/hexor
clientID: $oidc-creds:id
clientSecret: $oidc-creds:secret
insecureEnableGroups: true
scopes:
- openid
- profile
- email
- offline_access
getUserInfo: true
rbac:
create: true
policy.default: ""
policy.csv: |
g, game-servers-managers, GameServersManagersRole
# Role permissions
p, GameServersManagersRole, applications, get, games/*, allow
p, GameServersManagersRole, applications, update, games/*, allow
p, GameServersManagersRole, applications, sync, games/*, allow
p, GameServersManagersRole, applications, override, games/*, allow
p, GameServersManagersRole, applications, action/*, games/*, allow
p, GameServersManagersRole, exec, create, games/*, allow
p, GameServersManagersRole, logs, get, games/*, allow
p, GameServersManagersRole, applications, delete, games/*, deny
# Admin policy
g, argocd-admins, role:admin
secret:
createSecret: true
controller:
replicas: 1
# Dex OIDC provider (Keycloak connector configured in configs.cm.dex.config)
dex:
replicas: 1
enabled: true
redis:
enabled: true
redis-ha:
enabled: false
server:
replicas: 1
ingress:
enabled: false
certificate:
enabled: false
certificateSecret:
enabled: false
repoServer:
replicas: 1
livenessProbe:
timeoutSeconds: 10
periodSeconds: 60
applicationSet:
enabled: true
replicas: 1
notifications:
enabled: true
replicas: 1
+38
View File
@@ -0,0 +1,38 @@
# Public certificates through DNS-01. The credential Secrets are created by hand
# (they will move to Vaultwarden + External Secrets):
# kubectl -n cert-manager create secret generic cloudflare-api-token --from-literal=apiToken=<token>
# (Zone:DNS:Edit on the btwiusearch.net zone)
# kubectl -n cert-manager create secret generic aws-api-key-secret \
# --from-literal=accessKey=<id> --from-literal=secretKey=<secret>
# (Route53 access to the hexor.cy zone)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: k8s-master@hexor.cy
privateKeySecretRef:
name: letsencrypt-account-key
solvers:
- selector:
dnsZones:
- btwiusearch.net
dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: apiToken
- selector:
dnsZones:
- hexor.cy
dns01:
route53:
region: eu-north-1
accessKeyIDSecretRef:
name: aws-api-key-secret
key: accessKey
secretAccessKeySecretRef:
name: aws-api-key-secret
key: secretKey
@@ -0,0 +1,15 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- issuer.yaml
- self-signed.yaml
helmCharts:
- name: cert-manager
repo: https://charts.jetstack.io
version: 1.21.1
releaseName: cert-manager
namespace: cert-manager
valuesFile: values.yaml
includeCRDs: true
+29
View File
@@ -0,0 +1,29 @@
# Private CA for internal services: issuer "hexor-lab".
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: hexor-ca-root
namespace: cert-manager
spec:
isCA: true
duration: 87600h # 10y
commonName: hexor-ca
secretName: hexor-ca-root-secret
issuerRef:
name: selfsigned
kind: ClusterIssuer
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: hexor-lab
spec:
ca:
secretName: hexor-ca-root-secret
+2
View File
@@ -0,0 +1,2 @@
crds:
enabled: true
+11
View File
@@ -0,0 +1,11 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
helmCharts:
- name: cloudnative-pg
repo: https://cloudnative-pg.github.io/charts
version: 0.29.1
releaseName: cnpg
namespace: cnpg-system
valuesFile: values.yaml
includeCRDs: true
+4
View File
@@ -0,0 +1,4 @@
# CloudNativePG operator (chart 0.29.x = operator 1.30.x)
crds:
create: true
@@ -0,0 +1,13 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# The Vaultwarden bridge and the ClusterSecretStores are in ../secret-stores (they need Vaultwarden).
helmCharts:
- name: external-secrets
repo: https://charts.external-secrets.io
version: 2.11.0
releaseName: external-secrets
namespace: external-secrets
valuesFile: values.yaml
includeCRDs: true
+1
View File
@@ -0,0 +1 @@
installCRDs: true
+50
View File
@@ -0,0 +1,50 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: furumi-player
labels:
app: furumi-player
spec:
replicas: 1
strategy:
type: Recreate # the library is a hostPath on a single node
selector:
matchLabels:
app: furumi-player
template:
metadata:
labels:
app: furumi-player
annotations:
reloader.stakater.com/auto: "true"
spec:
# The music library lives on this node's /data (LVM raid, not replicated).
nodeSelector:
kubernetes.io/hostname: music
containers:
- name: furumi-player
image: ultradesu/furumusic:latest
imagePullPolicy: Always
env:
- name: FURU_DATABASE_URL
valueFrom:
secretKeyRef:
name: furumi-creds
key: PG_STRING
ports:
- name: http
containerPort: 8000
volumeMounts:
- name: library
mountPath: /media
- name: inbox
mountPath: /inbox
volumes:
- name: library
hostPath:
path: /data/furumi/library
type: Directory
- name: inbox
hostPath:
path: /data/furumi/inbox
type: Directory
+24
View File
@@ -0,0 +1,24 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: furumi
annotations:
cert-manager.io/cluster-issuer: letsencrypt
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
ingressClassName: traefik
rules:
- host: music.hexor.cy
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: furumi-player
port:
number: 8000
tls:
- secretName: furumi-tls
hosts:
- music.hexor.cy
+8
View File
@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- secrets.yaml
- deployment.yaml
- service.yaml
- ingress.yaml
+47
View File
@@ -0,0 +1,47 @@
# Read-only access to the furumi-db Secret in the psql namespace
# (see postgresql/databases/furumi.yaml for the other half).
apiVersion: v1
kind: ServiceAccount
metadata:
name: db-credentials-reader
---
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: psql-credentials
spec:
provider:
kubernetes:
remoteNamespace: psql
server:
caProvider:
type: ConfigMap
name: kube-root-ca.crt
key: ca.crt
auth:
serviceAccount:
name: db-credentials-reader
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: furumi-creds
spec:
refreshInterval: 1h
secretStoreRef:
kind: SecretStore
name: psql-credentials
target:
name: furumi-creds
template:
data:
PG_STRING: "postgresql://{{ .username }}:{{ .password }}@main-rw.psql.svc:5432/furumi"
data:
- secretKey: username
remoteRef:
key: furumi-db
property: username
- secretKey: password
remoteRef:
key: furumi-db
property: password
+16
View File
@@ -0,0 +1,16 @@
---
apiVersion: v1
kind: Service
metadata:
name: furumi-player
labels:
app: furumi-player
spec:
type: ClusterIP
selector:
app: furumi-player
ports:
- name: http
protocol: TCP
port: 8000
targetPort: 8000
+122
View File
@@ -0,0 +1,122 @@
# rsauth2-proxy: forward-auth for services that have no OIDC of their own.
# Needs, besides this file:
# - Secret auth-proxy-creds (AUTH_PROXY_CLIENT_ID, AUTH_PROXY_CLIENT_SECRET,
# AUTH_PROXY_COOKIE_SECRET); the client secret is the one of the Keycloak
# client "rsauth2-proxy". Created by hand until Vaultwarden is the source.
# - ConfigMap auth-proxy-routes, managed by Terraform (terraform/keycloak).
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-proxy
labels:
app: auth-proxy
spec:
replicas: 1
selector:
matchLabels:
app: auth-proxy
template:
metadata:
labels:
app: auth-proxy
spec:
containers:
- name: auth-proxy
image: ultradesu/rsauth2-proxy:latest
ports:
- containerPort: 8080
name: http
protocol: TCP
envFrom:
- secretRef:
name: auth-proxy-creds
env:
- name: AUTH_PROXY_OIDC_ISSUER
value: "https://auth.hexor.cy/auth/realms/hexor"
- name: AUTH_PROXY_COOKIE_DOMAIN
value: ".hexor.cy"
- name: AUTH_PROXY_CALLBACK_URL
value: "https://oauth.hexor.cy/callback"
- name: AUTH_PROXY_ROUTES_FILE
value: "/config/routes.yaml"
- name: AUTH_PROXY_LOG_LEVEL
value: "debug"
volumeMounts:
- name: routes
mountPath: /config
readOnly: true
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
cpu: 200m
memory: 64Mi
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 30
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
capabilities:
drop:
- ALL
volumes:
- name: routes
configMap:
name: auth-proxy-routes
---
apiVersion: v1
kind: Service
metadata:
name: auth-proxy
labels:
app: auth-proxy
spec:
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
selector:
app: auth-proxy
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: auth-proxy
spec:
entryPoints:
- websecure
routes:
- match: Host(`oauth.hexor.cy`)
kind: Rule
services:
- name: auth-proxy
port: 80
tls:
secretName: auth-proxy-tls
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: auth-proxy-tls
spec:
secretName: auth-proxy-tls
issuerRef:
name: letsencrypt
kind: ClusterIssuer
dnsNames:
- oauth.hexor.cy
@@ -0,0 +1,366 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: keycloak-hacker-theme
namespace: keycloak
data:
theme.properties: |
parent=keycloak.v2
import=common/keycloak
styles=css/hacker.css
hacker.css: |
/* ============================================================
HEXOR — Hacker Terminal Theme for Keycloak
============================================================ */
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;600&display=swap');
:root {
--hk-bg: #0a0a0a;
--hk-panel: #0d0d0d;
--hk-border: #00ff4180;
--hk-green: #00ff41;
--hk-green-dim: #00cc33;
--hk-green-glow: rgba(0, 255, 65, 0.15);
--hk-green-glow-strong: rgba(0, 255, 65, 0.35);
--hk-cyan: #00e5ff;
--hk-red: #ff3333;
--hk-text: #b0ffb0;
--hk-text-dim: #5a8a5a;
--hk-input-bg: #050505;
--hk-font: 'Fira Code', 'Cascadia Code', 'JetBrains Mono', monospace;
}
/* -- Force dark always --------------------------------------------------- */
html.login-pf,
html.login-pf.pf-v5-theme-dark {
color-scheme: dark;
}
/* -- Background: scanlines + CRT ---------------------------------------- */
body#keycloak-bg {
font-family: var(--hk-font) !important;
background: var(--hk-bg) !important;
color: var(--hk-text) !important;
min-height: 100vh;
position: relative;
}
body#keycloak-bg::before {
content: '';
position: fixed;
inset: 0;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 255, 65, 0.03) 2px,
rgba(0, 255, 65, 0.03) 4px
);
pointer-events: none;
z-index: 9999;
}
/* -- Login container ----------------------------------------------------- */
.pf-v5-c-login {
background: transparent !important;
}
.pf-v5-c-login__container {
max-width: 480px !important;
}
/* -- Header / Brand ------------------------------------------------------ */
#kc-header-wrapper {
font-family: var(--hk-font) !important;
color: var(--hk-green) !important;
text-shadow: 0 0 10px var(--hk-green), 0 0 30px var(--hk-green-glow);
font-size: 1.5rem !important;
letter-spacing: 0.15em;
text-transform: uppercase;
}
#kc-header-wrapper .kc-logo-text span::before {
content: '> ';
opacity: 0.6;
}
/* -- Main card ----------------------------------------------------------- */
.pf-v5-c-login__main {
background: var(--hk-panel) !important;
border: 1px solid var(--hk-border) !important;
border-radius: 0 !important;
box-shadow:
0 0 15px var(--hk-green-glow),
inset 0 0 30px rgba(0, 0, 0, 0.5) !important;
}
/* -- Title --------------------------------------------------------------- */
h1#kc-page-title,
.pf-v5-c-title {
font-family: var(--hk-font) !important;
color: var(--hk-green) !important;
text-shadow: 0 0 8px var(--hk-green-glow);
font-weight: 600 !important;
font-size: 1.25rem !important;
letter-spacing: 0.05em;
}
/* -- Labels -------------------------------------------------------------- */
.pf-v5-c-form__label-text {
font-family: var(--hk-font) !important;
color: var(--hk-green-dim) !important;
font-size: 0.85rem !important;
text-transform: uppercase;
letter-spacing: 0.08em;
}
/* -- Input fields -------------------------------------------------------- */
.pf-v5-c-form-control {
background-color: var(--hk-input-bg) !important;
border: 1px solid var(--hk-border) !important;
border-radius: 0 !important;
color: var(--hk-green) !important;
font-family: var(--hk-font) !important;
font-size: 0.9rem !important;
caret-color: var(--hk-green);
transition: border-color 0.2s, box-shadow 0.2s;
}
.pf-v5-c-form-control:focus-within,
.pf-v5-c-form-control:focus {
border-color: var(--hk-green) !important;
box-shadow: 0 0 8px var(--hk-green-glow), inset 0 0 4px var(--hk-green-glow) !important;
outline: none !important;
}
.pf-v5-c-form-control input {
color: var(--hk-green) !important;
font-family: var(--hk-font) !important;
}
.pf-v5-c-form-control input::placeholder {
color: var(--hk-text-dim) !important;
}
/* -- Primary button (Sign In) -------------------------------------------- */
.pf-v5-c-button.pf-m-primary {
background: transparent !important;
border: 1px solid var(--hk-green) !important;
border-radius: 0 !important;
color: var(--hk-green) !important;
font-family: var(--hk-font) !important;
font-weight: 600 !important;
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 0.9rem !important;
transition: all 0.2s;
position: relative;
overflow: hidden;
}
.pf-v5-c-button.pf-m-primary:hover {
background: var(--hk-green-glow) !important;
box-shadow: 0 0 15px var(--hk-green-glow-strong), inset 0 0 15px var(--hk-green-glow) !important;
color: #fff !important;
text-shadow: 0 0 5px var(--hk-green);
}
/* -- Secondary button (Try Another Way, Passkey) ------------------------- */
.pf-v5-c-button.pf-m-secondary {
background: transparent !important;
border: 1px solid var(--hk-cyan) !important;
border-radius: 0 !important;
color: var(--hk-cyan) !important;
font-family: var(--hk-font) !important;
text-transform: uppercase;
letter-spacing: 0.1em;
font-size: 0.85rem !important;
transition: all 0.2s;
}
.pf-v5-c-button.pf-m-secondary:hover {
background: rgba(0, 229, 255, 0.1) !important;
box-shadow: 0 0 12px rgba(0, 229, 255, 0.3) !important;
color: #fff !important;
}
/* -- Passkey authenticate button ----------------------------------------- */
#authenticateWebAuthnButton {
background: transparent !important;
border: 1px solid var(--hk-cyan) !important;
border-radius: 0 !important;
color: var(--hk-cyan) !important;
font-family: var(--hk-font) !important;
text-transform: uppercase;
letter-spacing: 0.1em;
font-size: 0.85rem !important;
padding: 0.75rem 1rem;
display: block;
width: 100%;
text-align: center;
text-decoration: none;
transition: all 0.2s;
margin-top: 1rem;
}
#authenticateWebAuthnButton::before {
content: '\f084 ';
font-family: 'Font Awesome 6 Free', 'FontAwesome';
font-weight: 900;
}
#authenticateWebAuthnButton:hover {
background: rgba(0, 229, 255, 0.1) !important;
box-shadow: 0 0 15px rgba(0, 229, 255, 0.3), inset 0 0 10px rgba(0, 229, 255, 0.1) !important;
color: #fff !important;
text-shadow: 0 0 5px var(--hk-cyan);
}
/* -- Show password button ------------------------------------------------ */
.pf-v5-c-button.pf-m-control {
background: var(--hk-input-bg) !important;
border: 1px solid var(--hk-border) !important;
border-left: none !important;
border-radius: 0 !important;
color: var(--hk-text-dim) !important;
}
.pf-v5-c-button.pf-m-control:hover {
color: var(--hk-green) !important;
}
/* -- Social providers (Google, etc) -------------------------------------- */
.pf-v5-c-login__main-footer {
border-top: 1px solid var(--hk-border) !important;
}
.kc-social-section .pf-v5-c-button.pf-m-secondary {
border-color: var(--hk-text-dim) !important;
color: var(--hk-text) !important;
}
.kc-social-section .pf-v5-c-button.pf-m-secondary:hover {
border-color: var(--hk-green) !important;
color: var(--hk-green) !important;
box-shadow: 0 0 10px var(--hk-green-glow) !important;
}
/* -- Try another way link ------------------------------------------------ */
#try-another-way {
color: var(--hk-cyan) !important;
text-decoration: none !important;
font-family: var(--hk-font) !important;
}
#try-another-way:hover {
text-shadow: 0 0 8px rgba(0, 229, 255, 0.5);
}
/* -- Links --------------------------------------------------------------- */
a {
color: var(--hk-cyan) !important;
text-decoration: none;
transition: text-shadow 0.2s;
}
a:hover {
text-shadow: 0 0 6px rgba(0, 229, 255, 0.4);
}
/* -- Alerts / messages --------------------------------------------------- */
.pf-v5-c-alert {
background: rgba(0, 255, 65, 0.05) !important;
border: 1px solid var(--hk-border) !important;
border-radius: 0 !important;
font-family: var(--hk-font) !important;
}
.pf-v5-c-alert.pf-m-danger {
border-color: var(--hk-red) !important;
background: rgba(255, 51, 51, 0.05) !important;
}
.pf-v5-c-alert__title {
color: var(--hk-text) !important;
font-family: var(--hk-font) !important;
}
/* -- Checkbox (Remember me) ---------------------------------------------- */
.pf-v5-c-check__label {
color: var(--hk-text-dim) !important;
font-family: var(--hk-font) !important;
font-size: 0.8rem !important;
}
/* -- Helper text (Forgot password, etc.) --------------------------------- */
.pf-v5-c-helper-text .pf-v5-c-button.pf-m-link {
color: var(--hk-cyan) !important;
font-family: var(--hk-font) !important;
font-size: 0.8rem !important;
}
/* -- Select auth list (Try another way options) -------------------------- */
.select-auth-container {
background: var(--hk-panel) !important;
font-family: var(--hk-font) !important;
}
.pf-v5-c-data-list__item {
border-color: var(--hk-border) !important;
background: transparent !important;
}
.pf-v5-c-data-list__item:hover {
background: var(--hk-green-glow) !important;
}
.select-auth-box-headline {
color: var(--hk-green) !important;
}
.select-auth-box-desc {
color: var(--hk-text-dim) !important;
}
/* -- Footer -------------------------------------------------------------- */
.pf-v5-c-login__main-footer-band {
background: transparent !important;
border-top: 1px solid var(--hk-border) !important;
}
/* -- Language selector --------------------------------------------------- */
select#login-select-toggle {
background: var(--hk-input-bg) !important;
color: var(--hk-green) !important;
border-color: var(--hk-border) !important;
font-family: var(--hk-font) !important;
}
/* -- Blinking cursor animation for header -------------------------------- */
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
#kc-header-wrapper::after {
content: '_';
animation: blink 1s step-end infinite;
color: var(--hk-green);
}
/* -- Subtle CRT flicker ------------------------------------------------- */
@keyframes flicker {
0% { opacity: 0.97; }
5% { opacity: 0.95; }
10% { opacity: 0.98; }
15% { opacity: 0.96; }
20% { opacity: 0.99; }
100% { opacity: 0.98; }
}
.pf-v5-c-login__main {
animation: flicker 4s infinite;
}
+16
View File
@@ -0,0 +1,16 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- secrets.yaml
- hacker-theme-configmap.yaml
- auth-proxy.yaml
# ConfigMap auth-proxy-routes is managed by Terraform (terraform/keycloak, kubernetes_config_map_v1).
helmCharts:
- name: keycloakx
repo: https://codecentric.github.io/helm-charts
version: 7.3.1
releaseName: keycloak
namespace: keycloak
valuesFile: values.yaml
+80
View File
@@ -0,0 +1,80 @@
# Read-only access to the keycloak-db Secret in the psql namespace
# (see postgresql/databases/keycloak.yaml for the other half).
apiVersion: v1
kind: ServiceAccount
metadata:
name: db-credentials-reader
---
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: psql-credentials
spec:
provider:
kubernetes:
remoteNamespace: psql
server:
caProvider:
type: ConfigMap
name: kube-root-ca.crt
key: ca.crt
auth:
serviceAccount:
name: db-credentials-reader
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: keycloak-creds
spec:
refreshInterval: 1h
secretStoreRef:
kind: SecretStore
name: psql-credentials
target:
name: keycloak-creds
template:
data:
KC_DB_USERNAME: "{{ .username }}"
KC_DB_PASSWORD: "{{ .password }}"
data:
- secretKey: username
remoteRef:
key: keycloak-db
property: username
- secretKey: password
remoteRef:
key: keycloak-db
property: password
---
# Bootstrap admin: only used when the database has no admin user yet (a fresh
# install). With the restored database the existing admin accounts are kept.
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
metadata:
name: keycloak-bootstrap
spec:
length: 40
digits: 10
symbols: 0
noUpper: false
allowRepeat: true
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: keycloak-bootstrap
spec:
refreshPolicy: CreatedOnce
target:
name: keycloak-bootstrap
template:
data:
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: "{{ .password }}"
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
name: keycloak-bootstrap
+75
View File
@@ -0,0 +1,75 @@
replicas: 1
image:
repository: quay.io/keycloak/keycloak
tag: "26.5.6"
command:
- "/opt/keycloak/bin/kc.sh"
- "start"
- "--http-port=8080"
- "--hostname-strict=false"
- "--proxy-headers=xforwarded"
extraEnvFrom: |
- secretRef:
name: keycloak-creds
- secretRef:
name: keycloak-bootstrap
extraEnv: |
- name: KC_HOSTNAME
value: auth.hexor.cy
- name: JAVA_OPTS_APPEND
value: "-Djgroups.dns.query=keycloak-keycloakx-headless.keycloak.svc"
extraVolumes: |
- name: hacker-theme
configMap:
name: keycloak-hacker-theme
extraVolumeMounts: |
- name: hacker-theme
mountPath: /opt/keycloak/themes/hacker/login/theme.properties
subPath: theme.properties
- name: hacker-theme
mountPath: /opt/keycloak/themes/hacker/login/css/hacker.css
subPath: hacker.css
dbchecker:
enabled: true
database:
vendor: postgres
hostname: main-rw.psql.svc
port: 5432
database: keycloak
existingSecret: keycloak-creds
existingSecretKey: KC_DB_PASSWORD
service:
type: ClusterIP
# HTTP is redirected to HTTPS by the cluster-wide Traefik default (core/traefik-config).
ingress:
enabled: true
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt
rules:
- host: auth.hexor.cy
paths:
- path: /
pathType: Prefix
tls:
- secretName: keycloak-tls
hosts:
- auth.hexor.cy
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: "3"
memory: 2Gi
+48
View File
@@ -0,0 +1,48 @@
# Main HA PostgreSQL cluster: one primary and one streaming replica, each on its
# own node with a node-local volume. Replication is asynchronous (nodes sit in
# different regions), so a failover can lose the last few transactions.
#
# Nodes eligible for instances carry the label workload/postgres=true. To move a
# replica: label the new node, unlabel the old one, then recreate the instance
# (kubectl cnpg destroy main <N>); the operator re-clones it from the primary.
#
# Endpoints inside the cluster: main-rw (primary), main-ro (replicas),
# main-r (any instance) in the psql namespace.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: main
namespace: psql
spec:
description: Main HA PostgreSQL cluster
imageName: ghcr.io/cloudnative-pg/postgresql:17
instances: 2
enableSuperuserAccess: false
# Roll updates replica-first and hand over by switchover instead of restarting the primary.
primaryUpdateStrategy: unsupervised
primaryUpdateMethod: switchover
storage:
storageClass: local-path
size: 20Gi # local-path volumes cannot be expanded later
affinity:
enablePodAntiAffinity: true
podAntiAffinityType: required
topologyKey: kubernetes.io/hostname
nodeSelector:
workload/postgres: "true"
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
memory: 2Gi
postgresql:
parameters:
max_connections: "200"
shared_buffers: 512MB
effective_cache_size: 1536MB
@@ -0,0 +1,83 @@
# Database "furumi": password, role, database and read access for the app.
# To add another database copy this file and replace the name.
# Generated once; the role and the application both read this Secret.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: furumi-db
namespace: psql
spec:
refreshPolicy: CreatedOnce
target:
name: furumi-db
template:
type: kubernetes.io/basic-auth
metadata:
labels:
cnpg.io/reload: "true"
data:
username: furumi
password: "{{ .password }}"
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
name: db-password
---
apiVersion: postgresql.cnpg.io/v1
kind: DatabaseRole
metadata:
name: main-furumi
namespace: psql
spec:
cluster:
name: main
name: furumi
login: true
passwordSecret:
name: furumi-db
---
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: main-furumi
namespace: psql
spec:
cluster:
name: main
name: furumi
owner: furumi
extensions:
- name: pg_trgm
databaseReclaimPolicy: retain
---
# Lets the furumi namespace read only its own credentials Secret.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: furumi-db-reader
namespace: psql
rules:
- apiGroups: [""]
resources: [secrets]
resourceNames: [furumi-db]
verbs: [get, list, watch]
- apiGroups: [authorization.k8s.io]
resources: [selfsubjectrulesreviews]
verbs: [create]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: furumi-db-reader
namespace: psql
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: furumi-db-reader
subjects:
- kind: ServiceAccount
name: db-credentials-reader
namespace: furumi
@@ -0,0 +1,81 @@
# Database "keycloak": password, role, database and read access for the app.
# To add another database copy this file and replace the name.
# Generated once; the role and the application both read this Secret.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: keycloak-db
namespace: psql
spec:
refreshPolicy: CreatedOnce
target:
name: keycloak-db
template:
type: kubernetes.io/basic-auth
metadata:
labels:
cnpg.io/reload: "true"
data:
username: keycloak
password: "{{ .password }}"
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
name: db-password
---
apiVersion: postgresql.cnpg.io/v1
kind: DatabaseRole
metadata:
name: main-keycloak
namespace: psql
spec:
cluster:
name: main
name: keycloak
login: true
passwordSecret:
name: keycloak-db
---
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: main-keycloak
namespace: psql
spec:
cluster:
name: main
name: keycloak
owner: keycloak
databaseReclaimPolicy: retain
---
# Lets the keycloak namespace read only its own credentials Secret.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: keycloak-db-reader
namespace: psql
rules:
- apiGroups: [""]
resources: [secrets]
resourceNames: [keycloak-db]
verbs: [get, list, watch]
- apiGroups: [authorization.k8s.io]
resources: [selfsubjectrulesreviews]
verbs: [create]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: keycloak-db-reader
namespace: psql
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: keycloak-db-reader
subjects:
- kind: ServiceAccount
name: db-credentials-reader
namespace: keycloak
@@ -0,0 +1,81 @@
# Database "vaultwarden": password, role, database and read access for the app.
# To add another database copy this file and replace the name.
# Generated once; the role and the application both read this Secret.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: vaultwarden-db
namespace: psql
spec:
refreshPolicy: CreatedOnce
target:
name: vaultwarden-db
template:
type: kubernetes.io/basic-auth
metadata:
labels:
cnpg.io/reload: "true"
data:
username: vaultwarden
password: "{{ .password }}"
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
name: db-password
---
apiVersion: postgresql.cnpg.io/v1
kind: DatabaseRole
metadata:
name: main-vaultwarden
namespace: psql
spec:
cluster:
name: main
name: vaultwarden
login: true
passwordSecret:
name: vaultwarden-db
---
apiVersion: postgresql.cnpg.io/v1
kind: Database
metadata:
name: main-vaultwarden
namespace: psql
spec:
cluster:
name: main
name: vaultwarden
owner: vaultwarden
databaseReclaimPolicy: retain
---
# Lets the vaultwarden namespace read only its own credentials Secret.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: vaultwarden-db-reader
namespace: psql
rules:
- apiGroups: [""]
resources: [secrets]
resourceNames: [vaultwarden-db]
verbs: [get, list, watch]
- apiGroups: [authorization.k8s.io]
resources: [selfsubjectrulesreviews]
verbs: [create]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: vaultwarden-db-reader
namespace: psql
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: vaultwarden-db-reader
subjects:
- kind: ServiceAccount
name: db-credentials-reader
namespace: vaultwarden
@@ -0,0 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- cluster.yaml
- password-generator.yaml
- databases/vaultwarden.yaml
- databases/keycloak.yaml
- databases/furumi.yaml
@@ -0,0 +1,12 @@
# Random passwords for database roles. Referenced by the ExternalSecrets in databases/.
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
metadata:
name: db-password
namespace: psql
spec:
length: 40
digits: 10
symbols: 0 # the password ends up inside a connection URL
noUpper: false
allowRepeat: true
+10
View File
@@ -0,0 +1,10 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
helmCharts:
- name: reloader
repo: https://stakater.github.io/stakater-charts
version: 2.2.18
releaseName: reloader
namespace: reloader
valuesFile: values.yaml
+24
View File
@@ -0,0 +1,24 @@
reloader:
watchGlobally: true
autoReloadAll: true
reloadOnCreate: true
reloadOnDelete: false
reloadStrategy: annotations
ignoreConfigMaps: false
ignoreSecrets: false
ignoreJobs: false
ignoreCronJobs: false
enableHA: true
syncAfterRestart: true
logLevel: info
rbac:
enabled: true
deployment:
replicas: 2
resources:
requests:
cpu: 10m
memory: 128Mi
limits:
cpu: 150m
memory: 512Mi
@@ -0,0 +1,26 @@
FROM debian:sid
ENV BW_CLI_VERSION=2025.12.1
RUN apt update && \
apt install -y wget unzip && \
wget https://github.com/bitwarden/clients/releases/download/cli-v${BW_CLI_VERSION}/bw-linux-${BW_CLI_VERSION}.zip && \
unzip bw-linux-${BW_CLI_VERSION}.zip && \
chmod +x bw && \
mv bw /usr/local/bin/bw && \
rm -rfv *.zip && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Build entrypoint script
RUN echo '#!/bin/sh\n\
set -e\n\
bw config server ${BW_HOST}\n\
export BW_SESSION=$(bw login ${BW_USER} --passwordenv BW_PASSWORD --raw)\n\
bw unlock --check\n\
echo "Running \`bw server\` on port 8087"\n\
bw serve --hostname 0.0.0.0\n' > /entrypoint.sh && \
chmod +x /entrypoint.sh
CMD ["/entrypoint.sh"]
@@ -0,0 +1,123 @@
# Bridge between External Secrets and Vaultwarden: `bw serve` logged in as the
# bot account. Needs the Secret bitwarden-cli (created by hand; the bot logs in
# with email + password, so keep password login enabled for it, i.e. no
# SSO_ONLY, or switch the image to API-key login):
# kubectl -n external-secrets create secret generic bitwarden-cli \
# --from-literal=BW_HOST=https://vw.btwiusearch.net \
# --from-literal=BW_USERNAME=<bot email> --from-literal=BW_PASSWORD=<password>
# Image source: Dockerfile-bitwarden-cli in this directory.
apiVersion: apps/v1
kind: Deployment
metadata:
name: bitwarden-cli
labels:
reloader.stakater.com/auto: "true"
app.kubernetes.io/instance: bitwarden-cli
app.kubernetes.io/name: bitwarden-cli
spec:
replicas: 1
strategy:
type: RollingUpdate
selector:
matchLabels:
app.kubernetes.io/name: bitwarden-cli
app.kubernetes.io/instance: bitwarden-cli
template:
metadata:
labels:
app.kubernetes.io/name: bitwarden-cli
app.kubernetes.io/instance: bitwarden-cli
spec:
nodeSelector:
kubernetes.io/arch: amd64
containers:
- name: bitwarden-cli
image: ultradesu/bitwarden-client:2025.12.1
imagePullPolicy: IfNotPresent
resources:
requests:
memory: "128Mi"
cpu: "300m"
limits:
memory: "512Mi"
cpu: "1000m"
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
livenessProbe:
exec:
command:
- wget
- -q
- http://127.0.0.1:8087/sync
- --post-data=''
initialDelaySeconds: 20
failureThreshold: 3
timeoutSeconds: 1
periodSeconds: 120
readinessProbe:
tcpSocket:
port: 8087
initialDelaySeconds: 20
failureThreshold: 3
timeoutSeconds: 1
periodSeconds: 10
startupProbe:
tcpSocket:
port: 8087
initialDelaySeconds: 10
failureThreshold: 30
timeoutSeconds: 1
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: bitwarden-cli
labels:
app.kubernetes.io/instance: bitwarden-cli
app.kubernetes.io/name: bitwarden-cli
spec:
type: ClusterIP
ports:
- port: 8087
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: bitwarden-cli
app.kubernetes.io/instance: bitwarden-cli
---
# Only the External Secrets controller may talk to the bridge: it serves the whole vault.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: external-secret-2-bw-cli
spec:
podSelector:
matchLabels:
app.kubernetes.io/instance: bitwarden-cli
app.kubernetes.io/name: bitwarden-cli
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/instance: external-secrets
app.kubernetes.io/name: external-secrets
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- bitwarden-cli.yaml
- stores.yaml
+47
View File
@@ -0,0 +1,47 @@
# ClusterSecretStores in front of the Vaultwarden vault that belongs to the bot
# account (the vault holds server secrets only, nothing personal).
#
# `key` may be either
# - the item UUID (old style, changes whenever items are imported again), or
# - the item path: the item NAME, which by convention is the full path
# k8s/<namespace>/<what> e.g. k8s/cert-manager/cloudflare-api-token
# Names are looked up by exact match (a name that is a substring of another
# item name, login or URL is fine). Do not use double quotes in names.
#
# vaultwarden-login: `property` is a path inside the item: login.username,
# login.password, notes, fields[0].value, ...
# vaultwarden-fields: `property` is the NAME of a custom field of the item.
#
# data:
# - secretKey: password
# sourceRef:
# storeRef: {name: vaultwarden-login, kind: ClusterSecretStore}
# remoteRef:
# key: k8s/cert-manager/cloudflare-api-token
# property: login.password
#
# The bridge syncs the vault every couple of minutes, so a new item becomes
# visible to ESO after that.
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: vaultwarden-login
spec:
provider:
webhook:
url: 'http://bitwarden-cli.external-secrets.svc:8087/{{ if regexMatch "^[0-9a-fA-F-]{36}$" .remoteRef.key }}object/item/{{ .remoteRef.key }}{{ else }}list/object/items?search={{ .remoteRef.key }}{{ end }}'
headers:
Content-Type: application/json
result:
jsonPath: '{{ if regexMatch "^[0-9a-fA-F-]{36}$" .remoteRef.key }}$.data.{{ .remoteRef.property }}{{ else }}$.data.data[?@.name=="{{ .remoteRef.key }}"].{{ .remoteRef.property }}{{ end }}'
---
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: vaultwarden-fields
spec:
provider:
webhook:
url: 'http://bitwarden-cli.external-secrets.svc:8087/{{ if regexMatch "^[0-9a-fA-F-]{36}$" .remoteRef.key }}object/item/{{ .remoteRef.key }}{{ else }}list/object/items?search={{ .remoteRef.key }}{{ end }}'
result:
jsonPath: '{{ if regexMatch "^[0-9a-fA-F-]{36}$" .remoteRef.key }}$.data.fields[?@.name=="{{ .remoteRef.property }}"].value{{ else }}$.data.data[?@.name=="{{ .remoteRef.key }}"].fields[?@.name=="{{ .remoteRef.property }}"].value{{ end }}'
@@ -0,0 +1,16 @@
# Configuration of the Traefik that ships with k3s.
# Ingresses without a router.entrypoints annotation now use only "websecure", so
# plain HTTP is redirected to HTTPS by default (see http-redirect.yaml).
# To publish an Ingress over plain HTTP add the annotation
# traefik.ingress.kubernetes.io/router.entrypoints: web
# (or "web,websecure" to serve both without a redirect).
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
ports:
websecure:
asDefault: true
@@ -0,0 +1,29 @@
# Catch-all on the "web" entrypoint with the lowest priority: every host that has
# no explicit HTTP router of its own is redirected to HTTPS.
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: redirect-https
namespace: kube-system
spec:
redirectScheme:
scheme: https
permanent: true
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: http-redirect
namespace: kube-system
spec:
entryPoints:
- web
routes:
- match: HostRegexp(`.+`)
kind: Rule
priority: 1
middlewares:
- name: redirect-https
services:
- name: noop@internal
kind: TraefikService
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmchartconfig.yaml
- http-redirect.yaml
+77
View File
@@ -0,0 +1,77 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: vaultwarden
labels:
app: vaultwarden
spec:
replicas: 1
strategy:
type: Recreate # the RWO local volume cannot be mounted by two pods
selector:
matchLabels:
app: vaultwarden
template:
metadata:
labels:
app: vaultwarden
spec:
containers:
- name: vaultwarden
image: vaultwarden/server:1.37.3
resources:
requests:
memory: 128Mi
cpu: 100m
limits:
memory: 512Mi
# SSO is switched on by the optional Secret vaultwarden-sso, which holds
# SSO_ENABLED=true and SSO_CLIENT_SECRET (the secret of the Keycloak client
# "Vaultwarden"). Without it Vaultwarden starts with SSO off.
envFrom:
- secretRef:
name: vaultwarden-sso
optional: true
env:
- name: DOMAIN
value: https://vw.btwiusearch.net
- name: ORG_GROUPS_ENABLED
value: "true"
- name: SSO_AUTHORITY
value: https://auth.hexor.cy/auth/realms/hexor
- name: SSO_CLIENT_ID
value: Vaultwarden
- name: SSO_SCOPES
value: openid profile email offline_access
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: vaultwarden-db-url
key: DATABASE_URL
- name: ADMIN_TOKEN
valueFrom:
secretKeyRef:
name: admin-token
key: ADMIN_TOKEN
ports:
- name: http
containerPort: 80
protocol: TCP
readinessProbe:
httpGet:
path: /alive
port: http
periodSeconds: 10
livenessProbe:
httpGet:
path: /alive
port: http
initialDelaySeconds: 30
periodSeconds: 30
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: vaultwarden-data
+23
View File
@@ -0,0 +1,23 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vaultwarden
annotations:
cert-manager.io/cluster-issuer: letsencrypt
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
rules:
- host: vw.btwiusearch.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vaultwarden
port:
number: 80
tls:
- secretName: vaultwarden-tls
hosts:
- vw.btwiusearch.net
@@ -0,0 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- secrets.yaml
- pvc.yaml
- deployment.yaml
- service.yaml
- ingress.yaml
+12
View File
@@ -0,0 +1,12 @@
# Attachments, sends, icon cache and RSA keys live here; the database is in PostgreSQL.
# A local-path volume is bound to one node: the pod stays on the node it first ran on.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: vaultwarden-data
spec:
accessModes: [ReadWriteOnce]
storageClassName: local-path
resources:
requests:
storage: 2Gi
+78
View File
@@ -0,0 +1,78 @@
# Read-only access to the vaultwarden-db Secret in the psql namespace
# (see postgresql/databases/vaultwarden.yaml for the other half).
apiVersion: v1
kind: ServiceAccount
metadata:
name: db-credentials-reader
---
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: psql-credentials
spec:
provider:
kubernetes:
remoteNamespace: psql
server:
caProvider:
type: ConfigMap
name: kube-root-ca.crt
key: ca.crt
auth:
serviceAccount:
name: db-credentials-reader
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: vaultwarden-db-url
spec:
refreshInterval: 1h
secretStoreRef:
kind: SecretStore
name: psql-credentials
target:
name: vaultwarden-db-url
template:
data:
DATABASE_URL: "postgresql://{{ .username }}:{{ .password }}@main-rw.psql.svc:5432/vaultwarden"
data:
- secretKey: username
remoteRef:
key: vaultwarden-db
property: username
- secretKey: password
remoteRef:
key: vaultwarden-db
property: password
---
# Admin panel token (/admin). Generated once; read it with:
# kubectl -n vaultwarden get secret admin-token -o jsonpath='{.data.ADMIN_TOKEN}' | base64 -d
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
metadata:
name: admin-token
spec:
length: 48
digits: 10
symbols: 0
noUpper: false
allowRepeat: true
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: admin-token
spec:
refreshPolicy: CreatedOnce
target:
name: admin-token
template:
data:
ADMIN_TOKEN: "{{ .password }}"
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
name: admin-token
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: vaultwarden
spec:
selector:
app: vaultwarden
ports:
- protocol: TCP
port: 80
targetPort: http
+20
View File
@@ -0,0 +1,20 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: airtrail
namespace: argocd
spec:
project: apps
destination:
namespace: airtrail
server: https://kubernetes.default.svc
source:
repoURL: ssh://git@gt.hexor.cy:30022/ab/homelab.git
targetRevision: HEAD
path: k8s/apps/airtrail
syncPolicy:
automated:
selfHeal: true
prune: true
syncOptions:
- CreateNamespace=true
+66
View File
@@ -0,0 +1,66 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: airtrail
labels:
app: airtrail
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: airtrail
template:
metadata:
labels:
app: airtrail
spec:
securityContext:
fsGroup: 1000
containers:
- name: airtrail
image: johly/airtrail:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 3000
env:
- name: ORIGIN
value: https://at.hexor.cy
- name: DB_URL
valueFrom:
secretKeyRef:
name: airtrail-creds
key: DB_URL
- name: UPLOAD_LOCATION
value: /app/uploads
- name: BODY_SIZE_LIMIT
value: 20M
volumeMounts:
- name: uploads
mountPath: /app/uploads
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
startupProbe:
tcpSocket:
port: http
periodSeconds: 10
failureThreshold: 30
readinessProbe:
tcpSocket:
port: http
periodSeconds: 10
livenessProbe:
tcpSocket:
port: http
periodSeconds: 30
volumes:
- name: uploads
persistentVolumeClaim:
claimName: airtrail-uploads
+25
View File
@@ -0,0 +1,25 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: airtrail-creds
spec:
target:
name: airtrail-creds
deletionPolicy: Delete
template:
type: Opaque
data:
DB_URL: |-
postgresql://airtrail:{{ .airtrail | trim | urlquery | replace "+" "%20" }}@psql.psql.svc:5432/airtrail
data:
- secretKey: airtrail
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
conversionStrategy: Default
decodingStrategy: None
metadataPolicy: None
key: 2a9deb39-ef22-433e-a1be-df1555625e22
property: fields[21].value
+25
View File
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: airtrail-tls-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt
traefik.ingress.kubernetes.io/router.middlewares: kube-system-https-redirect@kubernetescrd
acme.cert-manager.io/http01-edit-in-place: "true"
spec:
ingressClassName: traefik
rules:
- host: at.hexor.cy
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: airtrail
port:
number: 3000
tls:
- secretName: airtrail-tls
hosts:
- at.hexor.cy
+10
View File
@@ -0,0 +1,10 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- app.yaml
- external-secrets.yaml
- storage.yaml
- deployment.yaml
- service.yaml
- ingress.yaml
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: airtrail
labels:
app: airtrail
spec:
type: ClusterIP
selector:
app: airtrail
ports:
- name: http
protocol: TCP
port: 3000
targetPort: http
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: airtrail-uploads
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 1Gi
+24 -14
View File
@@ -8,7 +8,7 @@ data:
#!/usr/bin/env bash
set -euo pipefail
PORT="${1:-5847}"
PORT="${1:-5848}"
VPN_CIDR="${2:-10.8.0.0/16}"
POLICY_FILE="${3:-/run/amnezia/policy/policy.conf}"
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
@@ -96,12 +96,18 @@ data:
[[ "${source}" == \#* ]] && continue
if [ -n "${extra:-}" ] \
|| ! valid_ipv4_32 "${source}" \
|| ! valid_ipv4_32 "${destination:-}" \
|| ! routed_through_awg0 "${source}" \
|| ! routed_through_awg0 "${destination}"; then
echo "Invalid or out-of-tunnel policy line: ${source:-} ${destination:-} ${extra:-}" >&2
"${IPTABLES[@]}" -F "${inactive}"
return 1
|| ! valid_ipv4_32 "${destination:-}"; then
echo "Skipping malformed policy line: ${source:-} ${destination:-} ${extra:-}" >&2
continue
fi
# A pair whose endpoints are not (yet) routed through awg0 is skipped
# rather than fatal. During interface bring-up the awg0 route may not be
# in place, and a missing ACCEPT simply leaves the pair subject to the
# client-isolation DROP below (fail-closed) instead of aborting the whole
# policy and crash-looping the container.
if ! routed_through_awg0 "${source}" || ! routed_through_awg0 "${destination}"; then
echo "Skipping out-of-tunnel policy pair: ${source} ${destination}" >&2
continue
fi
"${IPTABLES[@]}" -A "${inactive}" \
-i awg0 -o awg0 -s "${source}" -d "${destination}" \
@@ -181,7 +187,7 @@ data:
#!/usr/bin/env bash
set -euo pipefail
PORT="${1:-5847}"
PORT="${1:-5848}"
VPN_CIDR="${2:-10.8.0.0/16}"
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
POLICY_CHAIN_A="AMNEZIAWG-POLICY-A"
@@ -239,7 +245,7 @@ data:
#!/usr/bin/env bash
set -euo pipefail
PORT="${1:-5847}"
PORT="${1:-5848}"
VPN_CIDR="${2:-10.8.0.0/16}"
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
IPTABLES=(iptables -w 5)
@@ -270,9 +276,13 @@ data:
exit 1
fi
FIRST_FORWARD_RULE="$("${IPTABLES[@]}" -S FORWARD | grep '^-A FORWARD ' | sed -n '1p')"
[[ "${FIRST_FORWARD_RULE}" == *"--comment amneziawg-forward-jump"* ]]
[[ "${FIRST_FORWARD_RULE}" == *"-j ${FORWARD_CHAIN}"* ]]
# The amneziawg FORWARD jump must be present. firewall-up inserts it at
# position 1, but CNI controllers (e.g. kube-router) legitimately re-assert
# their own jump ahead of it. Requiring the strict first position makes the
# readiness probe flap as the two fight over rule order. Isolation still holds
# as long as the jump is traversed and no earlier rule accepts awg0-to-awg0
# traffic (kube-router's chain returns non-pod traffic without a decision).
"${IPTABLES[@]}" -C FORWARD -m comment --comment amneziawg-forward-jump -j "${FORWARD_CHAIN}"
ACTIVE_POLICY="$("${IPTABLES[@]}" -S "${FORWARD_CHAIN}" 2>/dev/null \
| awk '$1 == "-A" && $3 == "-j" && $4 ~ /^AMNEZIAWG-POLICY-[AB]$/ { print $4; exit }')"
[ -n "${ACTIVE_POLICY}" ]
@@ -362,12 +372,12 @@ data:
apply_live_config() {
# Fail closed during a peer/key and policy transition. /dev/null produces
# an Internet-only policy with all awg0-to-awg0 traffic isolated.
/scripts/firewall-up.sh 5847 10.8.0.0/16 /dev/null
/scripts/firewall-up.sh 5848 10.8.0.0/16 /dev/null
render_config
awg-quick strip "${RUNTIME_CONFIG}" > "${SYNC_CONFIG}"
chmod 0600 "${SYNC_CONFIG}"
awg syncconf awg0 "${SYNC_CONFIG}"
/scripts/firewall-up.sh 5847 10.8.0.0/16 "${POLICY_FILE}"
/scripts/firewall-up.sh 5848 10.8.0.0/16 "${POLICY_FILE}"
}
watch_client_config() {
+5 -5
View File
@@ -52,7 +52,7 @@ spec:
fieldRef:
fieldPath: spec.nodeName
- name: PORT
value: "5847"
value: "5848"
command:
- /bin/bash
- -lc
@@ -116,14 +116,14 @@ spec:
- /scripts/run.sh
ports:
- name: awg
containerPort: 5847
containerPort: 5848
protocol: UDP
readinessProbe:
exec:
command:
- /bin/bash
- -lc
- /scripts/firewall-check.sh 5847 10.8.0.0/16
- /scripts/firewall-check.sh 5848 10.8.0.0/16
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 8
@@ -133,7 +133,7 @@ spec:
command:
- /bin/bash
- -lc
- /scripts/firewall-check.sh 5847 10.8.0.0/16 || /scripts/firewall-up.sh 5847 10.8.0.0/16
- /scripts/firewall-check.sh 5848 10.8.0.0/16 || /scripts/firewall-up.sh 5848 10.8.0.0/16
periodSeconds: 5
timeoutSeconds: 15
failureThreshold: 12
@@ -142,7 +142,7 @@ spec:
command:
- /bin/bash
- -lc
- /scripts/firewall-check.sh 5847 10.8.0.0/16 || /scripts/firewall-up.sh 5847 10.8.0.0/16
- /scripts/firewall-check.sh 5848 10.8.0.0/16 || /scripts/firewall-up.sh 5848 10.8.0.0/16
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 15
+14 -14
View File
@@ -16,21 +16,21 @@ spec:
[Interface]
PrivateKey = {{ .server_private_key }}
Address = 10.8.0.1/16
ListenPort = 5847
ListenPort = 5848
MTU = 1376
Jc = 4
Jmin = 64
Jmax = 128
S1 = 15
S2 = 18
S3 = 20
S4 = 23
H1 = 1020325451
H2 = 3288052141
H3 = 1766607858
H4 = 2528465083
PostUp = /scripts/firewall-up.sh 5847 10.8.0.0/16
PostDown = /scripts/firewall-down.sh 5847 10.8.0.0/16
Jc = 5
Jmin = 96
Jmax = 320
S1 = 37
S2 = 47
S3 = 29
S4 = 43
H1 = 1247270921
H2 = 1956534873
H3 = 886504771
H4 = 1623791047
PostUp = /scripts/firewall-up.sh 5848 10.8.0.0/16
PostDown = /scripts/firewall-down.sh 5848 10.8.0.0/16
data:
- secretKey: server_private_key
sourceRef:
+1 -1
View File
@@ -25,7 +25,7 @@ spec:
kubernetes.io/hostname: cy.tail2fe2d.ts.net
containers:
- name: amnezia-fellow
image: ultradesu/amnezia-fellow:latest
image: ultradesu/amnezia-fellow:1.0.2
imagePullPolicy: Always
ports:
- name: http
@@ -0,0 +1,106 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: firefly-data-importer-sync
labels:
app.kubernetes.io/name: firefly-data-importer-sync
spec:
schedule: "17 */6 * * *"
timeZone: Europe/London
concurrencyPolicy: Forbid
startingDeadlineSeconds: 1800
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 5
jobTemplate:
spec:
backoffLimit: 1
activeDeadlineSeconds: 1200
template:
metadata:
labels:
app.kubernetes.io/name: firefly-data-importer-sync
spec:
automountServiceAccountToken: false
restartPolicy: Never
nodeSelector:
kubernetes.io/hostname: ai.tail2fe2d.ts.net
tolerations:
- key: workload
operator: Equal
value: ai
effect: NoSchedule
containers:
- name: data-importer-sync
image: fireflyiii/data-importer:version-2.3.4
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -c
- |-
php artisan importer:import /import/import.json
status=$?
if [ "${status}" -eq 73 ]; then
echo "No new transactions; considering the sync successful."
exit 0
fi
exit "${status}"
env:
- name: FIREFLY_III_URL
value: http://firefly-iii
- name: VANITY_URL
value: http://ff.lan
- name: FIREFLY_III_ACCESS_TOKEN
valueFrom:
secretKeyRef:
name: firefly-data-importer-secrets
key: FIREFLY_III_ACCESS_TOKEN
- name: ENABLE_BANKING_APP_ID
valueFrom:
secretKeyRef:
name: firefly-data-importer-secrets
key: ENABLE_BANKING_APP_ID
- name: ENABLE_BANKING_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: firefly-data-importer-secrets
key: ENABLE_BANKING_PRIVATE_KEY
- name: IMPORT_DIR_ALLOWLIST
value: /import
# Recurring imports intentionally overlap their date range.
# Keep rejecting duplicates, but do not fail the Job for them.
- name: IGNORE_DUPLICATE_ERRORS
value: "true"
- name: TZ
value: Europe/London
- name: APP_ENV
value: production
- name: APP_DEBUG
value: "false"
- name: LOG_LEVEL
value: notice
volumeMounts:
- name: import-config
mountPath: /import
readOnly: true
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: "1"
memory: 512Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumes:
- name: import-config
secret:
secretName: firefly-data-importer-secrets
items:
- key: import.json
path: import.json
securityContext:
seccompProfile:
type: RuntimeDefault
@@ -66,6 +66,10 @@ spec:
-----BEGIN PRIVATE KEY-----
{{ .private_key | replace "-----BEGIN PRIVATE KEY-----" "" | replace "-----END PRIVATE KEY-----" "" | replace " " "" | trim }}
-----END PRIVATE KEY-----
FIREFLY_III_ACCESS_TOKEN: |-
{{ .firefly_access_token }}
import.json: |-
{{ .import_config }}
data:
- secretKey: app_id
sourceRef:
@@ -83,3 +87,19 @@ spec:
remoteRef:
key: a1867c81-715c-47cd-978d-14ea5bcedea9
property: fields[4].value
- secretKey: firefly_access_token
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
key: a1867c81-715c-47cd-978d-14ea5bcedea9
property: fields[6].value
- secretKey: import_config
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
key: a1867c81-715c-47cd-978d-14ea5bcedea9
property: fields[5].value
+1 -1
View File
@@ -4,12 +4,12 @@ kind: Kustomization
resources:
- app.yaml
- data-importer.yaml
- data-importer-sync.yaml
- data-importer-ingress.yaml
- external-secrets.yaml
- postgres.yaml
- storage.yaml
- storage-prep.yaml
- traefik-ai.yaml
- network-policy.yaml
helmCharts:
+3
View File
@@ -22,6 +22,9 @@ spec:
- podSelector:
matchLabels:
app.kubernetes.io/name: firefly-data-importer
- podSelector:
matchLabels:
app.kubernetes.io/name: firefly-data-importer-sync
# hostNetwork traffic is seen as node traffic rather than Pod traffic.
- ipBlock:
cidr: 192.168.1.117/32
+1
View File
@@ -8,3 +8,4 @@ resources:
- ingress.yaml
- deployment.yaml
- servicemonitor.yaml
- socks-proxy.yaml
+106
View File
@@ -0,0 +1,106 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: furumi-socks-proxy
labels:
app: furumi-socks-proxy
spec:
replicas: 1
selector:
matchLabels:
app: furumi-socks-proxy
template:
metadata:
labels:
app: furumi-socks-proxy
spec:
nodeSelector:
kubernetes.io/hostname: ai.tail2fe2d.ts.net
tolerations:
- key: workload
operator: Equal
value: ai
effect: NoSchedule
containers:
- name: socks-proxy
image: serjs/go-socks5-proxy:v0.0.4
imagePullPolicy: IfNotPresent
env:
- name: REQUIRE_AUTH
value: "true"
- name: PROXY_USER
value: furumi
- name: PROXY_PASSWORD
value: furumi
- name: PROXY_PORT
value: "1080"
ports:
- name: socks5
containerPort: 1080
protocol: TCP
readinessProbe:
tcpSocket:
port: socks5
initialDelaySeconds: 2
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
tcpSocket:
port: socks5
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
cpu: 200m
memory: 64Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
securityContext:
seccompProfile:
type: RuntimeDefault
---
apiVersion: v1
kind: Service
metadata:
name: furumi-socks-proxy
labels:
app: furumi-socks-proxy
spec:
type: ClusterIP
selector:
app: furumi-socks-proxy
ports:
- name: socks5
protocol: TCP
port: 1080
targetPort: socks5
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: furumi-socks-proxy-ingress
spec:
podSelector:
matchLabels:
app: furumi-socks-proxy
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: furumi-player
ports:
- protocol: TCP
port: 1080
+1 -1
View File
@@ -48,7 +48,7 @@ spec:
value: "true"
- name: GITEA__service__CAPTCHA_TYPE
value: "cfturnstile"
- name: GITEA__webhook__ALLOWED_HOST_LIST
- name: GITEA__security__ALLOWED_HOST_LIST
value: "*"
envFrom:
- secretRef:
+20
View File
@@ -0,0 +1,20 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: lan-play
namespace: argocd
spec:
project: apps
destination:
namespace: lan-play
server: https://kubernetes.default.svc
source:
repoURL: ssh://git@gt.hexor.cy:30022/ab/homelab.git
targetRevision: HEAD
path: k8s/apps/lan-play
syncPolicy:
automated:
selfHeal: true
prune: true
syncOptions:
- CreateNamespace=true
+63
View File
@@ -0,0 +1,63 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: lan-play
labels:
app.kubernetes.io/name: lan-play
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: lan-play
template:
metadata:
labels:
app.kubernetes.io/name: lan-play
spec:
nodeSelector:
kubernetes.io/hostname: ai.tail2fe2d.ts.net
tolerations:
- key: workload
operator: Equal
value: ai
effect: NoSchedule
containers:
- name: lan-play
image: ultradesu/lan-play:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 8080
protocol: TCP
volumeMounts:
- name: media
mountPath: /media
readinessProbe:
tcpSocket:
port: http
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: http
initialDelaySeconds: 20
periodSeconds: 30
resources:
requests:
cpu: "2"
memory: 2Gi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumes:
- name: media
hostPath:
path: /k8s/lan-play
type: DirectoryOrCreate
securityContext:
seccompProfile:
type: RuntimeDefault
+63
View File
@@ -0,0 +1,63 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: lan-play-auth
spec:
forwardAuth:
address: http://auth-proxy.auth-proxy.svc:80/auth
trustForwardHeader: true
authResponseHeaders:
- X-Auth-Request-User
- X-Auth-Request-Email
- X-Auth-Request-Groups
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: lan-play-public
spec:
entryPoints:
- websecure
routes:
- match: Host(`lp.hexor.cy`)
kind: Rule
middlewares:
- name: lan-play-auth
services:
- name: lan-play
port: 80
tls:
secretName: lan-play-tls
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: lan-play-tls
spec:
secretName: lan-play-tls
issuerRef:
name: letsencrypt
kind: ClusterIssuer
dnsNames:
- lp.hexor.cy
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: lan-play-local
labels:
firefly.hexor.cy/private-ai: "true"
annotations:
kubernetes.io/ingress.class: traefik-ai
spec:
rules:
- host: video.lan
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: lan-play
port:
number: 80
+8
View File
@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- app.yaml
- deployment.yaml
- service.yaml
- ingress.yaml
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: lan-play
labels:
app.kubernetes.io/name: lan-play
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: lan-play
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
+1 -1
View File
@@ -70,6 +70,7 @@ spec:
action: suggest
template: "{{ `{{ user.email }}` }}"
set_email_verification: always
on_conflict: replace
data:
- secretKey: oauth_client_id
sourceRef:
@@ -93,4 +94,3 @@ spec:
metadataPolicy: None
key: ca76867f-49f3-4a30-9ef3-b05af35ee49a
property: fields[1].value
on_conflict: replace
+1 -1
View File
@@ -34,7 +34,7 @@ spec:
mountPath: /templates/subscription
containers:
- name: pasarguard-web
image: pasarguard/panel:v5.0.3
image: pasarguard/panel:v5.3.0
imagePullPolicy: Always
envFrom:
- secretRef:
+1 -1
View File
@@ -18,7 +18,7 @@ spec:
spec:
nodeSelector:
kubernetes.io/os: linux
kubernetes.io/hostname: master.tail2fe2d.ts.net
# kubernetes.io/hostname: master.tail2fe2d.ts.net
volumes:
- name: data
persistentVolumeClaim:
+1 -1
View File
@@ -10,7 +10,7 @@ resources:
helmCharts:
- name: argo-cd
repo: https://argoproj.github.io/argo-helm
version: 10.2.1
version: 10.6.0
releaseName: argocd
namespace: argocd
valuesFile: values.yaml
+1 -1
View File
@@ -10,7 +10,7 @@ resources:
helmCharts:
- name: cert-manager
repo: https://charts.jetstack.io
version: 1.20.0
version: 1.21.1
releaseName: cert-manager
namespace: cert-manager
valuesFile: values.yaml
+1 -1
View File
@@ -7,7 +7,7 @@ resources:
helmCharts:
- name: external-secrets
repo: https://charts.external-secrets.io
version: 1.1.0
version: 2.10.0
releaseName: external-secrets
namespace: external-secrets
valuesFile: values.yaml
+1 -1
View File
@@ -9,7 +9,7 @@ resources:
helmCharts:
- name: keycloakx
repo: https://codecentric.github.io/helm-charts
version: 7.1.11
version: 7.3.1
releaseName: keycloak
namespace: keycloak
valuesFile: values.yaml
@@ -7,6 +7,7 @@ resources:
- coredns-internal-resolve.yaml
- https-middleware.yaml
- node-external-ip-labeler.yaml
- traefik-ai.yaml
helmCharts:
- name: csi-driver-nfs
@@ -9,11 +9,13 @@ apiVersion: v1
kind: ServiceAccount
metadata:
name: traefik-ai
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: traefik-ai
namespace: firefly-iii
rules:
- apiGroups:
- ""
@@ -52,6 +54,7 @@ apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: traefik-ai
namespace: firefly-iii
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
@@ -59,11 +62,65 @@ roleRef:
subjects:
- kind: ServiceAccount
name: traefik-ai
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: traefik-ai
namespace: lan-play
rules:
- apiGroups:
- ""
resources:
- services
- secrets
- endpoints
verbs:
- get
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
- ingresses/status
verbs:
- update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: traefik-ai
namespace: lan-play
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: traefik-ai
subjects:
- kind: ServiceAccount
name: traefik-ai
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: firefly-traefik-ai
name: traefik-ai
rules:
- apiGroups:
- ""
@@ -85,20 +142,21 @@ rules:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: firefly-traefik-ai
name: traefik-ai
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: firefly-traefik-ai
name: traefik-ai
subjects:
- kind: ServiceAccount
name: traefik-ai
namespace: firefly-iii
namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: traefik-ai
namespace: kube-system
labels:
app.kubernetes.io/name: traefik-ai
spec:
@@ -130,7 +188,7 @@ spec:
args:
- --entrypoints.web.address=192.168.1.117:80
- --providers.kubernetesingress=true
- --providers.kubernetesingress.namespaces=firefly-iii
- --providers.kubernetesingress.namespaces=firefly-iii,lan-play
- --providers.kubernetesingress.ingressclass=traefik-ai
- --providers.kubernetesingress.labelselector=firefly.hexor.cy/private-ai=true
- --providers.kubernetescrd=false
+1 -1
View File
@@ -7,7 +7,7 @@ kind: Kustomization
helmCharts:
- name: longhorn
repo: https://charts.longhorn.io
version: 1.12.0
version: 1.12.1
releaseName: longhorn
namespace: longhorn-system
valuesFile: values.yaml
+13
View File
@@ -144,6 +144,8 @@ spec:
{{ .amnezia_fellow }}
USER_web_petting: |-
{{ .web_petting }}
USER_airtrail: |-
{{ .airtrail }}
data:
- secretKey: authentik
sourceRef:
@@ -365,3 +367,14 @@ spec:
metadataPolicy: None
key: 2a9deb39-ef22-433e-a1be-df1555625e22
property: fields[20].value
- secretKey: airtrail
sourceRef:
storeRef:
name: vaultwarden-login
kind: ClusterSecretStore
remoteRef:
conversionStrategy: Default
decodingStrategy: None
metadataPolicy: None
key: 2a9deb39-ef22-433e-a1be-df1555625e22
property: fields[21].value
+4 -4
View File
@@ -14,15 +14,15 @@ resources:
helmCharts:
- name: kube-prometheus-stack
repo: https://prometheus-community.github.io/helm-charts
version: 84.3.0
version: 88.6.2
releaseName: prometheus
namespace: prometheus
valuesFile: prom-values.yaml
includeCRDs: true
- name: loki
repo: https://grafana.github.io/helm-charts
version: 6.29.0
repo: https://grafana-community.github.io/helm-charts
version: 18.11.7
releaseName: loki
namespace: prometheus
valuesFile: loki-values.yaml
@@ -30,7 +30,7 @@ helmCharts:
- name: promtail
repo: https://grafana.github.io/helm-charts
version: 6.16.6
version: 6.17.1
releaseName: promtail
namespace: prometheus
valuesFile: promtail-values.yaml
+9
View File
@@ -75,6 +75,10 @@ prometheus:
static_configs:
- targets: ['prom-a2s-exporter.counter-strike.svc:9841']
labels: {instance: master}
- job_name: macos_prom_exporter
static_configs:
- targets: ['macbook-pro.tail2fe2d.ts.net:9100']
labels: {instance: ultrabook-pro}
retention: "380d"
retentionSize: "0"
@@ -91,6 +95,11 @@ prometheus:
grafana:
enabled: true
sidecar:
datasources:
alertmanager:
uid: ff9iga3xqregwc
serviceAccount:
create: true
+2 -2
View File
@@ -18,7 +18,7 @@ spec:
serviceAccountName: system-upgrade
upgrade:
image: rancher/k3s-upgrade
version: v1.36.1+k3s1
version: v1.36.4+k3s1
---
# Agent plan
apiVersion: upgrade.cattle.io/v1
@@ -43,4 +43,4 @@ spec:
serviceAccountName: system-upgrade
upgrade:
image: rancher/k3s-upgrade
version: v1.36.1+k3s1
version: v1.36.4+k3s1
+2
View File
@@ -7,6 +7,7 @@ provider "registry.terraform.io/hashicorp/kubernetes" {
hashes = [
"h1:G9QqKNpcztBRqrywtlNylFJSpGzDfRFtO8hcWLdkvRY=",
"h1:iDIPyL9K9FXc8ievR5vRmXsgT1/I9Vq9CFDKm1Zo8bQ=",
"h1:oodIAuFMikXNmEtil5MQgP4dfSctUBYQiGJfjbsF3NY=",
"zh:0215c5c60be62028c09a2f22458e89cda3ef5830a632299f1d401eb3538874b0",
"zh:09ebb9f442431e278a310a9423f32caf467cb4b3cad3fe59573ca71fa7b14e20",
"zh:0c4e5912f83bb35846ae0a9ae54fc320706ee61894cd21cc6b4181b1c5a2fa5c",
@@ -27,6 +28,7 @@ provider "registry.terraform.io/keycloak/keycloak" {
constraints = ">= 5.0.0"
hashes = [
"h1:3DuKdVeOxwULh7l6bvJKWZvsgSZo92rtnrdvyp+X2Lc=",
"h1:VSlrHkPQm4mczl+5ttuj6jAh2eVvupaoeymEn9Qi5vo=",
"h1:reFEBcuBZf2SVqDa1YIaNLuIW72uEVsOu5hHhQgfHi0=",
"zh:19be4505b17e4818db121a82917cb6723019cf379cfb82b720eaa2886f15bede",
"zh:2bd1565ed22db6a9fb50d60626e22c277f3b034a71f65e6c0011e42f56cad2bb",
+2 -1
View File
@@ -243,6 +243,7 @@ resource "keycloak_openid_client" "oauth2_app" {
valid_redirect_uris = each.value.redirect_uris
valid_post_logout_redirect_uris = each.value.post_logout_redirect_uris
web_origins = each.value.web_origins
access_token_lifespan = each.value.access_token_lifespan
}
resource "keycloak_openid_group_membership_protocol_mapper" "oauth2_app_groups" {
@@ -292,7 +293,7 @@ resource "keycloak_group" "oauth2_app" {
resource "kubernetes_config_map_v1" "auth_proxy_routes" {
metadata {
name = "auth-proxy-routes"
namespace = "auth-proxy"
namespace = "keycloak"
}
data = {
+2 -1
View File
@@ -19,5 +19,6 @@ provider "keycloak" {
}
provider "kubernetes" {
config_path = "~/.kube/config"
config_path = "~/.kube/config"
config_context = "hexor-lab"
}
+15 -3
View File
@@ -24,6 +24,10 @@ proxy_applications = {
domain = "import.hexor.cy"
allowed_groups = ["hexor-admin"]
}
LanPlay = {
domain = "lp.hexor.cy"
allowed_groups = ["hexor-admin"]
}
}
oauth2_applications = {
@@ -33,11 +37,19 @@ oauth2_applications = {
post_logout_redirect_uris = ["https://gt.hexor.cy/*"]
}
ArgoCD = {
redirect_uris = ["https://ag.hexor.cy/api/dex/callback"]
web_origins = ["https://ag.hexor.cy"]
post_logout_redirect_uris = ["https://ag.hexor.cy/*"]
redirect_uris = ["https://ag.hexor.cy/api/dex/callback", "https://ag.btwiusearch.net/api/dex/callback"]
web_origins = ["https://ag.hexor.cy", "https://ag.btwiusearch.net"]
post_logout_redirect_uris = ["https://ag.hexor.cy/*", "https://ag.btwiusearch.net/*"]
extra_optional_scopes = ["offline_access"]
}
Vaultwarden = {
redirect_uris = ["https://vw.btwiusearch.net/identity/connect/oidc-signin"]
web_origins = ["https://vw.btwiusearch.net"]
post_logout_redirect_uris = ["https://vw.btwiusearch.net/*"]
extra_optional_scopes = ["offline_access"]
# Bitwarden clients treat tokens shorter than ~10 minutes as expired.
access_token_lifespan = "900"
}
PGAdmin = {
redirect_uris = ["https://pg.hexor.cy/oauth2/authorize"]
web_origins = ["https://pg.hexor.cy"]
+1
View File
@@ -51,6 +51,7 @@ variable "oauth2_applications" {
extra_default_scopes = optional(list(string), [])
extra_optional_scopes = optional(list(string), [])
allowed_groups = optional(list(string), [])
access_token_lifespan = optional(string) # seconds; null keeps the realm default
}))
default = {}
}