diff --git a/.gitignore b/.gitignore index a1df2e8..7d11c76 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,3 @@ AGENTS.md k8s/charts/README.md .opencode/ opencode.json -*.pyc -.venv/ -METALLB.md diff --git a/.yamllint.yaml b/.yamllint.yaml index f10a375..4ffc165 100644 --- a/.yamllint.yaml +++ b/.yamllint.yaml @@ -7,7 +7,6 @@ yaml-files: ignore: | k8s/charts/ - **/sealedsecrets.yaml extends: default diff --git a/README.md b/README.md index 3da2575..ac42e9c 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,3 @@ ethtool -K eno1 tx off rx off ```bash helm upgrade argo-cd argo/argo-cd -n argo-cd -f k8s/helm/argo-cd/values.yaml ``` - -- create a new user and send them an email - -```bash -scripts/users.py add -u catalin -n cătălin -e catalin@roboces.dev -``` - -## MetalLB and reserved VIPs - -- traefik VIP: 192.168.1.127 -- adguard dns VIP: 192.168.1.125 diff --git a/k8s/argo-apps/adguard.yaml b/k8s/argo-apps/adguard.yaml index d7611da..c36b601 100644 --- a/k8s/argo-apps/adguard.yaml +++ b/k8s/argo-apps/adguard.yaml @@ -41,10 +41,3 @@ spec: service: dns: type: LoadBalancer - port: 53 - annotations: - metallb.universe.tf/address-pool: adguard-dns-pool - loadBalancerIP: 192.168.1.125 - web: - type: ClusterIP - port: 8006 diff --git a/k8s/argo-apps/audiobookshelf.yaml b/k8s/argo-apps/audiobookshelf.yaml deleted file mode 100644 index d52b97a..0000000 --- a/k8s/argo-apps/audiobookshelf.yaml +++ /dev/null @@ -1,79 +0,0 @@ ---- -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: audiobookshelf - namespace: argo-cd -spec: - destination: - name: '' - namespace: apps-roboces - server: https://kubernetes.default.svc - sources: - - chart: audiobookshelf - repoURL: https://charts.christianhuth.de - targetRevision: 2.4.0 - helm: - valuesObject: - service: - type: ClusterIP - port: 8081 - ingress: - enabled: true - className: traefik - annotations: - traefik.ingress.kubernetes.io/router.entrypoints: websecure - traefik.ingress.kubernetes.io/router.tls: "true" - hosts: - - host: audiobooks.roboces.dev - paths: - - path: / - pathType: Prefix - - host: audiobooks.fuku - paths: - - path: / - pathType: Prefix - tls: [] - audiobookshelf: - timezone: Europe/Madrid - config: - persistence: - enabled: true - storageClassName: truenas-nfs-csi - accessModes: - - ReadWriteMany - resources: - requests: - storage: 5Gi - metadata: - persistence: - enabled: true - storageClassName: truenas-nfs-csi - accessModes: - - ReadWriteMany - resources: - requests: - storage: 20Gi - backup: - persistence: - enabled: true - storageClassName: truenas-nfs-csi - accessModes: - - ReadWriteMany - resources: - requests: - storage: 20Gi - library: - persistence: - enabled: true - storageClassName: truenas-nfs-csi - accessModes: - - ReadWriteMany - resources: - requests: - storage: 500Gi - project: roboces - syncPolicy: - automated: - prune: true - selfHeal: true diff --git a/k8s/argo-apps/authelia.yaml b/k8s/argo-apps/authelia.yaml deleted file mode 100644 index 0bd9ddc..0000000 --- a/k8s/argo-apps/authelia.yaml +++ /dev/null @@ -1,170 +0,0 @@ ---- -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: authelia - namespace: argo-cd -spec: - destination: - name: '' - namespace: apps-roboces - server: https://kubernetes.default.svc - sources: - - chart: authelia - repoURL: https://charts.authelia.com - targetRevision: 0.11.6 - helm: - valuesObject: - enabled: true - pod: - autoscaling: - enabled: true - kind: Deployment - resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: 1 - memory: 1024Mi - service: - type: ClusterIP - port: 9091 - ingress: - enabled: true - className: traefik - annotations: - traefik.ingress.kubernetes.io/router.entrypoints: websecure - traefik.ingress.kubernetes.io/router.tls: "true" - secret: - existingSecret: secrets-authelia - mountPath: /secrets - additionalSecrets: - secrets-authelia: - path: secrets-authelia - items: - - key: authentication.ldap.password.txt - path: authentication.ldap.password.txt - - key: identity_validation.reset_password.jwt.hmac.key - path: identity_validation.reset_password.jwt.hmac.key - - key: session.encryption.key - path: session.encryption.key - - key: smtp_password.txt - path: smtp_password.txt - - key: storage.encryption.key - path: storage.encryption.key - - key: oidc.audiobookshelf.client_secret - path: oidc.audiobookshelf.client_secret - - key: identity_providers.oidc.jwks.key.pem - path: identity_providers.oidc.jwks.key.pem - persistence: - enabled: true - existingClaim: authelia-data - configMap: - log: - level: info - session: - name: authelia_session - same_site: lax - expiration: 1 hour - inactivity: 5 minutes - remember_me: 1 month - cookies: - - domain: roboces.dev - subdomain: auth - - domain: auth.fuku - subdomain: '' - authentication_backend: - ldap: - enabled: true - implementation: lldap - address: ldap://lldap.apps-fuku.svc.cluster.local:3890 - base_dn: dc=fuku,dc=local - additional_users_dn: ou=people - users_filter: (&({username_attribute}={input})(objectClass=person)) - additional_groups_dn: ou=groups - groups_filter: (member={dn}) - user: uid=admin,ou=people,dc=fuku,dc=local - attributes: - distinguished_name: dn - username: uid - display_name: cn - mail: mail - member_of: memberOf - group_name: cn - password: - disabled: false - secret_name: secrets-authelia - path: authentication.ldap.password.txt - storage: - local: - enabled: true - path: /config/db.sqlite3 - notifier: - smtp: - enabled: true - address: submissions://mail.fukurokuju.dev:465 - sender: Authelia - subject: "Your verification code is {code}" - identifier: auth.fuku - startup_check_address: test@authelia.com - disable_html_emails: false - disable_require_tls: false - disable_starttls: false - username: auth@fukurokuju.dev - password: - disabled: false - secret_name: secrets-authelia - path: smtp_password.txt - tls: - server_name: mail.fukurokuju.dev - skip_verify: false - minimum_version: TLS1.2 - access_control: - default_policy: two_factor - identity_providers: - oidc: - enabled: true - hmac_secret: - path: identity_providers.oidc.hmac.key - jwks: - - key_id: authelia - algorithm: RS256 - use: sig - key: - path: /secrets/secrets-authelia/identity_providers.oidc.jwks.key.pem - clients: - - client_id: audiobookshelf - client_name: Audiobookshelf - client_secret: - path: /secrets/secrets-authelia/oidc.audiobookshelf.client_secret - public: false - authorization_policy: two_factor - require_pkce: true - pkce_challenge_method: S256 - redirect_uris: - - https://audiobooks.roboces.dev/audiobookshelf/auth/openid/callback - - https://audiobooks.roboces.dev/audiobookshelf/auth/openid/mobile-redirect - - https://audiobooks.fuku/audiobookshelf/auth/openid/callback - - https://audiobooks.fuku/audiobookshelf/auth/openid/mobile-redirect - - audiobookshelf://oauth - scopes: - - openid - - profile - - groups - - email - response_types: - - code - grant_types: - - authorization_code - access_token_signed_response_alg: none - userinfo_signed_response_alg: none - token_endpoint_auth_method: client_secret_basic - - repoURL: https://git.roboces.dev/catalin/fukuops.git - path: k8s/services/authelia - targetRevision: main - project: roboces - syncPolicy: - automated: - prune: true - selfHeal: true diff --git a/k8s/argo-apps/authentik.yaml b/k8s/argo-apps/authentik.yaml index e421fa8..5bfe1bf 100644 --- a/k8s/argo-apps/authentik.yaml +++ b/k8s/argo-apps/authentik.yaml @@ -12,7 +12,7 @@ spec: sources: - chart: authentik repoURL: https://charts.goauthentik.io/ - targetRevision: 2026.8.* + targetRevision: 2026.5.* helm: valuesObject: authentik: @@ -63,16 +63,16 @@ spec: enabled: true minAvailable: 1 service: - type: ClusterIP - servicePortHttp: 9001 - servicePortHttps: 9444 + type: LoadBalancer + servicePortHttp: 9000 + servicePortHttps: 9443 annotations: traefik.ingress.kubernetes.io/service.serversscheme: https traefik.ingress.kubernetes.io/service.serverstransport: apps-fuku-skipverify-authentik@kubernetescrd # yamllint disable rule:line-length metrics: enabled: true service: - type: ClusterIP + type: LoadBalancer serviceMonitor: enabled: false ingress: diff --git a/k8s/argo-apps/dokuwiki.yaml b/k8s/argo-apps/dokuwiki.yaml index 83e2daf..6e5216d 100644 --- a/k8s/argo-apps/dokuwiki.yaml +++ b/k8s/argo-apps/dokuwiki.yaml @@ -26,7 +26,7 @@ spec: drop: - all service: - type: ClusterIP + type: LoadBalancer port: 8004 ingress: enabled: true diff --git a/k8s/argo-apps/forgejo.yaml b/k8s/argo-apps/forgejo.yaml index 3ceecd3..a6e6599 100644 --- a/k8s/argo-apps/forgejo.yaml +++ b/k8s/argo-apps/forgejo.yaml @@ -14,13 +14,13 @@ spec: sources: - chart: forgejo repoURL: code.forgejo.org/forgejo-helm - targetRevision: 17.1.5 + targetRevision: 17.1.4 helm: valuesObject: replicaCount: 1 service: http: - type: ClusterIP + type: LoadBalancer ssh: type: NodePort ingress: diff --git a/k8s/argo-apps/helm-rustical.yaml b/k8s/argo-apps/helm-rustical.yaml index 2962404..7fcba18 100644 --- a/k8s/argo-apps/helm-rustical.yaml +++ b/k8s/argo-apps/helm-rustical.yaml @@ -25,7 +25,7 @@ spec: targetCPUUtilizationPercentage: 80 targetMemoryUtilizationPercentage: 80 service: - type: ClusterIP + type: LoadBalancer port: 8001 targetPort: 4000 ingress: diff --git a/k8s/argo-apps/immich.yaml b/k8s/argo-apps/immich.yaml index 65cca62..31e0c8b 100644 --- a/k8s/argo-apps/immich.yaml +++ b/k8s/argo-apps/immich.yaml @@ -27,7 +27,7 @@ spec: drop: - all service: - type: ClusterIP + type: LoadBalancer config: database: hostname: "192.168.1.3" @@ -96,7 +96,7 @@ spec: machineLearning: enabled: true service: - type: ClusterIP + type: LoadBalancer secret: enabled: true existingSecretName: "immich-secrets" diff --git a/k8s/argo-apps/lldap.yaml b/k8s/argo-apps/lldap.yaml deleted file mode 100644 index 577dfd3..0000000 --- a/k8s/argo-apps/lldap.yaml +++ /dev/null @@ -1,55 +0,0 @@ ---- -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: lldap - namespace: argo-cd -spec: - destination: - name: '' - namespace: apps-fuku - server: https://kubernetes.default.svc - sources: - - repoURL: git.roboces.dev/catalin/fukuops - chart: lldap - targetRevision: 1.0.1 - helm: - valuesObject: - lldap: - service: - type: ClusterIP - httpPort: 17170 - ldapPort: 3890 - ingress: - enabled: true - className: traefik - annotations: - traefik.ingress.kubernetes.io/router.entrypoints: websecure - traefik.ingress.kubernetes.io/router.tls: "true" - hosts: - - host: ldap.fuku - paths: - - path: / - pathType: Prefix - - host: ldap.fukurokuju.dev - paths: - - path: / - pathType: Prefix - config: - ldapBaseDn: "dc=fuku,dc=local" - httpUrl: "https://ldap.fukurokuju.dev" - persistence: - enabled: true - storageClass: "truenas-nfs-csi" - accessMode: ReadWriteMany - size: 5Gi - secret: - existingSecretName: "lldap-secrets" - podDisruptionBudget: - enabled: true - minAvailable: 1 - project: fuku - syncPolicy: - automated: - prune: true - selfHeal: true diff --git a/k8s/argo-apps/meili.yaml b/k8s/argo-apps/meili.yaml index bf49ade..f0bbab3 100644 --- a/k8s/argo-apps/meili.yaml +++ b/k8s/argo-apps/meili.yaml @@ -18,7 +18,7 @@ spec: targetRevision: main - chart: meilisearch repoURL: https://meilisearch.github.io/meilisearch-kubernetes - targetRevision: 0.38.* + targetRevision: 0.37.* helm: valuesObject: environment: diff --git a/k8s/argo-apps/metallb.yaml b/k8s/argo-apps/metallb.yaml deleted file mode 100644 index 7f909f5..0000000 --- a/k8s/argo-apps/metallb.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: metallb - namespace: argo-cd -spec: - destination: - name: '' - namespace: metallb-system - server: https://kubernetes.default.svc - sources: - - repoURL: https://metallb.github.io/metallb - chart: metallb - targetRevision: 0.16.* - helm: - valuesObject: - crds: - enabled: true - - repoURL: https://git.roboces.dev/catalin/fukuops.git - path: k8s/services/metallb - targetRevision: main - project: management - syncPolicy: - automated: {} diff --git a/k8s/argo-apps/miniflux.yaml b/k8s/argo-apps/miniflux.yaml index af22dc8..2339c6f 100644 --- a/k8s/argo-apps/miniflux.yaml +++ b/k8s/argo-apps/miniflux.yaml @@ -27,7 +27,7 @@ spec: drop: - all service: - type: ClusterIP + type: LoadBalancer ingress: enabled: true className: "traefik" diff --git a/k8s/argo-apps/oxicloud.yaml b/k8s/argo-apps/oxicloud.yaml index fcf6b13..aa5556e 100644 --- a/k8s/argo-apps/oxicloud.yaml +++ b/k8s/argo-apps/oxicloud.yaml @@ -22,7 +22,7 @@ spec: accessMode: ReadWriteMany size: 50Gi service: - type: ClusterIP + type: LoadBalancer config: server: port: 8086 diff --git a/k8s/argo-apps/pgadmin.yaml b/k8s/argo-apps/pgadmin.yaml deleted file mode 100644 index 9ff22e8..0000000 --- a/k8s/argo-apps/pgadmin.yaml +++ /dev/null @@ -1,48 +0,0 @@ ---- -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: pgadmin - namespace: argo-cd -spec: - destination: - name: '' - namespace: apps-fuku - server: https://kubernetes.default.svc - project: fuku - sources: - - chart: pgadmin4 - repoURL: https://helm.runix.net - targetRevision: 1.66.0 - helm: - valuesObject: - image: - tag: "9.7" - service: - type: ClusterIP - port: 8010 - ingress: - enabled: true - ingressClassName: traefik - hosts: - - host: pgadmin.fuku - paths: - - path: / - pathType: Prefix - env: - email: admin@fukurokuju.dev - enhanced_cookie_protection: "False" - existingSecret: secrets-pgadmin - persistentVolume: - enabled: true - existingClaim: pgadmin-pgadmin4 - accessModes: - - ReadWriteMany - size: 10Gi - storageClass: truenas-nfs-csi - test: - enabled: false - syncPolicy: - automated: - prune: true - selfHeal: true diff --git a/k8s/argo-apps/portainer.yaml b/k8s/argo-apps/portainer.yaml index 8751358..3a16da4 100644 --- a/k8s/argo-apps/portainer.yaml +++ b/k8s/argo-apps/portainer.yaml @@ -19,10 +19,7 @@ spec: helm: valuesObject: service: - type: NodePort - httpPort: 9002 - httpsPort: 9445 - edgePort: 8005 + type: LoadBalancer ingress: enabled: true ingressClassName: traefik @@ -30,7 +27,6 @@ spec: - host: containers.fukurokuju.dev paths: - path: / - port: 9002 tls: [] persistence: storageClass: truenas-nfs-csi diff --git a/k8s/argo-apps/renovate.yaml b/k8s/argo-apps/renovate.yaml index 1384c39..91932e3 100644 --- a/k8s/argo-apps/renovate.yaml +++ b/k8s/argo-apps/renovate.yaml @@ -13,7 +13,7 @@ spec: sources: - chart: renovate repoURL: https://docs.renovatebot.com/helm-charts - targetRevision: 46.254.* + targetRevision: 46.251.* helm: valuesObject: renovate: diff --git a/k8s/argo-apps/system-upgrade-controller.yaml b/k8s/argo-apps/system-upgrade-controller.yaml new file mode 100644 index 0000000..1bb7c16 --- /dev/null +++ b/k8s/argo-apps/system-upgrade-controller.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: system-upgrade-controller + namespace: argo-cd +spec: + destination: + name: '' + namespace: system-upgrade + server: https://kubernetes.default.svc + source: + repoURL: https://git.roboces.dev/catalin/fukuops.git + path: k8s/services/system-upgrade-controller + targetRevision: feat/system-upgrade-controller + project: management + syncPolicy: + automated: {} diff --git a/k8s/argo-apps/vault-sm.yaml b/k8s/argo-apps/vault-sm.yaml index 22f0f3c..60f0564 100644 --- a/k8s/argo-apps/vault-sm.yaml +++ b/k8s/argo-apps/vault-sm.yaml @@ -18,15 +18,14 @@ spec: api: enabled: true service: - type: ClusterIP + type: LoadBalancer persistence: storageClass: truenas-nfs-csi accessMode: ReadWriteMany dashboard: enabled: true service: - type: ClusterIP - port: 8007 + type: LoadBalancer ingress: enabled: true className: traefik @@ -36,7 +35,7 @@ spec: - path: / pathType: Prefix backend: dashboard - port: 8007 + port: 80 - path: /api pathType: Prefix backend: api diff --git a/k8s/argo-apps/vaultwarden.yaml b/k8s/argo-apps/vaultwarden.yaml index d35a61b..11573e5 100644 --- a/k8s/argo-apps/vaultwarden.yaml +++ b/k8s/argo-apps/vaultwarden.yaml @@ -12,7 +12,7 @@ spec: sources: - repoURL: git.roboces.dev/catalin/fukuops chart: vaultwarden - targetRevision: 1.0.4 + targetRevision: 1.0.2 helm: valuesObject: vaultwarden: @@ -34,7 +34,7 @@ spec: drop: - all service: - type: ClusterIP + type: LoadBalancer ingress: enabled: true className: "traefik" diff --git a/k8s/charts/_template/values.yaml b/k8s/charts/_template/values.yaml index 5f70727..7aa1f5f 100644 --- a/k8s/charts/_template/values.yaml +++ b/k8s/charts/_template/values.yaml @@ -7,7 +7,7 @@ chart: tag: "" service: - type: ClusterIP + type: LoadBalancer port: 80 targetPort: 80 diff --git a/k8s/charts/dokuwiki/values.yaml b/k8s/charts/dokuwiki/values.yaml index 31c68df..f003331 100644 --- a/k8s/charts/dokuwiki/values.yaml +++ b/k8s/charts/dokuwiki/values.yaml @@ -12,7 +12,7 @@ dokuwiki: securityContext: {} service: - type: ClusterIP + type: LoadBalancer port: 8004 targetPort: 8080 diff --git a/k8s/charts/lldap/.helmignore b/k8s/charts/lldap/.helmignore deleted file mode 100644 index c113420..0000000 --- a/k8s/charts/lldap/.helmignore +++ /dev/null @@ -1,13 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching. -*.tgz -.env -.git -.git_cache -.idea -*.md -docs/* -tmp/* -vendor/* -.travis.yml -.testignore diff --git a/k8s/charts/lldap/Chart.yaml b/k8s/charts/lldap/Chart.yaml deleted file mode 100644 index 059a454..0000000 --- a/k8s/charts/lldap/Chart.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v2 -name: lldap -description: Light LDAP implementation for authentication -type: application -version: 1.0.0 -appVersion: stable -annotations: - artifacthub.io/images: "- name: lldap\n image: lldap/lldap:stable\n" - artifacthub.io/changes: "- kind: added\n description: Initial chart release\n" - artifacthub.io/maintainers: "- name: catalin\n email: catalin@roboces.dev\n" -keywords: - - lldap - - ldap - - authentication - - identity diff --git a/k8s/charts/lldap/README.md b/k8s/charts/lldap/README.md deleted file mode 100644 index 1e7ea66..0000000 --- a/k8s/charts/lldap/README.md +++ /dev/null @@ -1,150 +0,0 @@ -# lldap - -Lightweight LDAP authentication server with a web UI. - -## TL;DR - -```bash -helm install lldap oci://git.roboces.dev/catalin/fukuops/lldap --version 1.0.0 -n apps-roboces -``` - -## Prerequisites - -- Kubernetes 1.19+ -- Helm 3+ -- A NFS storage class (default: `truenas-nfs-csi`) - only if persistence is enabled -- An existing `Secret` with credentials (see `lldap.secret.existingSecretName`) - -## Configuration - -All values are nested under the `lldap:` key. Example: - -```yaml -lldap: - service: - type: LoadBalancer - httpPort: 17170 - ldapPort: 3890 - config: - ldapBaseDn: "dc=example,dc=com" - httpUrl: "https://lldap.example.com" - persistence: - enabled: true - storageClass: "truenas-nfs-csi" - accessMode: ReadWriteMany - size: 10Gi - secret: - existingSecretName: lldap-secrets -``` - -## Secret Requirements - -The secret must contain the following keys: - -- `LLDAP_JWT_SECRET` - JWT signing secret (generate with `openssl rand -base64 32`) -- `LLDAP_KEY_SEED` - Key seed for password hashing (generate with `openssl rand -base64 32`) -- `LLDAP_LDAP_USER_PASS` - Admin password for LDAP and web UI - -Example secret: - -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: lldap-secrets -type: Opaque -stringData: - LLDAP_JWT_SECRET: - LLDAP_KEY_SEED: - LLDAP_LDAP_USER_PASS: -``` - -## Values Reference - -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `lldap.replicaCount` | int | `1` | Number of replicas | -| `lldap.image.repository` | string | `lldap/lldap` | Image repository | -| `lldap.image.pullPolicy` | string | `IfNotPresent` | Image pull policy | -| `lldap.image.tag` | string | `stable` | Image tag | -| `lldap.service.type` | string | `LoadBalancer` | Service type | -| `lldap.service.httpPort` | int | `17170` | Web UI port | -| `lldap.service.ldapPort` | int | `3890` | LDAP port | -| `lldap.config.ldapBaseDn` | string | `dc=example,dc=com` | LDAP base DN | -| `lldap.config.httpUrl` | string | `https://lldap.example.com` | Public URL for web UI | -| `lldap.config.databaseUrl` | string | `""` | Database URL (empty = SQLite) | -| `lldap.config.verbose` | bool | `false` | Enable verbose logging | -| `lldap.ldaps.enabled` | bool | `false` | Enable LDAPS | -| `lldap.ldaps.certFile` | string | `""` | TLS certificate file path | -| `lldap.ldaps.keyFile` | string | `""` | TLS key file path | -| `lldap.smtp.enabled` | bool | `false` | Enable SMTP for password reset | -| `lldap.smtp.enablePasswordReset` | bool | `false` | Enable password reset via email | -| `lldap.smtp.server` | string | `""` | SMTP server | -| `lldap.smtp.port` | int | `465` | SMTP port | -| `lldap.smtp.encryption` | string | `"TLS"` | SMTP encryption (NONE, TLS, STARTTLS) | -| `lldap.smtp.user` | string | `""` | SMTP username | -| `lldap.smtp.fromAddress` | string | `""` | From email address | -| `lldap.smtp.fromName` | string | `""` | From name | -| `lldap.smtp.replyTo` | string | `""` | Reply-to address | -| `lldap.persistence.enabled` | bool | `false` | Enable persistence | -| `lldap.persistence.storageClass` | string | `truenas-nfs-csi` | Storage class | -| `lldap.persistence.accessMode` | string | `ReadWriteMany` | Access mode | -| `lldap.persistence.size` | string | `1Gi` | PVC size | -| `lldap.secret.existingSecretName` | string | `""` | Existing secret name | -| `lldap.debug.enabled` | bool | `false` | Enable debug mode | -| `lldap.resources` | object | `{}` | Container resources | -| `lldap.livenessProbe` | object | (see values.yaml) | Liveness probe | -| `lldap.readinessProbe` | object | (see values.yaml) | Readiness probe | -| `lldap.nodeSelector` | object | `{}` | Node selector | -| `lldap.tolerations` | array | `[]` | Tolerations | -| `lldap.affinity` | object | `{}` | Affinity rules | - -## Database - -By default, lldap uses SQLite stored in `/data`. To use an external database: - -```yaml -lldap: - config: - databaseUrl: "mysql://user:password@mysql-server/my-database" -``` - -Supported databases: SQLite (default), MySQL/MariaDB, PostgreSQL. - -## Persistence - -When persistence is enabled, a PVC named `lldap-data` is created. The PVC uses the `truenas-nfs-csi` storage class by default with `ReadWriteMany` access mode. - -## Troubleshooting - -### Debug Mode - -Enable debug mode to troubleshoot issues: - -```yaml -lldap: - debug: - enabled: true -``` - -### Pod not starting - -```bash -kubectl get pvc -n apps-roboces -l app.kubernetes.io/name=lldap -``` - -### Check logs - -```bash -kubectl logs lldap-0 -n apps-roboces -``` - -### Verify configmap - -```bash -kubectl get configmap lldap-config -n apps-roboces -o yaml -``` - -### Default Credentials - -If no secret is provided, the default admin password is `password`. **Change this immediately in production.** diff --git a/k8s/charts/lldap/templates/_helpers.tpl b/k8s/charts/lldap/templates/_helpers.tpl deleted file mode 100644 index 7d26272..0000000 --- a/k8s/charts/lldap/templates/_helpers.tpl +++ /dev/null @@ -1,37 +0,0 @@ -{{/* Expand the name of the chart. */}} -{{- define "name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* Create a default fully qualified app name. */}} -{{- define "fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* Common labels */}} -{{- define "labels" -}} -helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{ include "selectorLabels" . }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* Selector labels */}} -{{- define "selectorLabels" -}} -app.kubernetes.io/name: {{ include "name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* Chart name as the PVC name */}} -{{- define "pvcName" -}} -{{- .Values.lldap.persistence.name | default (printf "%s-data" (include "name" .)) }} -{{- end }} diff --git a/k8s/charts/lldap/templates/configmap.yaml b/k8s/charts/lldap/templates/configmap.yaml deleted file mode 100644 index f753e1d..0000000 --- a/k8s/charts/lldap/templates/configmap.yaml +++ /dev/null @@ -1,33 +0,0 @@ -{{- if .Values.lldap.config }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "fullname" . }}-config - labels: - {{- include "labels" . | nindent 4 }} -data: - LLDAP_LDAP_BASE_DN: {{ .Values.lldap.config.ldapBaseDn | quote }} - LLDAP_HTTP_URL: {{ .Values.lldap.config.httpUrl | quote }} - LLDAP_LDAP_OPTIONS__PORT: {{ .Values.lldap.config.ldapPort | quote }} - LLDAP_HTTP_OPTIONS__PORT: {{ .Values.lldap.config.httpPort | quote }} - LLDAP_VERBOSE: {{ .Values.lldap.config.verbose | quote }} - {{- if .Values.lldap.config.databaseUrl }} - LLDAP_DATABASE_URL: {{ .Values.lldap.config.databaseUrl | quote }} - {{- end }} - {{- if .Values.lldap.ldaps.enabled }} - LLDAP_LDAPS_OPTIONS__ENABLED: "true" - LLDAP_LDAPS_OPTIONS__CERT_FILE: {{ .Values.lldap.ldaps.certFile | quote }} - LLDAP_LDAPS_OPTIONS__KEY_FILE: {{ .Values.lldap.ldaps.keyFile | quote }} - {{- end }} - {{- if .Values.lldap.smtp.enabled }} - LLDAP_SMTP_OPTIONS__ENABLE_PASSWORD_RESET: {{ .Values.lldap.smtp.enablePasswordReset | quote }} - LLDAP_SMTP_OPTIONS__SERVER: {{ .Values.lldap.smtp.server | quote }} - LLDAP_SMTP_OPTIONS__PORT: {{ .Values.lldap.smtp.port | quote }} - LLDAP_SMTP_OPTIONS__SMTP_ENCRYPTION: {{ .Values.lldap.smtp.encryption | quote }} - LLDAP_SMTP_OPTIONS__USER: {{ .Values.lldap.smtp.user | quote }} - LLDAP_SMTP_OPTIONS__FROM: {{ printf "%s <%s>" .Values.lldap.smtp.fromName .Values.lldap.smtp.fromAddress | quote }} - {{- if .Values.lldap.smtp.replyTo }} - LLDAP_SMTP_OPTIONS__TO: {{ .Values.lldap.smtp.replyTo | quote }} - {{- end }} - {{- end }} -{{- end }} diff --git a/k8s/charts/lldap/templates/deployment.yaml b/k8s/charts/lldap/templates/deployment.yaml deleted file mode 100644 index 391f5c5..0000000 --- a/k8s/charts/lldap/templates/deployment.yaml +++ /dev/null @@ -1,111 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "fullname" . }} - labels: - {{- include "labels" . | nindent 4 }} -spec: - replicas: {{ .Values.lldap.replicaCount }} - selector: - matchLabels: - {{- include "selectorLabels" . | nindent 6 }} - template: - metadata: - labels: - {{- include "selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.lldap.podSecurityContext }} - securityContext: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- if .Values.lldap.debug.enabled }} - initContainers: - - name: debug - image: alpine:3.19 - command: ["sleep", "infinity"] - envFrom: - {{- if .Values.lldap.config }} - - configMapRef: - name: {{ include "fullname" . }}-config - {{- end }} - {{- if .Values.lldap.secret.existingSecretName }} - - secretRef: - name: {{ .Values.lldap.secret.existingSecretName }} - {{- end }} - {{- if .Values.lldap.persistence.enabled }} - volumeMounts: - - name: {{ include "pvcName" . }} - mountPath: /data - {{- end }} - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: false - capabilities: - drop: - - all - {{- end }} - containers: - - name: {{ .Chart.Name }} - image: "{{ .Values.lldap.image.repository }}:{{ .Values.lldap.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.lldap.image.pullPolicy }} - {{- with .Values.lldap.securityContext }} - securityContext: - {{- toYaml . | nindent 12 }} - {{- end }} - ports: - - name: http - containerPort: {{ .Values.lldap.service.httpPort }} - protocol: TCP - - name: ldap - containerPort: {{ .Values.lldap.service.ldapPort }} - protocol: TCP - envFrom: - {{- if .Values.lldap.config }} - - configMapRef: - name: {{ include "fullname" . }}-config - {{- end }} - {{- if .Values.lldap.secret.existingSecretName }} - - secretRef: - name: {{ .Values.lldap.secret.existingSecretName }} - {{- end }} - {{- if .Values.lldap.persistence.enabled }} - volumeMounts: - - name: {{ include "pvcName" . }} - mountPath: /data - {{- end }} - {{- with .Values.lldap.resources }} - resources: - {{- toYaml . | nindent 12 }} - {{- end }} - {{- if not .Values.lldap.debug.enabled }} - {{- with .Values.lldap.livenessProbe }} - livenessProbe: - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.lldap.readinessProbe }} - readinessProbe: - {{- toYaml . | nindent 12 }} - {{- end }} - {{- end }} - {{- if .Values.lldap.persistence.enabled }} - volumes: - - name: {{ include "pvcName" . }} - persistentVolumeClaim: - claimName: {{ include "pvcName" . }} - {{- else }} - volumes: - - name: {{ include "pvcName" . }} - emptyDir: {} - {{- end }} - {{- with .Values.lldap.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.lldap.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.lldap.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} diff --git a/k8s/charts/lldap/templates/ingress.yaml b/k8s/charts/lldap/templates/ingress.yaml deleted file mode 100644 index 02c9d37..0000000 --- a/k8s/charts/lldap/templates/ingress.yaml +++ /dev/null @@ -1,41 +0,0 @@ -{{- if .Values.lldap.ingress.enabled }} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ include "fullname" . }} - labels: - {{- include "labels" . | nindent 4 }} - {{- with .Values.lldap.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if .Values.lldap.ingress.className }} - ingressClassName: {{ .Values.lldap.ingress.className }} - {{- end }} - {{- if .Values.lldap.ingress.tls }} - tls: - {{- range .Values.lldap.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} - rules: - {{- range .Values.lldap.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ .path }} - pathType: {{ .pathType | default "Prefix" }} - backend: - service: - name: {{ include "fullname" $ }} - port: - number: {{ $.Values.lldap.service.httpPort }} - {{- end }} - {{- end }} -{{- end }} diff --git a/k8s/charts/lldap/templates/pdb.yaml b/k8s/charts/lldap/templates/pdb.yaml deleted file mode 100644 index 00c3ddf..0000000 --- a/k8s/charts/lldap/templates/pdb.yaml +++ /dev/null @@ -1,18 +0,0 @@ -{{- if .Values.lldap.podDisruptionBudget.enabled }} -apiVersion: policy/v1 -kind: PodDisruptionBudget -metadata: - name: {{ include "fullname" . }} - labels: - {{- include "labels" . | nindent 4 }} -spec: - {{- if .Values.lldap.podDisruptionBudget.minAvailable }} - minAvailable: {{ .Values.lldap.podDisruptionBudget.minAvailable }} - {{- end }} - {{- if .Values.lldap.podDisruptionBudget.maxUnavailable }} - maxUnavailable: {{ .Values.lldap.podDisruptionBudget.maxUnavailable }} - {{- end }} - selector: - matchLabels: - {{- include "selectorLabels" . | nindent 6 }} -{{- end }} diff --git a/k8s/charts/lldap/templates/pvc.yaml b/k8s/charts/lldap/templates/pvc.yaml deleted file mode 100644 index 68594fd..0000000 --- a/k8s/charts/lldap/templates/pvc.yaml +++ /dev/null @@ -1,17 +0,0 @@ -{{- if .Values.lldap.persistence.enabled }} -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: {{ include "pvcName" . }} - labels: - {{- include "labels" . | nindent 4 }} -spec: - accessModes: - - {{ .Values.lldap.persistence.accessMode | default "ReadWriteMany" }} - {{- if .Values.lldap.persistence.storageClass }} - storageClassName: {{ .Values.lldap.persistence.storageClass }} - {{- end }} - resources: - requests: - storage: {{ .Values.lldap.persistence.size | default "1Gi" }} -{{- end }} diff --git a/k8s/charts/lldap/templates/service.yaml b/k8s/charts/lldap/templates/service.yaml deleted file mode 100644 index 06df975..0000000 --- a/k8s/charts/lldap/templates/service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ include "fullname" . }} - labels: - {{- include "labels" . | nindent 4 }} -spec: - type: {{ .Values.lldap.service.type }} - ports: - - port: {{ .Values.lldap.service.httpPort }} - targetPort: http - protocol: TCP - name: http - - port: {{ .Values.lldap.service.ldapPort }} - targetPort: ldap - protocol: TCP - name: ldap - selector: - {{- include "selectorLabels" . | nindent 4 }} diff --git a/k8s/charts/lldap/values.yaml b/k8s/charts/lldap/values.yaml deleted file mode 100644 index 0a3230f..0000000 --- a/k8s/charts/lldap/values.yaml +++ /dev/null @@ -1,90 +0,0 @@ -lldap: - replicaCount: 1 - - image: - repository: lldap/lldap - pullPolicy: IfNotPresent - tag: "stable" - - service: - type: ClusterIP - httpPort: 17170 - ldapPort: 3890 - - ingress: - enabled: false - className: "traefik" - annotations: {} - hosts: - - host: lldap.example.com - paths: - - path: / - pathType: Prefix - tls: [] - - config: - ldapBaseDn: "dc=example,dc=com" - httpUrl: "https://lldap.example.com" - databaseUrl: "" - verbose: false - ldapPort: 3890 - httpPort: 17170 - - ldaps: - enabled: false - certFile: "" - keyFile: "" - - smtp: - enabled: false - enablePasswordReset: false - server: "" - port: 465 - encryption: "TLS" - user: "" - fromAddress: "" - fromName: "" - replyTo: "" - - persistence: - enabled: false - storageClass: "truenas-nfs-csi" - accessMode: ReadWriteMany - size: 1Gi - - resources: {} - - livenessProbe: - tcpSocket: - port: http - initialDelaySeconds: 30 - timeoutSeconds: 15 - successThreshold: 1 - failureThreshold: 3 - periodSeconds: 10 - - readinessProbe: - tcpSocket: - port: http - initialDelaySeconds: 15 - timeoutSeconds: 2 - successThreshold: 1 - failureThreshold: 3 - periodSeconds: 10 - - nodeSelector: {} - tolerations: [] - affinity: {} - - secret: - existingSecretName: "" - - podSecurityContext: {} - securityContext: {} - - podDisruptionBudget: - enabled: false - minAvailable: 1 - - debug: - enabled: false diff --git a/k8s/charts/oxicloud/values.yaml b/k8s/charts/oxicloud/values.yaml index fa16dca..1348f56 100644 --- a/k8s/charts/oxicloud/values.yaml +++ b/k8s/charts/oxicloud/values.yaml @@ -8,7 +8,7 @@ oxicloud: tag: "" service: - type: ClusterIP + type: LoadBalancer port: 8086 ingress: diff --git a/k8s/charts/vaultwarden/Chart.yaml b/k8s/charts/vaultwarden/Chart.yaml index 9cedbc5..3d3255d 100644 --- a/k8s/charts/vaultwarden/Chart.yaml +++ b/k8s/charts/vaultwarden/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: vaultwarden description: A Helm chart for Vaultwarden password manager type: application -version: 1.0.4 -appVersion: 1.37.2 +version: 1.0.2 +appVersion: 1.37.1 annotations: - artifacthub.io/images: "- name: vaultwarden\n image: vaultwarden/server:1.37.2-alpine\n" + artifacthub.io/images: "- name: vaultwarden\n image: vaultwarden/server:1.37.1-alpine\n" diff --git a/k8s/charts/vaultwarden/values.yaml b/k8s/charts/vaultwarden/values.yaml index f93218f..ffee53e 100644 --- a/k8s/charts/vaultwarden/values.yaml +++ b/k8s/charts/vaultwarden/values.yaml @@ -13,7 +13,7 @@ vaultwarden: service: type: ClusterIP - port: 8008 + port: 80 targetPort: 80 ingress: diff --git a/k8s/helm/argo-cd/values.yaml b/k8s/helm/argo-cd/values.yaml index 8e618b6..418be05 100644 --- a/k8s/helm/argo-cd/values.yaml +++ b/k8s/helm/argo-cd/values.yaml @@ -8,14 +8,14 @@ configs: server: service: - type: ClusterIP + type: LoadBalancer servicePortHttp: 8002 servicePortHttps: 8003 redis: image: repository: docker.io/library/redis - tag: 8.10.1-alpine + tag: 8.10.0-alpine repoServer: livenessProbe: diff --git a/k8s/services/argo/project-fuku.yaml b/k8s/services/argo/project-fuku.yaml index 5a1ac75..9e7ba4a 100644 --- a/k8s/services/argo/project-fuku.yaml +++ b/k8s/services/argo/project-fuku.yaml @@ -25,8 +25,4 @@ spec: - ghcr.io/antoniolago/charts - https://repo.helmforge.dev - https://harish2k01.github.io/helm-charts/ - - https://helm.runix.net - git.roboces.dev/catalin/fukuops - - https://kubereboot.github.io/charts - - https://charts.goauthentik.io/ - - https://git.roboces.dev/catalin/fukuops diff --git a/k8s/services/argo/project-management.yaml b/k8s/services/argo/project-management.yaml index 141f7a2..4ef7347 100644 --- a/k8s/services/argo/project-management.yaml +++ b/k8s/services/argo/project-management.yaml @@ -18,8 +18,6 @@ spec: server: https://kubernetes.default.svc - namespace: argo-workflows server: https://kubernetes.default.svc - - namespace: metallb-system - server: https://kubernetes.default.svc clusterResourceWhitelist: - group: '*' kind: '*' @@ -27,5 +25,3 @@ spec: - https://git.roboces.dev/catalin/fukuops.git - https://democratic-csi.github.io/charts/ - https://bitnami-labs.github.io/sealed-secrets - - https://kubernetes-sigs.github.io/descheduler - - https://metallb.github.io/metallb diff --git a/k8s/services/argo/project-roboces.yaml b/k8s/services/argo/project-roboces.yaml index e38ab70..b7a335a 100644 --- a/k8s/services/argo/project-roboces.yaml +++ b/k8s/services/argo/project-roboces.yaml @@ -16,7 +16,4 @@ spec: sourceRepos: - code.forgejo.org/forgejo-helm - git.roboces.dev/catalin/fukuops - - https://git.roboces.dev/catalin/fukuops.git - https://git.roboces.dev/catalin/huesoporro.git - - https://charts.christianhuth.de - - https://charts.authelia.com diff --git a/k8s/services/authelia/pvc.yaml b/k8s/services/authelia/pvc.yaml deleted file mode 100644 index 84f9fee..0000000 --- a/k8s/services/authelia/pvc.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: authelia-data - namespace: apps-roboces -spec: - accessModes: - - ReadWriteMany - storageClassName: truenas-nfs-csi - resources: - requests: - storage: 1Gi diff --git a/k8s/services/authelia/sealedsecrets.yaml b/k8s/services/authelia/sealedsecrets.yaml deleted file mode 100644 index ddd5349..0000000 --- a/k8s/services/authelia/sealedsecrets.yaml +++ /dev/null @@ -1,21 +0,0 @@ ---- -kind: SealedSecret -apiVersion: bitnami.com/v1alpha1 -metadata: - name: secrets-authelia - namespace: apps-roboces -spec: - template: - metadata: - name: secrets-authelia - namespace: apps-roboces - type: Opaque - encryptedData: - authentication.ldap.password.txt: AgBwsqdgEOQ8r/yUOD82Cwr44jxqV9RPDTzMYju8IfWx7b4iDKtZVidLK4RxBCp1+MPyWmLs7H4jec3Xsr10da/V+LaS1Elib+Sp2CkFZPGj1/XAcu5w3k/BnMcsRWJEJtzU251NVon6IVEzszcGOaVsPqf6aIvnKgOJEmp1boMlnemkvtaeXVS0TuSLXCQEMDfGMh9X8D9je315Sdqpczo8YLz6jyj6RxlgXeygRXkXANd2KFOyfLNUAnzm4Tl6PMjULnXvsxLhlc35+am6IsZGVLcdRMPY12UpHeZyVIgJBZSdo/gLTJS/hLhL2sPJv8HksL6YfvDnIWqu3PxVwI3BGtW2znBQaEIFXtOIXL9EDG0NMAZSBYCRpjrPNHP7xj7ruqjg4eU1iUj6yJpIv9322L/qhFWViK9wXc3+0L2n2q4qVBgrZxcacgqtHrCYj5Uo9iluVPWwagcKFYRXBupqaUUTso7LtSsv4WbOrJzYzE9mZk+feH1ZNvBp13JfWNdRiso5rpl0tZFJ7oW4G/CJuWg2MmL3UNViYB/ntwFWZWT8dxCEq2LI3UxeawHntzvk82kFyZduLUwhLvY4WfQB4yb//gBOpc0OyHa+djwECIyy3Jh+70LG+1YeVc8yQm52eu9+mzRSwDlqspKZYfwbMPCCiQj6AiSfg9Ho5+ITq1PgEt3oo9b0JkYFmEAgT88Y6z0DRQrM9YOXCfazMg== - identity_providers.oidc.hmac.key: AgCMb1NgRFW7jB8xAXDzTDK336k1w63FNJGJPj9fUrkM/5QT6WuAAPYPeD/3PWG/+0XjUycU4Q2PK/ei8rK6BJ1jwdSRQqBe/ybdxl3WCWGmjB2/jKLIAFQonJdgIzZiY4rUHUepQqhva/wA0eM1nCV/KYuOCIIzdNAGj3E+zyP9yquZcDxknadLuvY6XbTTCdsdwYgFP9qYy8+Vg/K8c2GGubuU34uLkfCG3afow9/gvtSDU5lsZwGtZPcYibOVULQ/CEiGiXBXs7g5W1HjqcJdPaJZDfkG2hPWh4aWMxMkjORt/9NrNmBi70e4LW5cq32wX0Mz+UblDf66Qz3wnGmZ93owAw1N0MtlI/V0W2XvSnxcXvmloHGfbll2rhFiUFPFmWq71Uv52Qc8rAMOeuJgRnrlG3QHxLFkTonMYcasQrfm8MjHpaNkemsdA6XO9VvrRVxLyN31lV+2bbeqh2wHZgCRIbU4gUyApi69Rd1TyEYVW9QSxWbPkX5B5eaCqDCZLX9aQocfElrKk/zzvY2J53s07AkvsowgwbWhwpzwAYB2ajBndAcNhzS311ve1kzZlp498ePGCdpcyuTJfVWTKoXdmJu3xSg0P7Czts/zstQYGSpp+0I4gwzYTtSuf1M841Obc78l2PaUqN5P3YIHdq4ZiMTWPHhqgerobyf6XxtXcO5iEf7zSGYexRwaTroMp/Sqx9P0eTX1rufwkmvMlPH6mClr3RMVGno7uFuC1SkRM3Ox0a4wJ0Y2B5IX/7C+Pd68Z1VQX+wJeson0X2K - identity_providers.oidc.jwks.key.pem: AgBNZRQSUb3ibWMO/1ZWtXnNoj4e6/kp1PqQW7PA4Kw9VDnTKa4s3iG6BNl1cheUAZKfjNTGBfOlj4CNrUaa4T750wh8dHp2l5BXpAj0Vf1lV8ttPp79Q+AJkLVMhybtlcnczmdDGX8K99g1uC1Tv9aKTrSXQTQV4GJAjnwmj9pUvhxNI2PJiFWfXtoaXG3co5FJInAwrM5jBGplLb4u+TRWbmFb6esV9RFWZLRZcwFXKPr0ewg4rlH0Yqt9nxGOarUqGsNKTRusHw4dhcY7Kp4jNKR6Oa2s7gvDl1hsPkjM1JiZbWKMdboJxDUCA1AJtY/TXsTsklYzYJq6tnR+m8vzhema5l3pR4Vwt1Pw9NV7xear9AOY5nc2g+zB4StbSt1ezfjXCyE/z6geZ9QXTwwGywxnHW7VXA79lbwEWGoaiFwSVdd/tN5NJcbTINr0yYlLLUaRHorG1Q3DbnIr372dvo+bxPyCJW70lkYyGS/HXfPjsZ0IgZJa0/UxFYUuFjF5Yiaw9D6WM6kUSBfjheo152qvUkeVswYBtM0EKxY1NaZn2aVtMUpkupeU0yd9oBV27r4wUXBDxNP+mrGrKXkSfJAOuOGPQRoUAAfhhnBWM9/B3ngZMeKZWBZ/oCM18k2YWb8Ap/4/LoCXL6u2gjTQQgrE75OqNN0MOk33XM7KmLZQNmB0BbWywgU8vry6PXgPWPhzJHPuEGokLSCTFydEq19n+tqcrxOWTMmjYGxECf6mtxDeR+6sgb67MRNUeGSl3nbyOR6PGCKb6P9WR6LoMz2+AAUpw8uDJm2RDHSpAjl8PSz6zzbrP2dUytqeYULc8gxq8urNocPwAfDJqM/vTMekf4b9VhMEWhoXUzMkcghnc57sefdBZFDT7B95Bx4dNGPyuzKdQH650UfLyepQ6nGJefQCe/t2Fwb9H/HsCGm7+s+ODNjA6TixDpbxP10zemn/+FGHUBlG6iaIPQylEOP/GBltXazZZHs5J7RuFpckWRO97jlAbbbhlEUK5OIqigZIlS3QTxDnI7tBh8XWEw0kDN2tuigZ1/+HTuHtvpZJQTug6AYeP7kX+a6GVwz9RKO4QNb7atGdtsCpK9WQ6yhz8YlA5z4y+OyIobUCyh+fGMVZiYTRoO6PaXZBzyf0BvQlTDBClhan+QGo0+oEKUW4lEMZDlas9DIKJFmv9ugd1+9/tdbkPhKI1P6dx1wqNV7/Ovx7f8HE0JJxBxvwZmzeglCidtw8nlxaebAZIAzMLARE/bVCclqcu22ymN6i2wfN3OD8UXY3ehb9oJxab5v+dTR6acEQtMdnZJ6Mex1Wegxy527Yd9ZhbWEGSAh/fkFiHJcp05sA2EIS1s6D+yOXOlGWq6JSuD2XoCPxjKwe6WVudqR4VwvbKmhQT6L8HZ4Qfo3b7PVHNMA1ae8UFE8pj1NhL85t5nb4Q5gQUZ8khDqO3FthGhpsrZLh0+FAbN5+9BPEHe51W2NQpB9hmY3ZRr8csOD4CKkVjs4OHYcQJrN029L7mlzXQ1SI2DkwJY+0ok66h+o2ZKdEPbKj1fono0Cg9eeBCgJEj7D3EvNoRpT0PpoL5y0vHojHg7tHjz9mmz5d0VvehMtvQl3JOxo/++8xc1YJUy/S1g9QEx1WPLl7zJGtWeDd8BXkzFzonPMU0mMbeG+Gl7Wfb6EdLzEE67c/mbIVGxqz6lHsXY7mh0iugGuyJ84c5+yX5r7fjTDT+OR6PQxHu95DKO6Hkqq9A2wNTl0OhqTjtBo1LzrFOlFwufd08WhguzUOu4eZXGc2Nz0iPBc3u2VlqPORqH1WwFZyAGCdnX1f9rkLoVF8eXbSkd+W3soy5Nd27Yp9upgilyZWIUo1N4NVQhNxMTvtHHH5w4hORch6sTCeBGe/SlzlBnD/8KW1ETfy/6H1YrRU6jhyXuj5tM0v4GLOZyCzcG5Nf+POdCdgp4mzg5X6anE3fR/jfbFonsUv5KGqLbIPDow8lNW3j1ACiePtOl/Vz3P9OnmqGHqdoTOfi2TUeN6OgtL+pXwXxBCJZXDsTrHamoHw/4RMWnjz7ABdFrh8aOA/rOSaO4Vf+BJaWqekx3KaJNLrhGNtKNACvQ+klk0HceRRKgKOcbcTTH7qxpFmR/qYkmyFkG2xBkpj4KfSmW64BAK5eNlWPCmLL6Vkql1xIGznnV1ahowlPuXnwxI2PSPBhKFOPeI3ngr3pb1kxjg6pjoA6R2ON8XVJQ7uu5ORXaihehIibZd3VgoxAd00BxWCdsqgYb9kYoRFXn62LZ/6MzIAgI2gOhy7n1UuUtJQUB+O7quEiHGtRPRER2zDWQ9lhREVVqBGfnjU7wbHEOOzImcvJv0ei8yV8vkuChky2dSmitrCW0XMgnlcbLuBrn2id0JQjBjB4HKhPZM0UtipaayEworkdS3jH+Z7z8+qPUFH04k2hK54JWkZAUB1X75BPsbxzx5EwThGZ5B2xVK37MAiguT1YWSeMPe01q9jO+/8F5VaLuw8Gn8/Elm6aNordKUKUUdE6xIDT+CV/lNWmPBGKXVDMB1CLRwtzBbvwSkErGUL1XKdqPQ4nr2L89EElHyrwEVMd7FaqiKLEfB7RtZreCuYIKcXyy18LUWq+3gasjWVrYsoWscYrslJxJMHvhcSfX6tCB5du2XZ+e7KyAEvvIagMulBRptd52/prxEmLqJktAvImw6T+ffaFZMd8aPrHsr3JJNfL/WJLY88ECoBakzT7F5/uL6u3c9WyhLnbTgwdO6QsT5/v9OlXx4JJK6w/qkXJ8176YiedxMSciPCMbXSbDHB6hqj3di+0dHL09p9x5/Jg6PLEy5vaGZXoJiZtSTnF9xpCwF+nroPiO9iV/JGrQ6W/DdlbWZK3+5pXjRsALkYA7dOCS6RPcboFSdDxSDFcUrk8wOGekVTKJG8/PZS6vgqmSUe97/zPuDkwUX1qS1SJCqdkkNiGbLVGqw= - identity_validation.reset_password.jwt.hmac.key: AgAQRknTOoC5tbCitLZkRtcIs3VOaubEvlhRmGtHh/OS0v6lgU2Gcl5C6jisu/+MqQba4euzMACLVgSMiEkERY2GrMijecfVf5LOkKKwQ0Xjiw4a+MlSVs5KR1Ovlz414//3GfUCttunnDRnH6xQbsQjWLCI6lv6Umb40TQHOnRmb83wmSkBa/P9WR35EQ6IcES3q7Ad4ZpNtLaavEnB8NuLZg3yaJpN+1yuZPeMgTPUtTG/e9FLdBs3S8AYFIcrUnDLViUo9DfXumMs1DlTsmABpjyIgjIeywOmCxMYZYvQCJrRGYlKZRdoCoafhAPxt67hdUYOe56cq3kqlTNTdURMbYup7cIkW2hQ8ZiyPvYk++RIhV/cPWOSjh6CLxMq26Si1c2Lg3aAVFxQcDiXbdprjHzbKzFfKO/5LVnhT7O651BqkCguPC2vVoaeuxblHjqouBD7ZmPazIQ9J/uDUmvm5UPjEN+Ln+enrLXdoJN9HMor410/PJ+9xPAHXhdrXRS6j5TlN5q1pz0gj2Qmnv3cunsyLo+ZJ97F3EkSYPVoK3sMXGn2fdKy/lc9vPMMvtH/tLdhWefxd25APahStZHv4Mb7O6Z+A4uFiP9Qao64jeLj08LWBbJPefT7Y+KthugN6cW4y/xQN4UFvpPHhGB36yb+45eIBzOJHltOqZ+wldjUHeBgMdkODAQYYbCzhDuVVm2iMGQqUi/Vk+EayaV70prcAELgRskseME2MwfrhYMAKS91tfMbL2dGTQ== - oidc.audiobookshelf.client_secret: AgB5usnmgdDGm4MjtMTZDxhGb6qCxoMqx/2f3tZ7JYDgoVEspeL2tFBxXBOUDamCxpnuSp12O1ZcUpOw+9XAj4079vG7sWHAzjX0nIIJCNIjH3Y6+rhGh7J7nnWc9UJqzeWM6T4k9Dntyhsn96Xp5RDSUqhH68/u4OeqwzRJJofHlbqLmYq6+bq4MZfv/wZIsqfA02aWFJk7ST2qqD1Sc+oz0HkP8jXk9WwCCnRCuZoZgYUTHbPZAgOLMMRTlqTF4jHjFQ5B+IQTh2z53Agq6G8QLmU49J7RtPIunG+2Gf71KYXIeKktH8QwI9nBof1PXGYMmOvCCzqoBLsIBqYg/iAQ+kjlkbJov/3SBk+uwdj/wvG99+AJPVB2VHr8rinW0WXptzUATqYrHg0ufBxX4lKvVhqdkY7AaHPoI5ZwKi7AYftuo0IxDHGYC4GXVg9mWY7T6nNcvqB6s1epBNDjQnsCk+krxfZ83OecHUA4apwrSX4/Ytc+GkuQ/eJcdr9SoMXW7yzFfOLMh4+8syH7xigLwszw0t6lxJ37DnVbuYewhvazIU2TBWyTtQYnBeqVm2Wllh6xZiP5PyJ+VEi2fwGZVW6NX0Hh0gVmYNduj8xAJS4GRLwE2cOrN99kTMIXBdqmjXdYuAQKYDMlWU8QtTqtsoa4Y3UV/s7f7DdVtW+nIwxHFStNSQE4kOqPtUpgKbJPpPTjlPmIhW5Zm1gj3eOWIRLRV6UAkiJ2t6T2BEh+uqhcf26pbSjbfnqSEPVhfAzarnFV32Dkgue0I6rka/GPlf4fSK89JuXT9Igwdm0cKwOepYynZIf0NSSB1bWcgTTuL3N4CjteWc/XzacFkXvWuNJ3/WgN/brGZb2nRmmHEA== - session.encryption.key: AgCjw3K7solE1INH+VIwEeP2U70HCaY25Iu6sq5C7j8nuW8JzbSCBPeY+qTS76l4sIyLlLekk7ke5kCPpZo+Jsz62UQJMGaSASRy0FKQIxgn5UIkObB+qziGjC8WNj6y/JmbvPNiIp+aw0Nd3OmVLRkvIjJDkyMf/iW1oGeLctwRop9Y+wi7lDPyEHH+CaNPCLYidhAJFG785qibqY8jTxtHsEts2T+hSXz75v5abYS+lTFtFV7RDWudkWG6dPjUKwYGw7l/yQm5p2qzzvhoyCrQItdNuEvGrZQsnXZlq4ik0C7t977uiU+fumYKFQvHzVuQTMjjo9G5AlhUG/4w2olMqOMZoJ0u+hHFBX1K1uYCUr1hd2343jd+y86iObZzTazlTGdP142lXk9+CT+akpQIVX0Mk5mwtemCYG+64ebSQm+SVB9Ffgxx3Ips6BOhqwIO0fLB1iz4BFVUp+87juyHZOP/5uC+KeV3+DHaQDrHRo6DxF5D5CRXBCcHXeWBLkDXSENgD4kvRee7fEQf60ajvi9VFTPjk86KXCbOZVlZPPrqWG7Dh+kaYOjWCQXQvFzYAq6/Dr5ZDRPJ/X989zHjPFFGRQ7Ol9uyjT/7I/2IBfdQB1Ep5BWty50PBVIqbYKRzQN0g0x5BAj5/2J9mOX/0BNGExVCNn53rfc8yDurfP0q1nbYk1t+fv0T9oI7RTPYIVWtUnshG8fwYK1PARNYBbKrGwjWtTdVxzV+ocunmmOHQhvOFrTnsfupGQ== - smtp_password.txt: AgB8pRbQxScSBRty04Z5oen7F52Y7PnwF9gJ6fdbUTRobE7j2fWiHMQk4l1FTztW3Muz3B/MQZBgBEzulpkDJ1TrvTe5I7JFdANr3lmlKjy0CG2NcQ7Vma7Bqu2SVnL8wxi/3IBTGxVa6bplGrkUCsIr/qWLhkVknOpAaUvOUuECT8PVxUZq/s4UMddkEf4N3IQW7j/60xwzomMp4e5eZ8YTH0/ZGcuw9JOQSOoJDzvJ5B+wZF7q7j9LQc4gBPHQiFFMVlZrhHA0fdjfPxbb7QEiGdMBxeZ0JqaaFBnr4FG7hRY5/H9RS9iZCFH9QY6X86ul9ZgiJYVyrvZFKSBsW+THlLZ7MACLvOs/zdXhpc2SthccoK8ykGC/inWTnfodQpH7N4zJq7akXxFy9/em4ssjWul4coeLNc+xhyi5VauLQQfRpgijc1kVJeQbrUrMe+aE1tlRdgRx76OKiFym1t47+zN3eqRf+NegNBGPSdAtKEBKfNPrcFQmlZVR6fRZqiBxG0wE90nfq9fuw3RDnVU/0LsuxwTz99gWMWl8ySuo+EAhf+JmB26/Pi1hnByeNeD8FsVkUiC6XnwAVC+ytnaVEfatrKmSJyzRhTSuFUoFxD5YV0aQgILks4+CpQwn300s0bqkLOP0etigZCCPq4cR+t+FFYAkuVjr+xPsoww0UOgokHsZKoXYi0n96UjbT0w+zoHhqQTBrPPWhPjDogZBKBCnth5eUtgrhSA2 - storage.encryption.key: AgBt8lTRtRqn+PiTNjgmJ97/rEqKEE70KZLuVtyvBM2ek70E555vx16BsCN3ATvwcr5dw+nAwYKpHJzS+mgTloLWmz6L2HqtxhlnaKC7elFeOza7tPKmKeKV3zXQ8YEA9eWjKdUN2TzO5s10iPCp4esUy5asO5DS/QVyPHMUfufu3iwHCDgnDEGlQ0pO1LMT2FwJ2QYX4Cub+OjUVrkBm8VbNdFqIw3OBtahRX2gx+o1d55psfi08ylEMKUwkHXYtkBheDvYL7SRv8xHKHGrN1loWHZOby1GOTzOnOpL6mjJoU+4nlvEzVwxzuPOHDq24cfey7engu0IfrZ+9Fs+icFjInY7cpowlNl0CXIWQm++ljtKVOUGUB2Bp6IwY1iwu3NVlwYVoZRCfSw5rLhccXnODYjXGMAwHF1cau4K8mkx1mD48dJnQE+y6AfrOPH+tCJ0dSDyuebNNlcE9HDmJseIGDP37Z3E/S9CCGprvLCdUYeOhXERyTevm5VJoboYq1/mhonPvY0bU4w3zVV3mXgr3Xi4SOupneUL+XfuQASRz1eGoQpAGxI8h4qWkgA66f8uPTh0hdM1JfLus9oXCtFsiYJzd+VtIZE+0JtI4ztGtm2JtgH80t2eiJTQolQihfpAXazg9fQSPH6NHsFcRplqC7emxdBZnI2F+TSjqcoX+RA78z+J2OP4ISs4ngF8USmm9/rJIRUq26XNLMjktSREBh/IwrpZWgiZqU/QzMhNriClH3n4vDHWJ03Xiw== diff --git a/k8s/services/metallb/l2advert-adguard.yaml b/k8s/services/metallb/l2advert-adguard.yaml deleted file mode 100644 index 707c837..0000000 --- a/k8s/services/metallb/l2advert-adguard.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -apiVersion: metallb.io/v1beta1 -kind: L2Advertisement -metadata: - name: adguard-dns-l2 - namespace: metallb-system -spec: - ipAddressPools: - - adguard-dns-pool diff --git a/k8s/services/metallb/l2advert-prod.yaml b/k8s/services/metallb/l2advert-prod.yaml deleted file mode 100644 index 6b422c2..0000000 --- a/k8s/services/metallb/l2advert-prod.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -apiVersion: metallb.io/v1beta1 -kind: L2Advertisement -metadata: - name: ingress-l2 - namespace: metallb-system -spec: - ipAddressPools: - - ingress-pool diff --git a/k8s/services/metallb/namespace.yaml b/k8s/services/metallb/namespace.yaml deleted file mode 100644 index 928cb91..0000000 --- a/k8s/services/metallb/namespace.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -apiVersion: v1 -kind: Namespace -metadata: - name: metallb-system diff --git a/k8s/services/metallb/pool-adguard.yaml b/k8s/services/metallb/pool-adguard.yaml deleted file mode 100644 index 12f9c77..0000000 --- a/k8s/services/metallb/pool-adguard.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -apiVersion: metallb.io/v1beta1 -kind: IPAddressPool -metadata: - name: adguard-dns-pool - namespace: metallb-system -spec: - addresses: - - 192.168.1.125/32 - autoAssign: false diff --git a/k8s/services/metallb/pool-prod.yaml b/k8s/services/metallb/pool-prod.yaml deleted file mode 100644 index b090516..0000000 --- a/k8s/services/metallb/pool-prod.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -apiVersion: metallb.io/v1beta1 -kind: IPAddressPool -metadata: - name: ingress-pool - namespace: metallb-system -spec: - addresses: - - 192.168.1.126/32 - # Only Traefik should get this IP. Other LoadBalancer services that are - # not yet migrated will stay pending instead of stealing the single VIP. - autoAssign: false diff --git a/k8s/services/system-upgrade-controller/k3s-upgrade.yaml b/k8s/services/system-upgrade-controller/k3s-upgrade.yaml new file mode 100644 index 0000000..3cf98d6 --- /dev/null +++ b/k8s/services/system-upgrade-controller/k3s-upgrade.yaml @@ -0,0 +1,370 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: system-upgrade + labels: + pod-security.kubernetes.io/enforce: privileged +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: system-upgrade + namespace: system-upgrade +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: default-controller-env + namespace: system-upgrade +data: + SYSTEM_UPGRADE_CONTROLLER_DEBUG: "false" + SYSTEM_UPGRADE_CONTROLLER_THREADS: "2" + SYSTEM_UPGRADE_CONTROLLER_LEADER_ELECT: "true" + SYSTEM_UPGRADE_JOB_ACTIVE_DEADLINE_SECONDS: "900" + SYSTEM_UPGRADE_JOB_BACKOFF_LIMIT: "99" + SYSTEM_UPGRADE_JOB_IMAGE_PULL_POLICY: Always + SYSTEM_UPGRADE_JOB_KUBECTL_IMAGE: rancher/kubectl:v1.30.3 + SYSTEM_UPGRADE_JOB_PRIVILEGED: "true" + SYSTEM_UPGRADE_JOB_TTL_SECONDS_AFTER_FINISH: "900" + SYSTEM_UPGRADE_PLAN_POLLING_INTERVAL: 15m +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: system-upgrade-controller +rules: + - apiGroups: + - batch + resources: + - jobs + verbs: + - get + - list + - watch + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - list + - watch + - create + - patch + - update + - apiGroups: + - "" + resources: + - namespaces + - nodes + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - nodes + verbs: + - update + - apiGroups: + - "" + resources: + - events + verbs: + - get + - create + - patch + - update + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - system-upgrade-controller + verbs: + - get + - update + - apiGroups: + - upgrade.cattle.io + resources: + - plans + - plans/status + verbs: + - get + - list + - watch + - create + - patch + - update + - delete +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: system-upgrade-controller-drainer +rules: + - apiGroups: + - "" + resources: + - pods/eviction + verbs: + - create + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - delete + - apiGroups: + - "" + resources: + - nodes + verbs: + - get + - patch + - apiGroups: + - apps + resources: + - statefulsets + - daemonsets + - replicasets + verbs: + - get + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: system-upgrade +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system-upgrade-controller +subjects: + - kind: ServiceAccount + name: system-upgrade + namespace: system-upgrade +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: system-upgrade-drainer +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system-upgrade-controller-drainer +subjects: + - kind: ServiceAccount + name: system-upgrade + namespace: system-upgrade +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: system-upgrade-controller + namespace: system-upgrade +rules: + - apiGroups: + - batch + resources: + - jobs + verbs: + - create + - delete + - deletecollection + - patch + - update + - get + - list + - watch + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: system-upgrade + namespace: system-upgrade +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: system-upgrade-controller +subjects: + - kind: ServiceAccount + name: system-upgrade + namespace: system-upgrade +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: system-upgrade-controller + namespace: system-upgrade +spec: + strategy: + type: Recreate + selector: + matchLabels: + upgrade.cattle.io/controller: system-upgrade-controller + template: + metadata: + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: system-upgrade-controller + upgrade.cattle.io/controller: system-upgrade-controller + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: Exists + - key: kubernetes.io/os + operator: In + values: + - linux + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - topologyKey: kubernetes.io/hostname + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - system-upgrade-controller + serviceAccountName: system-upgrade + tolerations: + - key: CriticalAddonsOnly + operator: Exists + - key: node-role.kubernetes.io/master + operator: Exists + effect: NoSchedule + - key: node-role.kubernetes.io/controlplane + operator: Exists + effect: NoSchedule + - key: node-role.kubernetes.io/control-plane + operator: Exists + effect: NoSchedule + - key: node-role.kubernetes.io/etcd + operator: Exists + effect: NoExecute + containers: + - name: system-upgrade-controller + image: rancher/system-upgrade-controller:v0.14.0 + imagePullPolicy: IfNotPresent + securityContext: + runAsNonRoot: true + runAsUser: 65534 + runAsGroup: 65534 + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - ALL + envFrom: + - configMapRef: + name: default-controller-env + env: + - name: SYSTEM_UPGRADE_CONTROLLER_NAME + valueFrom: + fieldRef: + fieldPath: metadata.labels['upgrade.cattle.io/controller'] + - name: SYSTEM_UPGRADE_CONTROLLER_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: SYSTEM_UPGRADE_CONTROLLER_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + volumeMounts: + - name: etc-ssl + mountPath: /etc/ssl + readOnly: true + - name: etc-pki + mountPath: /etc/pki + readOnly: true + - name: etc-ca-certificates + mountPath: /etc/ca-certificates + readOnly: true + - name: tmp + mountPath: /tmp + volumes: + - name: etc-ssl + hostPath: + path: /etc/ssl + type: DirectoryOrCreate + - name: etc-pki + hostPath: + path: /etc/pki + type: DirectoryOrCreate + - name: etc-ca-certificates + hostPath: + path: /etc/ca-certificates + type: DirectoryOrCreate + - name: tmp + emptyDir: {} +--- +apiVersion: upgrade.cattle.io/v1 +kind: Plan +metadata: + name: k3s-server + namespace: system-upgrade + labels: + k3s-upgrade: server +spec: + concurrency: 1 + version: v1.36.3+k3s1 + nodeSelector: + matchExpressions: + - {key: k3s-upgrade, operator: Exists} + - {key: k3s-upgrade, operator: NotIn, values: ["disabled", "false"]} + - {key: k3os.io/mode, operator: DoesNotExist} + - {key: node-role.kubernetes.io/control-plane, operator: Exists} + serviceAccountName: system-upgrade + cordon: true + upgrade: + image: rancher/k3s-upgrade +--- +apiVersion: upgrade.cattle.io/v1 +kind: Plan +metadata: + name: k3s-agent + namespace: system-upgrade + labels: + k3s-upgrade: agent +spec: + concurrency: 2 + version: v1.36.3+k3s1 + nodeSelector: + matchExpressions: + - {key: k3s-upgrade, operator: Exists} + - {key: k3s-upgrade, operator: NotIn, values: ["disabled", "false"]} + - {key: k3os.io/mode, operator: DoesNotExist} + - {key: node-role.kubernetes.io/control-plane, operator: DoesNotExist} + serviceAccountName: system-upgrade + prepare: + image: rancher/k3s-upgrade + args: ["prepare", "k3s-server"] + drain: + force: true + skipWaitForDeleteTimeout: 60 + upgrade: + image: rancher/k3s-upgrade diff --git a/scripts/add-secret.py b/scripts/add-secret.py deleted file mode 100755 index 83c27ca..0000000 --- a/scripts/add-secret.py +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env -S uv run --script -# /// script -# dependencies = ["typer", "ruamel.yaml"] -# /// - -import json -import shutil -import subprocess -import sys -from difflib import unified_diff -from pathlib import Path -from typing import Annotated, Any, Optional - -import typer -from ruamel.yaml import YAML - -app = typer.Typer(help="Add or update a secret field in an existing SealedSecret") - - -def resolve_file(file: Path) -> Path: - if file.is_dir(): - candidate = file / "sealedsecrets.yaml" - if not candidate.is_file(): - raise typer.BadParameter(f"{file} is a directory and contains no sealedsecrets.yaml") - return candidate - if not file.is_file(): - raise typer.BadParameter(f"{file} does not exist") - return file - - -def looks_like_json(text: str) -> bool: - for char in text: - if char in (" ", "\t", "\n", "\r"): - continue - return char == "{" - return False - - -def extract_header_comments(text: str) -> tuple[str, str]: - """Split leading comments/blank lines from the rest of the file.""" - lines = text.splitlines(keepends=True) - idx = 0 - while idx < len(lines): - stripped = lines[idx].strip() - if stripped == "" or stripped.startswith("#"): - idx += 1 - else: - break - return "".join(lines[:idx]), "".join(lines[idx:]) - - -def load_documents(text: str) -> list[Any]: - if looks_like_json(text): - # JSON is valid YAML, but parse it explicitly so we can normalize it to YAML output. - return [json.loads(text)] - yaml = YAML(typ="rt") - yaml.preserve_quotes = True - return list(yaml.load_all(text)) - - -def _represent_none(representer, data): - return representer.represent_scalar("tag:yaml.org,2002:null", "null") - - -def dump_documents(docs: list[Any]) -> str: - yaml = YAML(typ="rt") - yaml.default_flow_style = False - yaml.preserve_quotes = True - yaml.width = 4096 - yaml.explicit_start = True - yaml.representer.add_representer(type(None), _represent_none) - from ruamel.yaml.compat import StringIO - - stream = StringIO() - yaml.dump_all(docs, stream) - return stream.getvalue() - - -def detect_scope(doc: Any) -> str: - annotations = doc.get("metadata", {}).get("annotations", {}) or {} - for key, value in annotations.items(): - if key == "sealedsecrets.bitnami.com/cluster-wide" and str(value).lower() == "true": - return "cluster-wide" - if key == "sealedsecrets.bitnami.com/namespace-wide" and str(value).lower() == "true": - return "namespace-wide" - return "strict" - - -def seal_value(value: str, doc: Any, cert: Optional[Path]) -> str: - metadata = doc.get("metadata", {}) - spec = doc.get("spec", {}) - - name = metadata.get("name") - if not name: - raise typer.BadParameter("SealedSecret has no metadata.name") - - namespace = metadata.get("namespace") or spec.get("template", {}).get("metadata", {}).get("namespace") - if not namespace: - raise typer.BadParameter("SealedSecret has no namespace in metadata or spec.template.metadata") - - cmd = ["kubeseal", "--raw", "--from-file=/dev/stdin"] - if cert: - cmd.extend(["--cert", str(cert)]) - - scope = detect_scope(doc) - if scope == "cluster-wide": - cmd.extend(["--scope", "cluster-wide"]) - elif scope == "namespace-wide": - cmd.extend(["--namespace", namespace, "--scope", "namespace-wide"]) - else: - cmd.extend(["--namespace", namespace, "--name", name]) - - result = subprocess.run( - cmd, - input=value, - text=True, - capture_output=True, - ) - if result.returncode != 0: - typer.echo(f"kubeseal failed:\n{result.stderr}", err=True) - raise typer.Exit(result.returncode) - - return result.stdout.strip() - - -def find_target_document(docs: list[Any], key: str, secret_name: Optional[str]) -> Any: - if secret_name: - for doc in docs: - if doc.get("metadata", {}).get("name") == secret_name: - return doc - raise typer.BadParameter(f"No SealedSecret named '{secret_name}' found in file") - - if len(docs) == 1: - return docs[0] - - candidates = [ - doc for doc in docs - if key in (doc.get("spec", {}).get("encryptedData", {}) or {}) - ] - if len(candidates) == 1: - return candidates[0] - if len(candidates) > 1: - names = [doc.get("metadata", {}).get("name", "") for doc in candidates] - raise typer.BadParameter( - f"Key '{key}' exists in multiple SealedSecrets ({', '.join(names)}). " - "Use --secret-name to choose one." - ) - - names = [doc.get("metadata", {}).get("name", "") for doc in docs] - raise typer.BadParameter( - f"File contains multiple SealedSecrets and key '{key}' does not exist yet. " - f"Available: {', '.join(names)}. Use --secret-name to choose one." - ) - - -@app.command() -def add( - name: Annotated[str, typer.Option("--name", help="Key name in SealedSecret encryptedData")], - secret: Annotated[str, typer.Option("--secret", help="Plaintext secret value")], - file: Annotated[Path, typer.Option("--file", help="Path to sealedsecrets.yaml or its directory")], - secret_name: Annotated[ - Optional[str], - typer.Option("--secret-name", help="Name of the SealedSecret resource to update (required for multi-secret files when the key is new)"), - ] = None, - cert: Annotated[ - Optional[Path], - typer.Option("--cert", help="Path to a kubeseal certificate for offline sealing"), - ] = None, - dry_run: Annotated[bool, typer.Option("--dry-run", help="Show diff without writing")] = False, -) -> None: - target_path = resolve_file(file) - original_text = target_path.read_text() - header, body = extract_header_comments(original_text) - - docs = load_documents(body) - if not docs: - raise typer.BadParameter(f"{target_path} contains no documents") - - target = find_target_document(docs, name, secret_name) - sealed = seal_value(secret, target, cert) - - if "spec" not in target or target["spec"] is None: - target["spec"] = {} - if "encryptedData" not in target["spec"] or target["spec"]["encryptedData"] is None: - target["spec"]["encryptedData"] = {} - - old_value = target["spec"]["encryptedData"].get(name) - action = "updated" if old_value is not None else "added" - target["spec"]["encryptedData"][name] = sealed - - new_text = header + dump_documents(docs) - - if dry_run: - diff = unified_diff( - original_text.splitlines(keepends=True), - new_text.splitlines(keepends=True), - fromfile=str(target_path), - tofile=str(target_path), - ) - sys.stdout.writelines(diff) - raise typer.Exit() - - backup_path = target_path.with_suffix(target_path.suffix + ".bak") - shutil.copy2(target_path, backup_path) - target_path.write_text(new_text) - - typer.echo(f"{action} '{name}' in SealedSecret '{target.get('metadata', {}).get('name')}' ({target_path})") - typer.echo(f"backup saved to {backup_path}") - - -if __name__ == "__main__": - app() diff --git a/scripts/k8s-ports.sh b/scripts/k8s-ports.sh index ca5b2cf..534d980 100755 --- a/scripts/k8s-ports.sh +++ b/scripts/k8s-ports.sh @@ -92,19 +92,6 @@ fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" K8S_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -extract_lb_port() { - local file="$1" - local field="$2" - local selector="$3" - local value - - value=$(yq -r '.. | objects | select(.type == "LoadBalancer") | '"$selector"' // empty' "$file" 2>/dev/null || true) - if [[ -n "$value" ]] && [[ "$value" != "null" ]] && [[ "$value" =~ ^[0-9]+$ ]]; then - PORTS+=("$value") - [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found $field $value in $file" - fi -} - collect_ports() { PORTS=() @@ -117,15 +104,29 @@ collect_ports() { continue fi - extract_lb_port "$file" ".port" ".port" - extract_lb_port "$file" "servicePortHttp" ".servicePortHttp" - extract_lb_port "$file" "servicePortHttps" ".servicePortHttps" - extract_lb_port "$file" "dns.port" ".dns.port" - extract_lb_port "$file" "httpPort" ".httpPort" - extract_lb_port "$file" "httpsPort" ".httpsPort" - extract_lb_port "$file" "edgePort" ".edgePort" - extract_lb_port "$file" "ldapPort" ".ldapPort" - extract_lb_port "$file" "servicePort" ".servicePort" + lb_port=$(yq -r '.. | objects | select(.type == "LoadBalancer") | select(.port != null) | .port // empty' "$file" 2>/dev/null || true) + if [[ -n "$lb_port" ]] && [[ "$lb_port" != "null" ]] && [[ "$lb_port" =~ ^[0-9]+$ ]]; then + PORTS+=("$lb_port") + [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found .port $lb_port in $file" + fi + + service_port_http=$(yq -r '.. | objects | select(.type == "LoadBalancer") | .servicePortHttp // empty' "$file" 2>/dev/null || true) + if [[ -n "$service_port_http" ]] && [[ "$service_port_http" != "null" ]] && [[ "$service_port_http" =~ ^[0-9]+$ ]]; then + PORTS+=("$service_port_http") + [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found servicePortHttp $service_port_http in $file" + fi + + service_port_https=$(yq -r '.. | objects | select(.type == "LoadBalancer") | .servicePortHttps // empty' "$file" 2>/dev/null || true) + if [[ -n "$service_port_https" ]] && [[ "$service_port_https" != "null" ]] && [[ "$service_port_https" =~ ^[0-9]+$ ]]; then + PORTS+=("$service_port_https") + [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found servicePortHttps $service_port_https in $file" + fi + + service_dns_port=$(yq -r '.. | objects | select(.type == "LoadBalancer") | .dns.port // empty' "$file" 2>/dev/null || true) + if [[ -n "$service_dns_port" ]] && [[ "$service_dns_port" != "null" ]] && [[ "$service_dns_port" =~ ^[0-9]+$ ]]; then + PORTS+=("$service_dns_port") + [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found dns.port $service_dns_port in $file" + fi done < <(find "$K8S_DIR/k8s" -name "*.yaml" -type f 2>/dev/null) || true if command -v kubectl &>/dev/null && kubectl cluster-info &>/dev/null 2>&1; then diff --git a/scripts/users.py b/scripts/users.py deleted file mode 100755 index eb49ca3..0000000 --- a/scripts/users.py +++ /dev/null @@ -1,293 +0,0 @@ -#!/usr/bin/env -S uv run --script -# /// script -# dependencies = ["ldap3", "typer", "httpx"] -# /// - -import base64 -import os -import re -import secrets -import subprocess -import sys - -import httpx -import typer -from ldap3 import ALL, Connection, Server, SUBTREE - -LLDAP_NAMESPACE = "apps-fuku" -LLDAP_SECRET = "lldap-secrets" -LLDAP_SERVICE = "lldap.apps-fuku.svc.cluster.local" -LLDAP_LDAP_PORT = 3890 -LLDAP_BASE_DN = "dc=fuku,dc=local" -LLDAP_ADMIN_USER = "uid=admin,ou=people,dc=fuku,dc=local" -LLDAP_URL = "https://ldap.fukurokuju.dev" - -app = typer.Typer(help="Manage Authelia users in the LLDAP backend.") - - -def fail(msg: str) -> None: - print(f"ERROR: {msg}", file=sys.stderr) - raise typer.Exit(1) - - -def run_kubectl(args: list[str]) -> subprocess.CompletedProcess[str]: - return subprocess.run( - ["kubectl", *args], - capture_output=True, - text=True, - check=True, - ) - - -def get_secret_value(key: str) -> str: - result = run_kubectl( - [ - "get", - "secret", - LLDAP_SECRET, - "-n", - LLDAP_NAMESPACE, - "-o", - f"jsonpath={{.data.{key}}}", - ] - ) - return base64.b64decode(result.stdout).decode() - - -def get_configmap_value(key: str) -> str: - result = run_kubectl( - [ - "get", - "configmap", - "lldap-config", - "-n", - LLDAP_NAMESPACE, - "-o", - f"jsonpath={{.data.{key}}}", - ] - ) - return result.stdout.strip() - - -def get_lldap_password() -> str: - return get_secret_value("LLDAP_LDAP_USER_PASS") - - -def get_lldap_hosts() -> list[str]: - result = run_kubectl( - [ - "get", - "svc", - "lldap", - "-n", - LLDAP_NAMESPACE, - "-o", - "jsonpath={.status.loadBalancer.ingress[*].ip}", - ] - ) - ips = [ip.strip() for ip in result.stdout.split() if ip.strip()] - if not ips: - fail("could not discover lldap LoadBalancer IPs") - return ips - - -def lldap_connection() -> Connection: - password = get_lldap_password() - hosts = get_lldap_hosts() - last_error = None - for host in hosts: - try: - server = Server(host, port=LLDAP_LDAP_PORT, use_ssl=False, get_info=ALL) - conn = Connection( - server, - user=LLDAP_ADMIN_USER, - password=password, - auto_bind=True, - read_only=False, - ) - return conn - except Exception as e: - last_error = e - continue - fail(f"could not connect to any lldap endpoint: {last_error}") - - -def validate_username(username: str) -> None: - if not re.match(r"^[a-zA-Z0-9_.-]+$", username): - fail(f"invalid username: {username} (allowed: a-z, 0-9, _, ., -)") - - -def trigger_password_reset(username: str) -> None: - url = f"{LLDAP_URL}/auth/reset/step1/{username}" - try: - response = httpx.post(url, timeout=30.0) - response.raise_for_status() - except httpx.HTTPError as e: - fail(f"failed to trigger password reset email: {e}") - - print(f"Password reset email triggered for '{username}'.") - - -def generate_random_password() -> str: - return secrets.token_urlsafe(32) - - -def get_group_id(conn: Connection, group_name: str) -> int | None: - conn.search( - f"ou=groups,{LLDAP_BASE_DN}", - f"(cn={group_name})", - SUBTREE, - attributes=["uid"], - ) - if not conn.entries: - return None - uid = conn.entries[0].uid.value - try: - return int(uid) - except (TypeError, ValueError): - return None - - -def user_exists(conn: Connection, username: str) -> bool: - conn.search( - f"ou=people,{LLDAP_BASE_DN}", - f"(uid={username})", - SUBTREE, - attributes=["uid"], - ) - return bool(conn.entries) - - -def email_in_use(conn: Connection, email: str) -> bool: - conn.search( - f"ou=people,{LLDAP_BASE_DN}", - f"(mail={email})", - SUBTREE, - attributes=["uid"], - ) - return bool(conn.entries) - - -@app.command("list", help="List existing users ") -def cmd_list() -> None: - with lldap_connection() as conn: - conn.search( - f"ou=people,{LLDAP_BASE_DN}", - "(objectClass=person)", - SUBTREE, - attributes=["uid", "cn", "mail", "memberOf"], - ) - if not conn.entries: - print("No users found.") - return - print(f"{'USERNAME':<20} {'DISPLAY NAME':<30} {'EMAIL':<30} GROUPS") - for entry in conn.entries: - uid = entry.uid.value if entry.uid else "" - cn = entry.cn.value if entry.cn else "" - mail = entry.mail.value if entry.mail else "" - groups = ",".join( - g.split(",")[0].replace("cn=", "") for g in entry.memberOf.values - ) if entry.memberOf else "" - print(f"{uid:<20} {cn:<30} {mail:<30} {groups}") - - -@app.command("add", help="Add a user. Omit --password to send an invite email") -def cmd_add( - username: str = typer.Option(..., "--username", "-u", help="Username"), - displayname: str = typer.Option(..., "--displayname", "-n", help="Display name"), - password: str | None = typer.Option(None, "--password", "-p", help="Password"), - email: str = typer.Option("", "--email", "-e", help="Email address"), - groups: list[str] = typer.Option( - ["users"], "--group", "-g", help="Group to add the user to (repeatable)" - ), -) -> None: - validate_username(username) - - invite_mode = password is None - if invite_mode and not email: - fail("--email is required when --password is omitted") - - effective_email = email or f"{username}@roboces.dev" - - with lldap_connection() as conn: - if user_exists(conn, username): - fail(f"user '{username}' already exists") - if email_in_use(conn, effective_email): - fail(f"email '{effective_email}' is already in use") - - if invite_mode: - password = generate_random_password() - - user_dn = f"uid={username},ou=people,{LLDAP_BASE_DN}" - attrs = { - "objectClass": ["inetOrgPerson", "posixAccount", "mailAccount", "person"], - "uid": username, - "cn": displayname, - "mail": effective_email, - } - if not conn.add(user_dn, attributes=attrs): - fail(f"failed to create user: {conn.result}") - - if not conn.extend.standard.modify_password(user=user_dn, new_password=password): - fail(f"failed to set password: {conn.result}") - - for group_name in groups: - group_id = get_group_id(conn, group_name) - if group_id is None: - print(f" WARNING: group '{group_name}' not found, skipping") - continue - if not conn.add_user_to_group(user_dn, group_id): - print(f" WARNING: could not add to group '{group_name}': {conn.result}") - else: - print(f" Added to group '{group_name}'.") - - if invite_mode: - trigger_password_reset(username) - print(f"User '{username}' created and invite email sent to '{effective_email}'.") - else: - print(f"User '{username}' created.") - - -@app.command("remove", help="Remove a user") -def cmd_remove( - username: str = typer.Option(..., "--username", "-u", help="Username"), -) -> None: - validate_username(username) - with lldap_connection() as conn: - user_dn = f"uid={username},ou=people,{LLDAP_BASE_DN}" - conn.search(user_dn, "(objectClass=*)", SUBTREE, attributes=["uid"]) - if not conn.entries: - fail(f"user '{username}' not found") - if not conn.delete(user_dn): - fail(f"failed to delete user: {conn.result}") - print(f"User '{username}' removed from LLDAP.") - - -@app.command("reset-password", help="Trigger a password reset email for an existing user") -def cmd_reset_password( - username: str = typer.Option(..., "--username", "-u", help="Username"), - confirm: bool = typer.Option( - True, - "--confirm/--no-confirm", - help="Prompt for confirmation before sending the reset email", - ), -) -> None: - validate_username(username) - with lldap_connection() as conn: - if not user_exists(conn=conn, username=username): - fail(f"user '{username}' not found") - - if confirm: - typer.confirm( - f"Send password reset email to '{username}'?", - abort=True, - ) - - trigger_password_reset(username) - print(f"Password reset email triggered for '{username}'.") - - -if __name__ == "__main__": - os.environ.setdefault("PYTHONUNBUFFERED", "1") - sys.stdout.reconfigure(line_buffering=True) - app() diff --git a/tofu/adguard/main.tf b/tofu/adguard/main.tf index 79f4872..afd06bf 100644 --- a/tofu/adguard/main.tf +++ b/tofu/adguard/main.tf @@ -114,31 +114,12 @@ resource "adguard_rewrite" "vault" { answer = "192.168.1.12" } -resource "adguard_rewrite" "authelia-roboces" { +resource "adguard_rewrite" "pocketid" { domain = "auth.roboces.dev" answer = "192.168.1.12" } -resource "adguard_rewrite" "authelia" { - domain = "auth.fuku" - answer = "192.168.1.12" -} - resource "adguard_rewrite" "dns" { domain = "dns.fuku" answer = "192.168.1.12" } -resource "adguard_rewrite" "ldap" { - domain = "ldap.fuku" - answer = "192.168.1.12" -} - -resource "adguard_rewrite" "audiobooks" { - domain = "audiobooks.roboces.dev" - answer = "192.168.1.12" -} - -resource "adguard_rewrite" "immich" { - domain = "photos.roboces.dev" - answer = "192.168.1.12" -}