├── .gitignore ├── talos ├── patches │ ├── allow-controlplane-workloads.yaml │ ├── interface-names.yaml │ ├── dhcp.yaml │ ├── etcd-metrics.yaml │ ├── vip.yaml │ ├── kubelet-certificates.yaml │ ├── nfs-config.yaml │ ├── local-path-provisioner-mount.yaml │ └── allow-controlplane-metallb.yaml ├── image-schematic.yaml └── talos.secret.sops.yaml ├── kubernetes ├── homepage │ ├── config │ │ ├── kubernetes.yaml │ │ ├── settings.yaml │ │ ├── widgets.yaml │ │ ├── bookmarks.yaml │ │ └── services.yaml │ ├── ns.yaml │ ├── service.yaml │ ├── ingress.yaml │ ├── kustomization.yaml │ ├── serviceaccount.yaml │ ├── clusterrole.yaml │ ├── deployment.yaml │ └── homepage.secret.sops.yaml ├── common │ ├── kustomization.yaml │ └── repos │ │ ├── kustomization.yaml │ │ └── app-template.yaml ├── traefik │ ├── ns.yaml │ ├── repo.yaml │ ├── kustomization.yaml │ ├── dashboard-ingress.yaml │ ├── middlewares.yaml │ ├── release.yaml │ ├── tinyauth.secret.sops.yaml │ ├── tinyauth.yaml │ └── external-ingress.yaml ├── volsync │ ├── ns.yaml │ ├── kustomization.yaml │ ├── repo.yaml │ └── release.yaml ├── cert-manager │ ├── ns.yaml │ ├── certificates │ │ ├── kustomization.yaml │ │ ├── production │ │ │ └── axis.yaml │ │ └── staging │ │ │ └── axis.yaml │ ├── kustomization.yaml │ ├── issuers │ │ ├── kustomization.yaml │ │ ├── letsencrypt-production.yaml │ │ ├── letsencrypt-staging.yaml │ │ └── cloudflare-issuer.secret.sops.yaml │ ├── repo.yaml │ └── release.yaml ├── flux-system │ ├── kustomization.yaml │ └── gotk-sync.yaml ├── actual │ ├── backup │ │ ├── kustomization.yaml │ │ ├── backup.yaml │ │ └── backblaze.secret.sops.yaml │ ├── akahu-actual │ │ ├── kustomization.yaml │ │ ├── release.yaml │ │ └── akahu-actual.secret.sops.yaml │ ├── kustomization.yaml │ ├── ns.yaml │ ├── pvc.yaml │ ├── ingress.yaml │ └── release.yaml ├── gitea │ ├── backup │ │ ├── kustomization.yaml │ │ ├── backup.yaml │ │ └── backblaze.secret.sops.yaml │ ├── ns.yaml │ ├── repo.yaml │ ├── pvc.yaml │ ├── mirror-pvc.yaml │ ├── kustomization.yaml │ ├── ingress.yaml │ ├── mirror-ingress.yaml │ ├── mirror.secret.sops.yaml │ ├── release.yaml │ ├── gitea.secret.sops.yaml │ └── mirror-release.yaml ├── kube-system │ ├── metrics-server │ │ ├── kustomization.yaml │ │ ├── repo.yaml │ │ └── release.yaml │ └── snapshot-controller │ │ ├── kustomization.yaml │ │ ├── repo.yaml │ │ └── release.yaml ├── storage │ ├── local-path-provisioner │ │ ├── kustomization.yaml │ │ ├── repo.yaml │ │ └── release.yaml │ ├── democratic-csi │ │ ├── kustomization.yaml │ │ ├── repo.yaml │ │ └── iscsi.secret.sops.yaml │ ├── kustomization.yaml │ └── ns.yaml ├── ccinvoice │ ├── backup │ │ ├── kustomization.yaml │ │ ├── backup.yaml │ │ └── backblaze.secret.sops.yaml │ ├── kustomization.yaml │ ├── ns.yaml │ ├── pvc.yaml │ ├── ingress.yaml │ ├── release.yaml │ └── ccinvoice.secret.sops.yaml ├── media │ ├── radarr │ │ ├── backup │ │ │ ├── kustomization.yaml │ │ │ ├── backup.yaml │ │ │ └── backblaze.secret.sops.yaml │ │ ├── kustomization.yaml │ │ ├── ingress.yaml │ │ ├── pvc.yaml │ │ └── release.yaml │ ├── sonarr │ │ ├── backup │ │ │ ├── kustomization.yaml │ │ │ ├── backup.yaml │ │ │ └── backblaze.secret.sops.yaml │ │ ├── kustomization.yaml │ │ ├── ingress.yaml │ │ ├── pvc.yaml │ │ └── release.yaml │ ├── prowlarr │ │ ├── backup │ │ │ ├── kustomization.yaml │ │ │ ├── backup.yaml │ │ │ └── backblaze.secret.sops.yaml │ │ ├── kustomization.yaml │ │ ├── pvc.yaml │ │ ├── ingress.yaml │ │ └── release.yaml │ ├── qbittorrent │ │ ├── kustomization.yaml │ │ ├── pvc.yaml │ │ ├── ingress.yaml │ │ └── release.yaml │ ├── kustomization.yaml │ ├── ns.yaml │ └── recyclarr │ │ ├── kustomization.yaml │ │ ├── recyclarr.secret.sops.yaml │ │ ├── config │ │ └── recyclarr.yml │ │ └── release.yaml ├── home-assistant │ ├── backup │ │ ├── kustomization.yaml │ │ ├── backup.yaml │ │ └── backblaze.secret.sops.yaml │ ├── kustomization.yaml │ ├── ns.yaml │ ├── pvc.yaml │ ├── ingress.yaml │ └── release.yaml ├── monitoring │ ├── loki │ │ ├── kustomization.yaml │ │ ├── repo.yaml │ │ ├── promtail.yaml │ │ └── release.yaml │ ├── kube-prometheus-stack │ │ ├── kustomization.yaml │ │ ├── repo.yaml │ │ ├── alertmanagerconfig.yaml │ │ ├── ingress.yaml │ │ ├── alertmanager.secret.sops.yaml │ │ └── release.yaml │ ├── kustomization.yaml │ ├── ns.yaml │ └── grafana │ │ ├── kustomization.yaml │ │ ├── repo.yaml │ │ ├── ingress.yaml │ │ ├── admin.secret.sops.yaml │ │ └── release.yaml └── metallb-system │ ├── kustomization.yaml │ ├── ns.yaml │ ├── repo.yaml │ ├── addresspool.yaml │ └── release.yaml ├── .github ├── CODEOWNERS └── workflows │ └── ci.yaml ├── .editorconfig ├── taskfile.yaml ├── .taskfiles ├── sops.yaml ├── volsync.yaml └── talos.yaml ├── .gitattributes ├── .sops.yaml ├── LICENSE ├── scripts ├── sops.sh ├── pick-backup.sh └── restore.sh ├── README.md └── .renovaterc.json5 /.gitignore: -------------------------------------------------------------------------------- 1 | *.secret.yaml 2 | talos/rendered/* 3 | -------------------------------------------------------------------------------- /talos/patches/allow-controlplane-workloads.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | cluster: 3 | allowSchedulingOnControlPlanes: true 4 | -------------------------------------------------------------------------------- /talos/patches/interface-names.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | machine: 3 | install: 4 | extraKernelArgs: 5 | - net.ifnames=0 6 | -------------------------------------------------------------------------------- /talos/patches/dhcp.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | machine: 3 | network: 4 | interfaces: 5 | - interface: eth0 6 | dhcp: true 7 | -------------------------------------------------------------------------------- /talos/patches/etcd-metrics.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | cluster: 3 | etcd: 4 | extraArgs: 5 | listen-metrics-urls: http://0.0.0.0:2381 6 | -------------------------------------------------------------------------------- /kubernetes/homepage/config/kubernetes.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://gethomepage.dev/configs/kubernetes/ 3 | mode: cluster 4 | ingress: traefik 5 | -------------------------------------------------------------------------------- /talos/patches/vip.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | machine: 3 | network: 4 | interfaces: 5 | - interface: eth0 6 | vip: 7 | ip: 10.0.10.10 8 | -------------------------------------------------------------------------------- /talos/image-schematic.yaml: -------------------------------------------------------------------------------- 1 | customization: 2 | systemExtensions: 3 | officialExtensions: 4 | - siderolabs/qemu-guest-agent 5 | - siderolabs/iscsi-tools 6 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 2 | * @scottmckendry 3 | -------------------------------------------------------------------------------- /kubernetes/homepage/config/settings.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://gethomepage.dev/configs/settings/ 3 | title: AXIS 4 | theme: dark 5 | color: stone 6 | hideVersion: true 7 | disableUpdateCheck: true 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indest_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*.sh] 10 | indent_size = 4 11 | -------------------------------------------------------------------------------- /kubernetes/common/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - repos 7 | -------------------------------------------------------------------------------- /taskfile.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://taskfile.dev/schema.json 3 | version: 3 4 | 5 | includes: 6 | talos: .taskfiles/talos.yaml 7 | sops: .taskfiles/sops.yaml 8 | volsync: .taskfiles/volsync.yaml 9 | -------------------------------------------------------------------------------- /kubernetes/homepage/ns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/namespace.json 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: homepage 7 | -------------------------------------------------------------------------------- /kubernetes/traefik/ns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/namespace.json 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: traefik 7 | -------------------------------------------------------------------------------- /kubernetes/volsync/ns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/namespace.json 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: volsync 7 | -------------------------------------------------------------------------------- /kubernetes/cert-manager/ns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/namespace.json 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: cert-manager 7 | -------------------------------------------------------------------------------- /kubernetes/common/repos/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - app-template.yaml 7 | -------------------------------------------------------------------------------- /kubernetes/flux-system/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - gotk-components.yaml 7 | - gotk-sync.yaml 8 | -------------------------------------------------------------------------------- /kubernetes/volsync/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - ns.yaml 7 | - release.yaml 8 | - repo.yaml 9 | -------------------------------------------------------------------------------- /kubernetes/actual/backup/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - backup.yaml 7 | - backblaze.secret.sops.yaml 8 | -------------------------------------------------------------------------------- /kubernetes/gitea/backup/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - backup.yaml 7 | - backblaze.secret.sops.yaml 8 | -------------------------------------------------------------------------------- /kubernetes/kube-system/metrics-server/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - release.yaml 7 | - repo.yaml 8 | -------------------------------------------------------------------------------- /kubernetes/storage/local-path-provisioner/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - release.yaml 7 | - repo.yaml 8 | -------------------------------------------------------------------------------- /kubernetes/actual/akahu-actual/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - release.yaml 6 | - akahu-actual.secret.sops.yaml 7 | -------------------------------------------------------------------------------- /kubernetes/ccinvoice/backup/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - backup.yaml 7 | - backblaze.secret.sops.yaml 8 | -------------------------------------------------------------------------------- /kubernetes/kube-system/snapshot-controller/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - repo.yaml 7 | - release.yaml 8 | -------------------------------------------------------------------------------- /kubernetes/media/radarr/backup/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - backup.yaml 7 | - backblaze.secret.sops.yaml 8 | -------------------------------------------------------------------------------- /kubernetes/media/sonarr/backup/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - backup.yaml 7 | - backblaze.secret.sops.yaml 8 | -------------------------------------------------------------------------------- /kubernetes/storage/democratic-csi/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - iscsi.secret.sops.yaml 7 | - repo.yaml 8 | -------------------------------------------------------------------------------- /kubernetes/home-assistant/backup/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - backup.yaml 7 | - backblaze.secret.sops.yaml 8 | -------------------------------------------------------------------------------- /kubernetes/media/prowlarr/backup/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - backup.yaml 7 | - backblaze.secret.sops.yaml 8 | -------------------------------------------------------------------------------- /kubernetes/media/qbittorrent/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - release.yaml 7 | - ingress.yaml 8 | - pvc.yaml 9 | -------------------------------------------------------------------------------- /kubernetes/monitoring/loki/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - promtail.yaml 7 | - release.yaml 8 | - repo.yaml 9 | -------------------------------------------------------------------------------- /kubernetes/storage/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - ns.yaml 7 | - local-path-provisioner 8 | - democratic-csi 9 | -------------------------------------------------------------------------------- /kubernetes/cert-manager/certificates/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - staging/axis.yaml 7 | - production/axis.yaml 8 | -------------------------------------------------------------------------------- /kubernetes/gitea/ns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/namespace.json 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: gitea 7 | annotations: 8 | volsync.backube/priveleged-movers: "true" 9 | -------------------------------------------------------------------------------- /kubernetes/monitoring/kube-prometheus-stack/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - release.yaml 7 | - repo.yaml 8 | - ingress.yaml 9 | -------------------------------------------------------------------------------- /kubernetes/media/prowlarr/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - release.yaml 7 | - ingress.yaml 8 | - pvc.yaml 9 | - backup 10 | -------------------------------------------------------------------------------- /kubernetes/media/radarr/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - release.yaml 7 | - ingress.yaml 8 | - pvc.yaml 9 | - backup 10 | -------------------------------------------------------------------------------- /kubernetes/media/sonarr/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - release.yaml 7 | - ingress.yaml 8 | - pvc.yaml 9 | - backup 10 | -------------------------------------------------------------------------------- /kubernetes/monitoring/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - ns.yaml 7 | - kube-prometheus-stack 8 | - grafana 9 | - loki 10 | -------------------------------------------------------------------------------- /kubernetes/storage/ns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/namespace.json 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: storage 7 | labels: 8 | pod-security.kubernetes.io/enforce: privileged 9 | -------------------------------------------------------------------------------- /talos/patches/kubelet-certificates.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | machine: 3 | kubelet: 4 | extraArgs: 5 | rotate-server-certificates: true 6 | 7 | cluster: 8 | extraManifests: 9 | - https://raw.githubusercontent.com/alex1989hu/kubelet-serving-cert-approver/main/deploy/standalone-install.yaml 10 | -------------------------------------------------------------------------------- /kubernetes/metallb-system/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - ns.yaml 7 | - release.yaml 8 | - repo.yaml 9 | - addresspool.yaml 10 | -------------------------------------------------------------------------------- /kubernetes/monitoring/ns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/namespace.json 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: monitoring 7 | labels: 8 | pod-security.kubernetes.io/enforce: privileged 9 | -------------------------------------------------------------------------------- /kubernetes/home-assistant/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - ns.yaml 6 | - pvc.yaml 7 | - release.yaml 8 | - ingress.yaml 9 | - backup 10 | -------------------------------------------------------------------------------- /kubernetes/home-assistant/ns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/namespace.json 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: home-assistant 7 | annotations: 8 | volsync.backube/priveleged-movers: "true" 9 | -------------------------------------------------------------------------------- /kubernetes/metallb-system/ns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/namespace.json 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: metallb-system 7 | labels: 8 | pod-security.kubernetes.io/enforce: privileged 9 | -------------------------------------------------------------------------------- /talos/patches/nfs-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | machine: 3 | files: 4 | - op: overwrite 5 | path: /etc/nfsmount.conf 6 | permissions: 0o644 7 | content: | 8 | [ NFSMount_Global_Options ] 9 | nfsvers=4.2 10 | hard=True 11 | nconnect=16 12 | noatime=True 13 | -------------------------------------------------------------------------------- /kubernetes/cert-manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - ns.yaml 7 | - release.yaml 8 | - repo.yaml 9 | - issuers 10 | - certificates 11 | -------------------------------------------------------------------------------- /kubernetes/media/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - ns.yaml 7 | - prowlarr 8 | - qbittorrent 9 | - radarr 10 | - recyclarr 11 | - sonarr 12 | -------------------------------------------------------------------------------- /kubernetes/monitoring/grafana/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - release.yaml 7 | - repo.yaml 8 | - ingress.yaml 9 | - admin.secret.sops.yaml 10 | -------------------------------------------------------------------------------- /talos/patches/local-path-provisioner-mount.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | machine: 3 | kubelet: 4 | extraMounts: 5 | - destination: /var/local-path-provisioner 6 | type: bind 7 | source: /var/local-path-provisioner 8 | options: 9 | - bind 10 | - rshared 11 | - rw 12 | -------------------------------------------------------------------------------- /kubernetes/actual/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - ns.yaml 6 | - pvc.yaml 7 | - release.yaml 8 | - ingress.yaml 9 | - backup 10 | - akahu-actual 11 | -------------------------------------------------------------------------------- /kubernetes/cert-manager/issuers/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - cloudflare-issuer.secret.sops.yaml 7 | - letsencrypt-staging.yaml 8 | - letsencrypt-production.yaml 9 | -------------------------------------------------------------------------------- /kubernetes/ccinvoice/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - ns.yaml 6 | - pvc.yaml 7 | - release.yaml 8 | - ingress.yaml 9 | - ccinvoice.secret.sops.yaml 10 | - backup 11 | -------------------------------------------------------------------------------- /kubernetes/gitea/repo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/helmrepository_v1.json 3 | apiVersion: source.toolkit.fluxcd.io/v1 4 | kind: HelmRepository 5 | metadata: 6 | name: gitea 7 | namespace: gitea 8 | spec: 9 | url: https://dl.gitea.io/charts 10 | interval: 1h 11 | -------------------------------------------------------------------------------- /kubernetes/actual/ns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/namespace.json 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: actual 7 | annotations: 8 | volsync.backube/priveleged-movers: "true" # required for setting file permissions during a backup restore 9 | -------------------------------------------------------------------------------- /kubernetes/media/ns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/namespace.json 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: media 7 | annotations: 8 | volsync.backube/priveleged-movers: "true" # required for setting file permissions during a backup restore 9 | -------------------------------------------------------------------------------- /kubernetes/traefik/repo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/helmrepository_v1.json 3 | apiVersion: source.toolkit.fluxcd.io/v1 4 | kind: HelmRepository 5 | metadata: 6 | name: traefik 7 | namespace: traefik 8 | spec: 9 | url: https://helm.traefik.io/traefik 10 | interval: 1h 11 | -------------------------------------------------------------------------------- /kubernetes/ccinvoice/ns.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/namespace.json 3 | apiVersion: v1 4 | kind: Namespace 5 | metadata: 6 | name: ccinvoice 7 | annotations: 8 | volsync.backube/priveleged-movers: "true" # required for setting file permissions during a backup restore 9 | -------------------------------------------------------------------------------- /kubernetes/cert-manager/repo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/helmrepository_v1.json 3 | apiVersion: source.toolkit.fluxcd.io/v1 4 | kind: HelmRepository 5 | metadata: 6 | name: cert-manager 7 | namespace: cert-manager 8 | spec: 9 | url: https://charts.jetstack.io 10 | interval: 1h 11 | -------------------------------------------------------------------------------- /kubernetes/volsync/repo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/helmrepository_v1.json 3 | apiVersion: source.toolkit.fluxcd.io/v1 4 | kind: HelmRepository 5 | metadata: 6 | name: backube 7 | namespace: volsync 8 | spec: 9 | url: https://backube.github.io/helm-charts 10 | interval: 1h 11 | -------------------------------------------------------------------------------- /.taskfiles/sops.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://taskfile.dev/schema.json 3 | version: 3 4 | 5 | tasks: 6 | encrypt: 7 | desc: Encrypt all secret files with SOPS 8 | cmds: 9 | - ./scripts/sops.sh encrypt 10 | 11 | decrypt: 12 | desc: Decrypt all secret files with SOPS 13 | cmds: 14 | - ./scripts/sops.sh decrypt 15 | -------------------------------------------------------------------------------- /kubernetes/monitoring/loki/repo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/helmrepository_v1.json 3 | apiVersion: source.toolkit.fluxcd.io/v1 4 | kind: HelmRepository 5 | metadata: 6 | name: loki 7 | namespace: monitoring 8 | spec: 9 | url: https://grafana.github.io/helm-charts 10 | interval: 1h 11 | -------------------------------------------------------------------------------- /kubernetes/metallb-system/repo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/helmrepository_v1.json 3 | apiVersion: source.toolkit.fluxcd.io/v1 4 | kind: HelmRepository 5 | metadata: 6 | name: metallb 7 | namespace: metallb-system 8 | spec: 9 | url: https://metallb.github.io/metallb 10 | interval: 1h 11 | -------------------------------------------------------------------------------- /kubernetes/metallb-system/addresspool.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: metallb.io/v1beta1 3 | kind: IPAddressPool 4 | metadata: 5 | name: default-pool 6 | namespace: metallb-system 7 | spec: 8 | addresses: 9 | - 10.0.10.1-10.0.10.9 10 | --- 11 | apiVersion: metallb.io/v1beta1 12 | kind: L2Advertisement 13 | metadata: 14 | name: default-pool 15 | namespace: metallb-system 16 | -------------------------------------------------------------------------------- /kubernetes/storage/democratic-csi/repo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/helmrepository_v1.json 3 | apiVersion: source.toolkit.fluxcd.io/v1 4 | kind: HelmRepository 5 | metadata: 6 | name: democratic-csi 7 | namespace: storage 8 | spec: 9 | url: https://democratic-csi.github.io/charts/ 10 | interval: 1h 11 | -------------------------------------------------------------------------------- /kubernetes/actual/pvc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/persistentvolumeclaim.json 3 | apiVersion: v1 4 | kind: PersistentVolumeClaim 5 | metadata: 6 | name: actual 7 | namespace: actual 8 | spec: 9 | accessModes: 10 | - ReadWriteOnce 11 | resources: 12 | requests: 13 | storage: 2Gi 14 | -------------------------------------------------------------------------------- /kubernetes/gitea/pvc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/persistentvolumeclaim.json 3 | apiVersion: v1 4 | kind: PersistentVolumeClaim 5 | metadata: 6 | name: gitea-data 7 | namespace: gitea 8 | spec: 9 | accessModes: 10 | - ReadWriteOnce 11 | resources: 12 | requests: 13 | storage: 20Gi 14 | -------------------------------------------------------------------------------- /kubernetes/ccinvoice/pvc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/persistentvolumeclaim.json 3 | apiVersion: v1 4 | kind: PersistentVolumeClaim 5 | metadata: 6 | name: ccinvoice 7 | namespace: ccinvoice 8 | spec: 9 | accessModes: 10 | - ReadWriteOnce 11 | resources: 12 | requests: 13 | storage: 1Gi 14 | -------------------------------------------------------------------------------- /kubernetes/media/prowlarr/pvc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/persistentvolumeclaim.json 3 | apiVersion: v1 4 | kind: PersistentVolumeClaim 5 | metadata: 6 | name: prowlarr 7 | namespace: media 8 | spec: 9 | accessModes: 10 | - ReadWriteOnce 11 | resources: 12 | requests: 13 | storage: 1Gi 14 | -------------------------------------------------------------------------------- /talos/patches/allow-controlplane-metallb.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # removes the following from node labels 3 | # node.kubernetes.io/exclude-from-external-load-balancers: "" 4 | 5 | # TODO: **Work out why I can't target the propety directly** 6 | # Targeting the property directly does not work when gerenating the configuration from 7 | # this patch. 8 | 9 | machine: 10 | nodeLabels: 11 | $patch: delete 12 | -------------------------------------------------------------------------------- /kubernetes/gitea/mirror-pvc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/persistentvolumeclaim.json 3 | apiVersion: v1 4 | kind: PersistentVolumeClaim 5 | metadata: 6 | name: gitea-mirror-data 7 | namespace: gitea 8 | spec: 9 | accessModes: 10 | - ReadWriteOnce 11 | resources: 12 | requests: 13 | storage: 5Gi 14 | -------------------------------------------------------------------------------- /kubernetes/kube-system/metrics-server/repo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/helmrepository_v1beta2.json 3 | apiVersion: source.toolkit.fluxcd.io/v1 4 | kind: HelmRepository 5 | metadata: 6 | namespace: kube-system 7 | name: metrics-server 8 | spec: 9 | url: https://kubernetes-sigs.github.io/metrics-server/ 10 | interval: 24h 11 | -------------------------------------------------------------------------------- /kubernetes/media/qbittorrent/pvc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/persistentvolumeclaim.json 3 | apiVersion: v1 4 | kind: PersistentVolumeClaim 5 | metadata: 6 | name: qbittorrent 7 | namespace: media 8 | spec: 9 | accessModes: 10 | - ReadWriteOnce 11 | resources: 12 | requests: 13 | storage: 1Gi 14 | -------------------------------------------------------------------------------- /kubernetes/traefik/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - ns.yaml 7 | - release.yaml 8 | - repo.yaml 9 | - dashboard-ingress.yaml 10 | - middlewares.yaml 11 | - tinyauth.yaml 12 | - tinyauth.secret.sops.yaml 13 | - external-ingress.yaml 14 | -------------------------------------------------------------------------------- /kubernetes/homepage/config/widgets.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://gethomepage.dev/configs/info-widgets/ 3 | - kubernetes: 4 | cluster: 5 | show: false 6 | cpu: true 7 | memory: true 8 | showLabel: true 9 | label: "cluster" 10 | nodes: 11 | show: true 12 | cpu: true 13 | memory: true 14 | showLabel: true 15 | - search: 16 | provider: brave 17 | target: _blank 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | if: github.ref == 'refs/heads/main' 12 | steps: 13 | - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4 14 | with: 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | release-type: simple 17 | -------------------------------------------------------------------------------- /kubernetes/gitea/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | resources: 5 | - ns.yaml 6 | - pvc.yaml 7 | - gitea.secret.sops.yaml 8 | - repo.yaml 9 | - release.yaml 10 | - ingress.yaml 11 | - backup 12 | - mirror-release.yaml 13 | - mirror-pvc.yaml 14 | - mirror.secret.sops.yaml 15 | - mirror-ingress.yaml 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.env linguist-detectable linguist-language=SHELL 3 | *.json linguist-detectable linguist-language=JSON 4 | *.json5 linguist-detectable linguist-language=JSON5 5 | *.md linguist-detectable linguist-language=MARKDOWN 6 | *.sh linguist-detectable linguist-language=SHELL 7 | *.toml linguist-detectable linguist-language=TOML 8 | *.yml linguist-detectable linguist-language=YAML 9 | *.yaml linguist-detectable linguist-language=YAML 10 | -------------------------------------------------------------------------------- /kubernetes/media/recyclarr/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - ./recyclarr.secret.sops.yaml 7 | - ./release.yaml 8 | configMapGenerator: 9 | - name: recyclarr-configmap 10 | namespace: media 11 | files: 12 | - recyclarr.yml=./config/recyclarr.yml 13 | generatorOptions: 14 | disableNameSuffixHash: true 15 | -------------------------------------------------------------------------------- /.sops.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | creation_rules: 3 | - path_regex: "kubernetes/.*?(secret|sops).ya?ml$" 4 | encrypted_regex: "^(data|stringData|email|host|apiKey)$" 5 | age: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 6 | 7 | - path_regex: "talos/.*?(secret|sops).ya?ml$" 8 | encrypted_regex: "^.*(id|key|secret|password|token|passphrase|ca|crt|pem).*$" 9 | age: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 10 | 11 | stores: 12 | yaml: 13 | indent: 2 14 | -------------------------------------------------------------------------------- /kubernetes/metallb-system/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/helm.toolkit.fluxcd.io/helmrelease_v2.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | namespace: metallb-system 7 | name: metallb 8 | spec: 9 | chart: 10 | spec: 11 | chart: metallb 12 | version: 0.15.3 13 | sourceRef: 14 | kind: HelmRepository 15 | name: metallb 16 | interval: 1h 17 | timeout: 10m 18 | -------------------------------------------------------------------------------- /kubernetes/homepage/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/service.json 3 | apiVersion: v1 4 | kind: Service 5 | metadata: 6 | name: homepage 7 | namespace: homepage 8 | labels: 9 | app.kubernetes.io/name: homepage 10 | spec: 11 | type: ClusterIP 12 | ports: 13 | - port: 3000 14 | targetPort: http 15 | protocol: TCP 16 | name: http 17 | selector: 18 | app.kubernetes.io/name: homepage 19 | -------------------------------------------------------------------------------- /kubernetes/monitoring/grafana/repo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/ocirepository_v1.json 3 | apiVersion: source.toolkit.fluxcd.io/v1 4 | kind: OCIRepository 5 | metadata: 6 | name: grafana 7 | namespace: monitoring 8 | spec: 9 | interval: 5m 10 | layerSelector: 11 | mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip 12 | operation: copy 13 | ref: 14 | tag: 10.4.0 15 | url: oci://ghcr.io/grafana/helm-charts/grafana 16 | -------------------------------------------------------------------------------- /kubernetes/actual/backup/backup.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/volsync.backube/replicationsource_v1alpha1.json 3 | apiVersion: volsync.backube/v1alpha1 4 | kind: ReplicationSource 5 | metadata: 6 | name: actual-actual 7 | namespace: actual 8 | spec: 9 | sourcePVC: actual 10 | trigger: 11 | schedule: "0 13 * * *" 12 | restic: 13 | pruneIntervalDays: 14 14 | repository: restic-actual-actual 15 | retain: 16 | daily: 7 17 | weekly: 4 18 | monthly: 2 19 | copyMethod: Direct 20 | -------------------------------------------------------------------------------- /kubernetes/kube-system/snapshot-controller/repo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/ocirepository_v1.json 3 | apiVersion: source.toolkit.fluxcd.io/v1 4 | kind: OCIRepository 5 | metadata: 6 | name: snapshot-controller 7 | namespace: kube-system 8 | spec: 9 | interval: 5m 10 | layerSelector: 11 | mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip 12 | operation: copy 13 | ref: 14 | tag: 4.2.0 15 | url: oci://ghcr.io/piraeusdatastore/helm-charts/snapshot-controller 16 | -------------------------------------------------------------------------------- /kubernetes/actual/ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 3 | apiVersion: traefik.io/v1alpha1 4 | kind: IngressRoute 5 | metadata: 6 | name: actual 7 | namespace: traefik 8 | spec: 9 | routes: 10 | - match: Host(`budget.axis.scottmckendry.tech`) 11 | kind: Rule 12 | middlewares: 13 | - name: ipallowlist 14 | namespace: traefik 15 | services: 16 | - name: actual 17 | namespace: actual 18 | port: 5006 19 | tls: 20 | secretName: axis-tls 21 | -------------------------------------------------------------------------------- /kubernetes/gitea/ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 3 | apiVersion: traefik.io/v1alpha1 4 | kind: IngressRoute 5 | metadata: 6 | name: gitea-web 7 | namespace: traefik 8 | spec: 9 | routes: 10 | - match: Host(`git.axis.scottmckendry.tech`) 11 | kind: Rule 12 | middlewares: 13 | - name: ipallowlist 14 | namespace: traefik 15 | services: 16 | - name: gitea-http 17 | namespace: gitea 18 | port: 3000 19 | tls: 20 | secretName: axis-tls 21 | -------------------------------------------------------------------------------- /kubernetes/kube-system/metrics-server/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/helm.toolkit.fluxcd.io/helmrelease_v2.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | namespace: kube-system 7 | name: metrics-server 8 | spec: 9 | chart: 10 | spec: 11 | sourceRef: 12 | kind: HelmRepository 13 | name: metrics-server 14 | chart: metrics-server 15 | version: 3.13.0 16 | interval: 1h 17 | values: 18 | args: 19 | - --kubelet-insecure-tls 20 | -------------------------------------------------------------------------------- /kubernetes/monitoring/kube-prometheus-stack/repo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/ocirepository_v1.json 3 | apiVersion: source.toolkit.fluxcd.io/v1 4 | kind: OCIRepository 5 | metadata: 6 | name: kube-prometheus-stack 7 | namespace: monitoring 8 | spec: 9 | interval: 5m 10 | layerSelector: 11 | mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip 12 | operation: copy 13 | ref: 14 | tag: 79.12.0 15 | url: oci://ghcr.io/prometheus-community/charts/kube-prometheus-stack 16 | -------------------------------------------------------------------------------- /kubernetes/storage/local-path-provisioner/repo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/gitrepository_v1.json 3 | apiVersion: source.toolkit.fluxcd.io/v1 4 | kind: GitRepository 5 | metadata: 6 | namespace: storage 7 | name: local-path-provisioner 8 | spec: 9 | interval: 24h 10 | url: https://github.com/rancher/local-path-provisioner 11 | ref: 12 | tag: v0.0.32 13 | ignore: | 14 | # exclude all 15 | /* 16 | # include charts directory 17 | !/deploy/chart/local-path-provisioner/ 18 | -------------------------------------------------------------------------------- /kubernetes/traefik/dashboard-ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 3 | apiVersion: traefik.io/v1alpha1 4 | kind: IngressRoute 5 | metadata: 6 | name: traefik-dashboard 7 | namespace: traefik 8 | spec: 9 | routes: 10 | - match: Host(`traefik.axis.scottmckendry.tech`) 11 | kind: Rule 12 | middlewares: 13 | - name: ipallowlist 14 | namespace: traefik 15 | services: 16 | - name: api@internal 17 | kind: TraefikService 18 | tls: 19 | secretName: axis-tls 20 | -------------------------------------------------------------------------------- /kubernetes/ccinvoice/ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 3 | apiVersion: traefik.io/v1alpha1 4 | kind: IngressRoute 5 | metadata: 6 | name: ccinvoice 7 | namespace: traefik 8 | spec: 9 | routes: 10 | - match: Host(`invoices.axis.scottmckendry.tech`) 11 | kind: Rule 12 | middlewares: 13 | - name: ipallowlist 14 | namespace: traefik 15 | services: 16 | - name: ccinvoice 17 | namespace: ccinvoice 18 | port: 3000 19 | tls: 20 | secretName: axis-tls 21 | -------------------------------------------------------------------------------- /kubernetes/monitoring/grafana/ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 3 | apiVersion: traefik.io/v1alpha1 4 | kind: IngressRoute 5 | metadata: 6 | name: grafana 7 | namespace: traefik 8 | spec: 9 | routes: 10 | - match: Host(`grafana.axis.scottmckendry.tech`) 11 | kind: Rule 12 | middlewares: 13 | - name: ipallowlist 14 | namespace: traefik 15 | services: 16 | - name: grafana 17 | namespace: monitoring 18 | port: 80 19 | tls: 20 | secretName: axis-tls 21 | -------------------------------------------------------------------------------- /kubernetes/gitea/mirror-ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 3 | apiVersion: traefik.io/v1alpha1 4 | kind: IngressRoute 5 | metadata: 6 | name: gitea-mirror-web 7 | namespace: traefik 8 | spec: 9 | routes: 10 | - match: Host(`gitea-mirror.axis.scottmckendry.tech`) 11 | kind: Rule 12 | middlewares: 13 | - name: ipallowlist 14 | namespace: traefik 15 | services: 16 | - name: gitea-mirror 17 | namespace: gitea 18 | port: 4321 19 | tls: 20 | secretName: axis-tls 21 | -------------------------------------------------------------------------------- /kubernetes/cert-manager/certificates/production/axis.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/cert-manager.io/certificate_v1.json 3 | apiVersion: cert-manager.io/v1 4 | kind: Certificate 5 | metadata: 6 | name: axis 7 | namespace: traefik # needs to be in the same namespace as the ingressroutes & middlewares 8 | spec: 9 | secretName: axis-tls 10 | issuerRef: 11 | name: letsencrypt-production 12 | kind: ClusterIssuer 13 | commonName: "*.axis.scottmckendry.tech" 14 | dnsNames: 15 | - "axis.scottmckendry.tech" 16 | - "*.axis.scottmckendry.tech" 17 | -------------------------------------------------------------------------------- /kubernetes/common/repos/app-template.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/ocirepository_v1.json 3 | # https://github.com/bjw-s-labs/helm-charts/blob/main/charts/library/common/values.yaml 4 | apiVersion: source.toolkit.fluxcd.io/v1 5 | kind: OCIRepository 6 | metadata: 7 | name: app-template 8 | namespace: flux-system 9 | spec: 10 | interval: 5m 11 | layerSelector: 12 | mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip 13 | operation: copy 14 | ref: 15 | tag: 3.7.3 16 | url: oci://ghcr.io/bjw-s/helm/app-template 17 | -------------------------------------------------------------------------------- /kubernetes/cert-manager/certificates/staging/axis.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/cert-manager.io/certificate_v1.json 3 | apiVersion: cert-manager.io/v1 4 | kind: Certificate 5 | metadata: 6 | name: axis-staging 7 | namespace: traefik # needs to be in the same namespace as the ingressroutes & middlewares 8 | spec: 9 | secretName: axis-staging-tls 10 | issuerRef: 11 | name: letsencrypt-staging 12 | kind: ClusterIssuer 13 | commonName: "*.axis.scottmckendry.tech" 14 | dnsNames: 15 | - "axis.scottmckendry.tech" 16 | - "*.axis.scottmckendry.tech" 17 | -------------------------------------------------------------------------------- /kubernetes/homepage/ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 3 | apiVersion: traefik.io/v1alpha1 4 | kind: IngressRoute 5 | metadata: 6 | name: homepage 7 | namespace: traefik 8 | spec: 9 | routes: 10 | - match: Host(`axis.scottmckendry.tech`) 11 | kind: Rule 12 | middlewares: 13 | - name: ipallowlist 14 | namespace: traefik 15 | - name: forwardauth 16 | namespace: traefik 17 | services: 18 | - name: homepage 19 | namespace: homepage 20 | port: 3000 21 | tls: 22 | secretName: axis-tls 23 | -------------------------------------------------------------------------------- /kubernetes/media/radarr/ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 3 | apiVersion: traefik.io/v1alpha1 4 | kind: IngressRoute 5 | metadata: 6 | name: radarr 7 | namespace: traefik 8 | spec: 9 | routes: 10 | - match: Host(`radarr.axis.scottmckendry.tech`) 11 | kind: Rule 12 | middlewares: 13 | - name: ipallowlist 14 | namespace: traefik 15 | - name: forwardauth 16 | namespace: traefik 17 | services: 18 | - name: radarr 19 | namespace: media 20 | port: 80 21 | tls: 22 | secretName: axis-tls 23 | -------------------------------------------------------------------------------- /kubernetes/media/sonarr/ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 3 | apiVersion: traefik.io/v1alpha1 4 | kind: IngressRoute 5 | metadata: 6 | name: sonarr 7 | namespace: traefik 8 | spec: 9 | routes: 10 | - match: Host(`sonarr.axis.scottmckendry.tech`) 11 | kind: Rule 12 | middlewares: 13 | - name: ipallowlist 14 | namespace: traefik 15 | - name: forwardauth 16 | namespace: traefik 17 | services: 18 | - name: sonarr 19 | namespace: media 20 | port: 80 21 | tls: 22 | secretName: axis-tls 23 | -------------------------------------------------------------------------------- /kubernetes/homepage/kustomization.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/kustomization.json 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | resources: 6 | - ns.yaml 7 | - clusterrole.yaml 8 | - serviceaccount.yaml 9 | - deployment.yaml 10 | - service.yaml 11 | - ingress.yaml 12 | - homepage.secret.sops.yaml 13 | configMapGenerator: 14 | - name: homepage 15 | namespace: homepage 16 | files: 17 | - config/kubernetes.yaml 18 | - config/settings.yaml 19 | - config/bookmarks.yaml 20 | - config/services.yaml 21 | - config/widgets.yaml 22 | -------------------------------------------------------------------------------- /kubernetes/media/prowlarr/ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 3 | apiVersion: traefik.io/v1alpha1 4 | kind: IngressRoute 5 | metadata: 6 | name: prowlarr 7 | namespace: traefik 8 | spec: 9 | routes: 10 | - match: Host(`prowlarr.axis.scottmckendry.tech`) 11 | kind: Rule 12 | middlewares: 13 | - name: ipallowlist 14 | namespace: traefik 15 | - name: forwardauth 16 | namespace: traefik 17 | services: 18 | - name: prowlarr-app 19 | namespace: media 20 | port: 80 21 | tls: 22 | secretName: axis-tls 23 | -------------------------------------------------------------------------------- /kubernetes/media/qbittorrent/ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 3 | apiVersion: traefik.io/v1alpha1 4 | kind: IngressRoute 5 | metadata: 6 | name: qbittorrent 7 | namespace: traefik 8 | spec: 9 | routes: 10 | - match: Host(`qbt.axis.scottmckendry.tech`) 11 | kind: Rule 12 | middlewares: 13 | - name: ipallowlist 14 | namespace: traefik 15 | - name: forwardauth 16 | namespace: traefik 17 | services: 18 | - name: qbittorrent 19 | namespace: media 20 | port: 80 21 | tls: 22 | secretName: axis-tls 23 | -------------------------------------------------------------------------------- /kubernetes/cert-manager/issuers/letsencrypt-production.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/cert-manager.io/clusterissuer_v1.json 3 | apiVersion: cert-manager.io/v1 4 | kind: ClusterIssuer 5 | metadata: 6 | name: letsencrypt-production 7 | namespace: cert-manager 8 | spec: 9 | acme: 10 | server: https://acme-v02.api.letsencrypt.org/directory 11 | privateKeySecretRef: 12 | name: letsencrypt-production 13 | solvers: 14 | - dns01: 15 | cloudflare: 16 | apiTokenSecretRef: 17 | name: cloudflare-issuer-secret 18 | key: CLOUDFLARE_DNS_TOKEN 19 | -------------------------------------------------------------------------------- /kubernetes/cert-manager/issuers/letsencrypt-staging.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/cert-manager.io/clusterissuer_v1.json 3 | apiVersion: cert-manager.io/v1 4 | kind: ClusterIssuer 5 | metadata: 6 | name: letsencrypt-staging 7 | namespace: cert-manager 8 | spec: 9 | acme: 10 | server: https://acme-staging-v02.api.letsencrypt.org/directory 11 | privateKeySecretRef: 12 | name: letsencrypt-staging 13 | solvers: 14 | - dns01: 15 | cloudflare: 16 | apiTokenSecretRef: 17 | name: cloudflare-issuer-secret 18 | key: CLOUDFLARE_DNS_TOKEN 19 | -------------------------------------------------------------------------------- /kubernetes/monitoring/loki/promtail.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/helm.toolkit.fluxcd.io/helmrelease_v2.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: promtail 7 | namespace: monitoring 8 | spec: 9 | chart: 10 | spec: 11 | chart: promtail 12 | version: 6.17.1 13 | sourceRef: 14 | kind: HelmRepository 15 | name: loki 16 | interval: 1h 17 | timeout: 10m 18 | values: 19 | config: 20 | clients: 21 | - url: http://loki-headless.monitoring.svc.cluster.local:3100/loki/api/v1/push 22 | serviceMonitor: 23 | enabled: true 24 | -------------------------------------------------------------------------------- /kubernetes/storage/local-path-provisioner/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/helm.toolkit.fluxcd.io/helmrelease_v2.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | namespace: storage 7 | name: local-path-provisioner 8 | spec: 9 | chart: 10 | spec: 11 | chart: deploy/chart/local-path-provisioner 12 | version: 0.0.31 13 | sourceRef: 14 | kind: GitRepository 15 | name: local-path-provisioner 16 | interval: 1h 17 | values: 18 | nodePathMap: 19 | - node: DEFAULT_PATH_FOR_NON_LISTED_NODES 20 | paths: 21 | - /var/local-path-provisioner 22 | replicaCount: 3 23 | -------------------------------------------------------------------------------- /kubernetes/kube-system/snapshot-controller/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/helm.toolkit.fluxcd.io/helmrelease_v2.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: snapshot-controller 7 | namespace: kube-system 8 | spec: 9 | interval: 1h 10 | chartRef: 11 | kind: OCIRepository 12 | name: snapshot-controller 13 | install: 14 | crds: CreateReplace 15 | remediation: 16 | retries: -1 17 | upgrade: 18 | cleanupOnFail: true 19 | crds: CreateReplace 20 | remediation: 21 | retries: 3 22 | values: 23 | controller: 24 | replicaCount: 2 25 | serviceMonitor: 26 | create: true 27 | -------------------------------------------------------------------------------- /kubernetes/volsync/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/helm.toolkit.fluxcd.io/helmrelease_v2.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | namespace: volsync 7 | name: volsync 8 | spec: 9 | chart: 10 | spec: 11 | chart: volsync 12 | version: 0.14.0 13 | sourceRef: 14 | kind: HelmRepository 15 | name: backube 16 | interval: 1h 17 | timeout: 10m 18 | values: 19 | manageCRDs: true 20 | replicaCount: 2 21 | metrics: 22 | enabled: true 23 | disableAuth: true 24 | serviceMonitor: 25 | enabled: true 26 | namespace: monitoring 27 | honorLabels: true 28 | -------------------------------------------------------------------------------- /kubernetes/gitea/backup/backup.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/volsync.backube/replicationsource_v1alpha1.json 3 | apiVersion: volsync.backube/v1alpha1 4 | kind: ReplicationSource 5 | metadata: 6 | name: gitea-gitea 7 | namespace: gitea 8 | spec: 9 | sourcePVC: gitea-data 10 | trigger: 11 | schedule: "0 13 * * *" 12 | restic: 13 | moverSecurityContext: 14 | runAsNonRoot: true 15 | runAsUser: 1000 16 | runAsGroup: 1000 17 | fsGroup: 1000 18 | fsGroupChangePolicy: OnRootMismatch 19 | pruneIntervalDays: 14 20 | repository: restic-gitea-gitea 21 | retain: 22 | daily: 7 23 | weekly: 4 24 | monthly: 2 25 | copyMethod: Direct 26 | -------------------------------------------------------------------------------- /kubernetes/media/radarr/backup/backup.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/volsync.backube/replicationsource_v1alpha1.json 3 | apiVersion: volsync.backube/v1alpha1 4 | kind: ReplicationSource 5 | metadata: 6 | name: media-radarr 7 | namespace: media 8 | spec: 9 | sourcePVC: radarr 10 | trigger: 11 | schedule: "0 13 * * *" 12 | restic: 13 | moverSecurityContext: 14 | runAsNonRoot: true 15 | runAsUser: 1000 16 | runAsGroup: 1000 17 | fsGroup: 1000 18 | fsGroupChangePolicy: OnRootMismatch 19 | pruneIntervalDays: 14 20 | repository: restic-media-radarr 21 | retain: 22 | daily: 7 23 | weekly: 4 24 | monthly: 2 25 | copyMethod: Direct 26 | -------------------------------------------------------------------------------- /kubernetes/media/sonarr/backup/backup.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/volsync.backube/replicationsource_v1alpha1.json 3 | apiVersion: volsync.backube/v1alpha1 4 | kind: ReplicationSource 5 | metadata: 6 | name: media-sonarr 7 | namespace: media 8 | spec: 9 | sourcePVC: sonarr 10 | trigger: 11 | schedule: "0 13 * * *" 12 | restic: 13 | moverSecurityContext: 14 | runAsNonRoot: true 15 | runAsUser: 1000 16 | runAsGroup: 1000 17 | fsGroup: 1000 18 | fsGroupChangePolicy: OnRootMismatch 19 | pruneIntervalDays: 14 20 | repository: restic-media-sonarr 21 | retain: 22 | daily: 7 23 | weekly: 4 24 | monthly: 2 25 | copyMethod: Direct 26 | -------------------------------------------------------------------------------- /kubernetes/media/prowlarr/backup/backup.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/volsync.backube/replicationsource_v1alpha1.json 3 | apiVersion: volsync.backube/v1alpha1 4 | kind: ReplicationSource 5 | metadata: 6 | name: media-prowlarr 7 | namespace: media 8 | spec: 9 | sourcePVC: prowlarr 10 | trigger: 11 | schedule: "0 13 * * *" 12 | restic: 13 | moverSecurityContext: 14 | runAsNonRoot: true 15 | runAsUser: 1000 16 | runAsGroup: 1000 17 | fsGroup: 1000 18 | fsGroupChangePolicy: OnRootMismatch 19 | pruneIntervalDays: 14 20 | repository: restic-media-prowlarr 21 | retain: 22 | daily: 7 23 | weekly: 4 24 | monthly: 2 25 | copyMethod: Direct 26 | -------------------------------------------------------------------------------- /kubernetes/ccinvoice/backup/backup.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/volsync.backube/replicationsource_v1alpha1.json 3 | apiVersion: volsync.backube/v1alpha1 4 | kind: ReplicationSource 5 | metadata: 6 | name: ccinvoice-ccinvoice 7 | namespace: ccinvoice 8 | spec: 9 | sourcePVC: ccinvoice 10 | trigger: 11 | schedule: "0 13 * * *" 12 | restic: 13 | moverSecurityContext: 14 | runAsNonRoot: true 15 | runAsUser: 1000 16 | runAsGroup: 1000 17 | fsGroup: 1000 18 | fsGroupChangePolicy: OnRootMismatch 19 | pruneIntervalDays: 14 20 | repository: restic-ccinvoice-ccinvoice 21 | retain: 22 | daily: 7 23 | weekly: 4 24 | monthly: 2 25 | copyMethod: Direct 26 | -------------------------------------------------------------------------------- /kubernetes/media/radarr/pvc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/persistentvolumeclaim.json 3 | apiVersion: v1 4 | kind: PersistentVolumeClaim 5 | metadata: 6 | name: radarr 7 | namespace: media 8 | spec: 9 | accessModes: 10 | - ReadWriteOnce 11 | resources: 12 | requests: 13 | storage: 1Gi 14 | --- 15 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/persistentvolumeclaim.json 16 | apiVersion: v1 17 | kind: PersistentVolumeClaim 18 | metadata: 19 | name: radarr-cache 20 | namespace: media 21 | spec: 22 | accessModes: 23 | - ReadWriteOnce 24 | resources: 25 | requests: 26 | storage: 1Gi 27 | -------------------------------------------------------------------------------- /kubernetes/media/sonarr/pvc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/persistentvolumeclaim.json 3 | apiVersion: v1 4 | kind: PersistentVolumeClaim 5 | metadata: 6 | name: sonarr 7 | namespace: media 8 | spec: 9 | accessModes: 10 | - ReadWriteOnce 11 | resources: 12 | requests: 13 | storage: 1Gi 14 | --- 15 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/persistentvolumeclaim.json 16 | apiVersion: v1 17 | kind: PersistentVolumeClaim 18 | metadata: 19 | name: sonarr-cache 20 | namespace: media 21 | spec: 22 | accessModes: 23 | - ReadWriteOnce 24 | resources: 25 | requests: 26 | storage: 1Gi 27 | -------------------------------------------------------------------------------- /kubernetes/homepage/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/serviceaccount.json 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: homepage 7 | namespace: homepage 8 | labels: 9 | app.kubernetes.io/name: homepage 10 | secrets: 11 | - name: homepage 12 | --- 13 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 14 | apiVersion: v1 15 | kind: Secret 16 | type: kubernetes.io/service-account-token 17 | metadata: 18 | name: homepage 19 | namespace: homepage 20 | labels: 21 | app.kubernetes.io/name: homepage 22 | annotations: 23 | kubernetes.io/service-account.name: homepage 24 | -------------------------------------------------------------------------------- /kubernetes/home-assistant/backup/backup.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/volsync.backube/replicationsource_v1alpha1.json 3 | apiVersion: volsync.backube/v1alpha1 4 | kind: ReplicationSource 5 | metadata: 6 | name: home-assistant-home-assistant 7 | namespace: home-assistant 8 | spec: 9 | sourcePVC: home-assistant 10 | trigger: 11 | schedule: "0 13 * * *" 12 | restic: 13 | moverSecurityContext: 14 | runAsNonRoot: true 15 | runAsUser: 1000 16 | runAsGroup: 1000 17 | fsGroup: 1000 18 | fsGroupChangePolicy: OnRootMismatch 19 | pruneIntervalDays: 14 20 | repository: restic-home-assistant-home-assistant 21 | retain: 22 | daily: 7 23 | weekly: 4 24 | monthly: 2 25 | copyMethod: Direct 26 | -------------------------------------------------------------------------------- /kubernetes/home-assistant/pvc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/persistentvolumeclaim.json 3 | apiVersion: v1 4 | kind: PersistentVolumeClaim 5 | metadata: 6 | name: home-assistant 7 | namespace: home-assistant 8 | spec: 9 | accessModes: 10 | - ReadWriteOnce 11 | resources: 12 | requests: 13 | storage: 1Gi 14 | --- 15 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/persistentvolumeclaim.json 16 | apiVersion: v1 17 | kind: PersistentVolumeClaim 18 | metadata: 19 | name: home-assistant-cache 20 | namespace: home-assistant 21 | spec: 22 | accessModes: 23 | - ReadWriteOnce 24 | resources: 25 | requests: 26 | storage: 1Gi 27 | -------------------------------------------------------------------------------- /kubernetes/monitoring/kube-prometheus-stack/alertmanagerconfig.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/monitoring.coreos.com/alertmanagerconfig_v1alpha1.json 3 | apiVersion: monitoring.coreos.com/v1alpha1 4 | kind: AlertmanagerConfig 5 | metadata: 6 | name: alertmanager 7 | namespace: monitoring 8 | spec: 9 | route: 10 | groupBy: ["alertname", "job"] 11 | groupInterval: 5m 12 | groupWait: 1m 13 | receiver: discord 14 | repeatInterval: 12h 15 | routes: 16 | - matchers: 17 | - name: alertname 18 | matchType: "=" 19 | value: Watchdog 20 | receiver: "null" 21 | receivers: 22 | - name: "null" 23 | - name: discord 24 | discordConfigs: 25 | - apiURL: 26 | name: alertmanager-discord 27 | key: DISCORD_WEBHOOK_URL 28 | sendResolved: true 29 | -------------------------------------------------------------------------------- /kubernetes/cert-manager/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/helm.toolkit.fluxcd.io/helmrelease_v2.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | namespace: cert-manager 7 | name: cert-manager 8 | spec: 9 | chart: 10 | spec: 11 | chart: cert-manager 12 | version: v1.19.2 13 | sourceRef: 14 | kind: HelmRepository 15 | name: cert-manager 16 | interval: 1h 17 | timeout: 10m 18 | values: 19 | extraArgs: 20 | - --dns01-recursive-nameservers=1.1.1.1:53,9.9.9.9:53 21 | - --dns01-recursive-nameservers-only=true 22 | replicaCount: 3 23 | crds: 24 | enabled: true 25 | podDnsPolicy: "None" 26 | podDnsConfig: 27 | nameservers: 28 | - 1.1.1.1 29 | - 9.9.9.9 30 | 31 | prometheus: 32 | enabled: true 33 | servicemonitor: 34 | enabled: true 35 | -------------------------------------------------------------------------------- /kubernetes/flux-system/gotk-sync.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/gitrepository_v1.json 3 | apiVersion: source.toolkit.fluxcd.io/v1 4 | kind: GitRepository 5 | metadata: 6 | name: flux-system 7 | namespace: flux-system 8 | spec: 9 | interval: 1m0s 10 | ref: 11 | branch: main 12 | secretRef: 13 | name: flux-system 14 | url: https://github.com/scottmckendry/axis.git 15 | --- 16 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/kustomize.toolkit.fluxcd.io/kustomization_v1.json 17 | apiVersion: kustomize.toolkit.fluxcd.io/v1 18 | kind: Kustomization 19 | metadata: 20 | name: flux-system 21 | namespace: flux-system 22 | spec: 23 | interval: 10m0s 24 | path: ./kubernetes 25 | prune: true 26 | sourceRef: 27 | kind: GitRepository 28 | name: flux-system 29 | decryption: 30 | provider: sops 31 | secretRef: 32 | name: sops-age 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Scott McKendry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /kubernetes/home-assistant/ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 3 | apiVersion: traefik.io/v1alpha1 4 | kind: IngressRoute 5 | metadata: 6 | name: home-assistant 7 | namespace: traefik 8 | spec: 9 | routes: 10 | - match: Host(`ha.axis.scottmckendry.tech`) 11 | kind: Rule 12 | middlewares: 13 | - name: ipallowlist 14 | namespace: traefik 15 | services: 16 | - name: home-assistant-app 17 | namespace: home-assistant 18 | port: 8123 19 | tls: 20 | secretName: axis-tls 21 | --- 22 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 23 | apiVersion: traefik.io/v1alpha1 24 | kind: IngressRoute 25 | metadata: 26 | name: home-assistant-code 27 | namespace: traefik 28 | spec: 29 | routes: 30 | - match: Host(`ha-code.axis.scottmckendry.tech`) 31 | kind: Rule 32 | middlewares: 33 | - name: ipallowlist 34 | namespace: traefik 35 | - name: forwardauth 36 | namespace: traefik 37 | services: 38 | - name: home-assistant-code 39 | namespace: home-assistant 40 | port: 8081 41 | tls: 42 | secretName: axis-tls 43 | -------------------------------------------------------------------------------- /kubernetes/traefik/middlewares.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 3 | apiVersion: traefik.io/v1alpha1 4 | kind: Middleware 5 | metadata: 6 | name: ipallowlist 7 | namespace: traefik 8 | spec: 9 | ipAllowList: 10 | sourceRange: 11 | - 127.0.0.1/32 12 | - 10.0.0.0/8 13 | - 192.168.0.0/16 14 | --- 15 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 16 | apiVersion: traefik.io/v1alpha1 17 | kind: Middleware 18 | metadata: 19 | name: forwardauth 20 | namespace: traefik 21 | spec: 22 | forwardAuth: 23 | address: http://tinyauth.traefik.svc.cluster.local:3000/api/auth/traefik 24 | --- 25 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 26 | apiVersion: traefik.io/v1alpha1 27 | kind: Middleware 28 | metadata: 29 | name: default-headers 30 | namespace: traefik 31 | spec: 32 | headers: 33 | frameDeny: true 34 | sslRedirect: true 35 | browserXssFilter: true 36 | contentTypeNosniff: true 37 | forceSTSHeader: true 38 | stsIncludeSubdomains: true 39 | stsPreload: true 40 | stsSeconds: 15552000 41 | customFrameOptionsValue: SAMEORIGIN 42 | customRequestHeaders: 43 | X-Forwarded-Proto: https 44 | -------------------------------------------------------------------------------- /kubernetes/monitoring/kube-prometheus-stack/ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 3 | apiVersion: traefik.io/v1alpha1 4 | kind: IngressRoute 5 | metadata: 6 | name: prometheus 7 | namespace: traefik 8 | spec: 9 | routes: 10 | - match: Host(`prometheus.axis.scottmckendry.tech`) 11 | kind: Rule 12 | middlewares: 13 | - name: ipallowlist 14 | namespace: traefik 15 | - name: forwardauth 16 | namespace: traefik 17 | services: 18 | - name: kube-prometheus-stack-prometheus 19 | namespace: monitoring 20 | port: 9090 21 | tls: 22 | secretName: axis-tls 23 | --- 24 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 25 | apiVersion: traefik.io/v1alpha1 26 | kind: IngressRoute 27 | metadata: 28 | name: alertmanager 29 | namespace: traefik 30 | spec: 31 | routes: 32 | - match: Host(`am.axis.scottmckendry.tech`) 33 | kind: Rule 34 | middlewares: 35 | - name: ipallowlist 36 | namespace: traefik 37 | - name: forwardauth 38 | namespace: traefik 39 | services: 40 | - name: kube-prometheus-stack-alertmanager 41 | namespace: monitoring 42 | port: 9093 43 | tls: 44 | secretName: axis-tls 45 | -------------------------------------------------------------------------------- /kubernetes/traefik/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/helm.toolkit.fluxcd.io/helmrelease_v2.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | namespace: traefik 7 | name: traefik 8 | spec: 9 | chart: 10 | spec: 11 | chart: traefik 12 | version: 37.4.0 13 | sourceRef: 14 | kind: HelmRepository 15 | name: traefik 16 | interval: 1h 17 | timeout: 10m 18 | values: 19 | # https://github.com/traefik/traefik-helm-chart/blob/master/traefik/values.yaml 20 | additionalArguments: 21 | - --serversTransport.insecureskipverify=true 22 | 23 | deployment: 24 | enabled: true 25 | replicas: 3 26 | 27 | ports: 28 | web: 29 | redirections: 30 | entrypoint: 31 | to: websecure 32 | priority: 10 33 | 34 | service: 35 | enabled: true 36 | type: LoadBalancer 37 | LoadBalancerIP: 10.0.10.1 38 | externalTrafficPolicy: Local 39 | 40 | providers: 41 | kubernetesCRD: 42 | allowCrossNamespace: true 43 | allowExternalNameServices: true 44 | 45 | metrics: 46 | prometheus: 47 | service: 48 | enabled: true 49 | serviceMonitor: 50 | enabled: true 51 | namespace: monitoring 52 | honorLabels: true 53 | -------------------------------------------------------------------------------- /kubernetes/gitea/mirror.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | type: Opaque 5 | metadata: 6 | name: gitea-mirror-secret 7 | namespace: gitea 8 | stringData: 9 | BETTER_AUTH_SECRET: ENC[AES256_GCM,data:UzWXX+iwBl0wHimp3RNkj/dn+pTq4Qru9G5UaNL3pH4=,iv:Kh2eUPa1+ugGbBJgso7CdOR42r1lNlUBNPUf75Wbs+g=,tag:0fMDL4UoLNcH84Z1tdclSA==,type:str] 10 | sops: 11 | age: 12 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 13 | enc: | 14 | -----BEGIN AGE ENCRYPTED FILE----- 15 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaWmtKOVh3cFJ5NTE1czZ4 16 | eUp3M3pJbytkU3NkQW42VmZoV0tZanFzL3hZCmJvOG1oSGpuQTZBU3MzWmFyMXRQ 17 | WVlwUzl1akRJVjRVNkp5cGl3SEU1UE0KLS0tIFprbFBEenlocFNPbTRLc3IyVTZC 18 | ZWttUFcyY1hSRElzQ3FmN29EK2VaUHcK8w/H1ERdfDOQzHEfL82zWZHta75uPeR0 19 | 36rI9Fms/wsfjVEMw0xmNET+wAZvM89qlkHh5ddxC2hintcsLy2Lhg== 20 | -----END AGE ENCRYPTED FILE----- 21 | lastmodified: "2025-10-24T21:47:11Z" 22 | mac: ENC[AES256_GCM,data:IZo21ej6Xz5F3vGFksMHpgvVTIzu6NY5zD3VG8qvDT3gXwg+aLJ5oW4a8iJ/5jJW4qbjTdZ1wk0zw4j5elWyy7/8YKDcmhbsRMZSoEwMCxRae+QXzJtTQM2c0OMsLqrKNW/ogaCUicF2YxwlLvF9HuGLJyoAaTxig6VqVYCoHOU=,iv:tqdnuwcY/1SA0d55FGlMh3gieZe5gLbMLtVxIsyh+fc=,tag:o8xkB0bqKWSkjmuvYX6pjg==,type:str] 23 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 24 | version: 3.11.0 25 | -------------------------------------------------------------------------------- /kubernetes/actual/akahu-actual/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/bjw-s/helm-charts/main/charts/other/app-template/schemas/helmrelease-helm-v2.schema.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: akahu-actual 7 | namespace: actual 8 | spec: 9 | interval: 1h 10 | chartRef: 11 | kind: OCIRepository 12 | name: app-template 13 | namespace: flux-system 14 | dependsOn: 15 | - name: actual 16 | namespace: actual 17 | values: 18 | controllers: 19 | akahu-actual: 20 | type: cronjob 21 | cronjob: 22 | schedule: 49 * * * * 23 | containers: 24 | tag: 25 | image: 26 | repository: ghcr.io/scottmckendry/akahu-actual 27 | tag: v0.9.0@sha256:2be76e9936f255950d2b0fa2ab0b321e64622d194a1d22fb16283ab84e00fb2b 28 | envFrom: 29 | - secretRef: 30 | name: akahu-actual-secret 31 | securityContext: 32 | allowPrivilegeEscalation: false 33 | readOnlyRootFilesystem: true 34 | capabilities: { drop: ["ALL"] } 35 | defaultPodOptions: 36 | securityContext: 37 | runAsNonRoot: true 38 | runAsUser: 1000 39 | runAsGroup: 1000 40 | persistence: 41 | tmp: 42 | type: emptyDir 43 | globalMounts: 44 | - path: /tmp 45 | readOnly: false 46 | -------------------------------------------------------------------------------- /kubernetes/ccinvoice/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/bjw-s/helm-charts/main/charts/other/app-template/schemas/helmrelease-helm-v2.schema.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: ccinvoice 7 | namespace: ccinvoice 8 | spec: 9 | interval: 1h 10 | chartRef: 11 | kind: OCIRepository 12 | name: app-template 13 | namespace: flux-system 14 | values: 15 | controllers: 16 | main: 17 | containers: 18 | app: 19 | image: 20 | repository: ghcr.io/scottmckendry/ccinvoice 21 | tag: v3.2.1@sha256:69c5c19479c7c04c3a35dd03b8291a2065cbb64a1a42bccdf7b955bd48346f8e 22 | envFrom: 23 | - secretRef: 24 | name: ccinvoice-secrets 25 | securityContext: 26 | allowPrivilegeEscalation: false 27 | capabilities: 28 | drop: ["ALL"] 29 | seccompProfile: 30 | type: RuntimeDefault 31 | defaultPodOptions: 32 | securityContext: 33 | runAsNonRoot: true 34 | runAsUser: 1000 35 | runAsGroup: 1000 36 | fsGroup: 1000 37 | service: 38 | main: 39 | controller: main 40 | ports: 41 | http: 42 | port: 3000 43 | persistence: 44 | data: 45 | existingClaim: ccinvoice 46 | globalMounts: 47 | - path: /app/data 48 | -------------------------------------------------------------------------------- /kubernetes/cert-manager/issuers/cloudflare-issuer.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: cloudflare-issuer-secret 6 | namespace: cert-manager 7 | type: Opaque 8 | stringData: 9 | CLOUDFLARE_DNS_TOKEN: ENC[AES256_GCM,data:ejtn+RWqSEYB4KMNXh2mRZGKsa7MJMbdNJ6IP3SnY9jwR63hiuUEBg==,iv:udyOcMsEwSwOYfZEJs8k5i9RDIvC/90OhkUFcR62WDk=,tag:PieqEHfr/8rnM2aOzLCa5Q==,type:str] 10 | sops: 11 | kms: [] 12 | gcp_kms: [] 13 | azure_kv: [] 14 | hc_vault: [] 15 | age: 16 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 17 | enc: | 18 | -----BEGIN AGE ENCRYPTED FILE----- 19 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXekZjdWxQZ002ZVpaUEd3 20 | dTRiOE1rVW0xSTJLbE8rM0cyOFJiMW1LSjBvClNlcGtXaTQ1UTJzOVZ0TUU5RVpM 21 | M1JMRC9wdTcya05pQ1gyUmJZeEUvVzAKLS0tIFhiSHhYNlN0dXJWbElLa3A0dis2 22 | N1A4VmJHNk1SZWp4RndBWlZmenJTQWMKxIJPcvqu4cTyMw0rJr0to0aSBy49z/b/ 23 | i9u05iCOE4ywaByd0XL4EBxOCgQ515ac8wc5VmZiKE/AZN0g9zLbsg== 24 | -----END AGE ENCRYPTED FILE----- 25 | lastmodified: "2025-04-08T00:30:13Z" 26 | mac: ENC[AES256_GCM,data:/5s6F9fq7wqWOvQMlrXdTukGr1MKu6vyf4SIlimmuQjanjWQfNXX6BVV2Qf2gepz5KkUxQip4KM71Kdw5VptCpxRSnaC9pavVjf1zCiclZULfFDkdjwLwD/YnWvBjblWYzJM8COegmrk92v4ae6TKHR8empNpxr6YtFjUJokdJY=,iv:zVkGGGLnD/SSjZkni/D7AWjXjmnAgqKoOUL6RX2Jtm8=,tag:wdmA/Iv79wip6jb1LwWxtw==,type:str] 27 | pgp: [] 28 | encrypted_regex: ^(data|stringData|email)$ 29 | version: 3.9.4 30 | -------------------------------------------------------------------------------- /kubernetes/media/recyclarr/recyclarr.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: recyclarr-secret 6 | namespace: media 7 | type: Opaque 8 | stringData: 9 | SONARR_API_KEY: ENC[AES256_GCM,data:eJc+5fCwugmR8mfUX9sod380rXM4g2myvfcLWYXWNgA=,iv:6DdW+SByC3H7al97K+RcDPN129TYdViiWoTYP/0E5+E=,tag:qWJ+OsHGVHuNfbDg0NlTmA==,type:str] 10 | RADARR_API_KEY: ENC[AES256_GCM,data:l4E99hO65lHipkPffiDznrqrrmocw9iQRFzjpqVZ0Kc=,iv:XGajhEpTFjyiwLkxqwRieo6Em7nLT1La+mJ+bYRX61c=,tag:jp5rx4QD8bOoyNc086MJqg==,type:str] 11 | sops: 12 | age: 13 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 14 | enc: | 15 | -----BEGIN AGE ENCRYPTED FILE----- 16 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0MDNqN0F3Tkx1K0tid29V 17 | eElHbGFvOG1YTGJ1OXpRaFkwWkkwVEJNY1JRCnh1a0tQT1FDZVNWWmQ4OXF2N1Fm 18 | am5kTXlDdjV6WTR6SkFtak1kT0trZmsKLS0tIE1mcVVBbk5DVVpRRGNWMEYrR0Zh 19 | c1FVdWdXMmxUVHU3eDN0TkxJb3c3YjAKBFDMvsvacEtS+1Joh0pf/HAKqT9U+pDs 20 | Qyths4U8vXYYN2sWzH/kvNPe39GPs3GrpY13TUM+8/oo6wxjbHwjdQ== 21 | -----END AGE ENCRYPTED FILE----- 22 | lastmodified: "2025-05-07T08:01:12Z" 23 | mac: ENC[AES256_GCM,data:r04yHLLhQLGV098/1LosGyxdkV4U031X9TJHKTTu6MXHGphLo7jcIlRCv02gjo6tyU26UjWEscNyQJZJV80d6jyij5y98l7MdSf4y83NUtKXrN6d0eQYZNL1PZ2yodXwxRR1ssR1bPm8K1EkWQjDdEtb8cUng7xeJaTbcgTh9Pc=,iv:xqsKiR2CrZiMj+5oBbhcEO9PEA0jnRaTPpzmkpkXL1k=,tag:bp7wZqyHkzGjM/gSNGr6JQ==,type:str] 24 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 25 | version: 3.10.2 26 | -------------------------------------------------------------------------------- /kubernetes/gitea/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/helm.toolkit.fluxcd.io/helmrelease_v2.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: gitea 7 | namespace: gitea 8 | spec: 9 | chart: 10 | spec: 11 | chart: gitea 12 | version: 12.4.0 13 | sourceRef: 14 | kind: HelmRepository 15 | name: gitea 16 | interval: 1h 17 | timeout: 10m 18 | values: 19 | postgresql-ha: 20 | enabled: false 21 | valkey-cluster: 22 | enabled: false 23 | postgresql: 24 | enabled: true 25 | valkey: 26 | enabled: true 27 | 28 | gitea: 29 | admin: 30 | existingSecret: gitea-secrets 31 | metrics: 32 | enabled: true 33 | serviceMonitor: 34 | enabled: true 35 | config: 36 | indexer: 37 | REPO_INDEXER_ENABLED: true 38 | ISSUE_INDEXER_TYPE: bleve 39 | server: 40 | ROOT_URL: https://git.axis.scottmckendry.tech 41 | podAnnotations: {} 42 | 43 | image: 44 | rootless: true 45 | podSecurityContext: 46 | runAsUser: 1000 47 | runAsGroup: 1000 48 | fsGroup: 1000 49 | fsGroupChangePolicy: OnRootMismatch 50 | 51 | persistence: 52 | enabled: true 53 | claimName: gitea-data 54 | create: false 55 | 56 | service: 57 | http: 58 | type: ClusterIP 59 | port: 3000 60 | 61 | ingress: 62 | enabled: false # Using Traefik IngressRoute 63 | 64 | securityContext: 65 | capabilities: 66 | drop: 67 | - ALL 68 | -------------------------------------------------------------------------------- /kubernetes/monitoring/grafana/admin.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | type: Opaque 5 | metadata: 6 | name: grafana-admin-credentials 7 | namespace: monitoring 8 | stringData: 9 | admin-user: ENC[AES256_GCM,data:f8vY4TY=,iv:A+dEOMPxt2Y01qyVDitwzzK1/cBfXGER+u/1WIjqLBc=,tag:iEJvavjd7s9miXHfat4NYw==,type:str] 10 | admin-password: ENC[AES256_GCM,data:kldvClk50QmatrJo0id7,iv:HT0RM5ge5+gWxJksY6BUgDou2NrJi33TR49H7XgCmIg=,tag:qumIAgOTTCLvGO1r5WzAMw==,type:str] 11 | sops: 12 | kms: [] 13 | gcp_kms: [] 14 | azure_kv: [] 15 | hc_vault: [] 16 | age: 17 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 18 | enc: | 19 | -----BEGIN AGE ENCRYPTED FILE----- 20 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUZDh1cWJ3NFhRTjd4V0Iy 21 | bFBBeVEwMFVvblFqOXd4Q2JId2FTSVNMMVJZClAzWDEwSDdYSDczZlZSWkVzZG9j 22 | dWRGWlFTS2wzOUdVUnQzTHY1TlN1R00KLS0tIHVSVnVZcGd6aHNYcUpZSFRpSE9S 23 | WmZzdlduS0hwaXYrdm5VUStCWU1SUk0K2rD5jat8ogo2dLU7ZRRzjxBwL05202SA 24 | mba9WGgYvnmD41ZZ4TM+nTvxTmTpIxl1OBYpCsmd8R9tHtIyiw9vDA== 25 | -----END AGE ENCRYPTED FILE----- 26 | lastmodified: "2025-04-18T03:28:24Z" 27 | mac: ENC[AES256_GCM,data:r4xkzEKDyAlNGO1+e1b2eLEpQv9pVlgWG/JtsmHJgID00aDXoaUd2pgQSSmwZzUtGWtFmOkUurVU0k6FkzCnc/kWGTYxf+ln7Jyv77/0iSr8N9BNtzAqjR/R98HHAYbAcy95pKWiCPzQkDcr5diVVsIxCvqX3p+YlE+DWe8BgQE=,iv:dCyqToxKchx//IYOTHd3ws/HoQTkCad7xIyGwrENwCI=,tag:/Qvw5YLhURt/D3grvwJfkA==,type:str] 28 | pgp: [] 29 | encrypted_regex: ^(data|stringData|email)$ 30 | version: 3.9.4 31 | -------------------------------------------------------------------------------- /kubernetes/monitoring/kube-prometheus-stack/alertmanager.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: alertmanager-discord 6 | type: Opaque 7 | stringData: 8 | DISCORD_WEBHOOK_URL: ENC[AES256_GCM,data:Wmd7nzTbqjImbToxVHPVbPlY7m/Q5+Y5KN8NLtlxUHWH4/XY1trWZ7RRVyfeLhio/IhzGZzt7qinWao9NnHLdwUfv/J0Py5j7GL6fV30DeoOnwvMnS5yy2e51E29TfVMyDZ4GvZNlSXNP/JNlXiZU+0W68Zo+dPJqg==,iv:Z66ckwEKsC+le1aNBZ7wFrEErCjInCUK850KOnsI18Y=,tag:uoB+0+2SYncE/FdR6F6osw==,type:str] 9 | sops: 10 | kms: [] 11 | gcp_kms: [] 12 | azure_kv: [] 13 | hc_vault: [] 14 | age: 15 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 16 | enc: | 17 | -----BEGIN AGE ENCRYPTED FILE----- 18 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVQmZFSk5TUlVmNkdLeXly 19 | Vmk1MEw3MXRad2pVRkgzMithdlR3eFpPb2s4Ck1hWlk1dndvazBueW5JNERhMjdj 20 | ZkN5MDVTbGlFOUFXZ2M3bXF0U3pwN3cKLS0tIFNvVmJlV3hYSnBUQ0tzTHlUK3Vq 21 | M3B4dW9CeGNkbVRmM3U2OUdxdjdlUTAKv7Q9k6MpQkSvddcYYY7rui7kiCv1L9MG 22 | cm5Pq0bRiBRtASWEcZIswQmAYS5DZr23nBqZxGs7dHhsk7D3cYEMkA== 23 | -----END AGE ENCRYPTED FILE----- 24 | lastmodified: "2025-05-14T17:44:32Z" 25 | mac: ENC[AES256_GCM,data:kAMbKeKUx9ua0dsxAf4xkgILqRN30EpbPf8xFMw7l155baRnHeQVt6mK3PDsdQA/i0CQjyxKhOIUWGZ3dJmf8zwk+IoevbrvrWnP026OV1o0v36A1mys5kt6YyI2OVYqAJPXmQpPNmEAOAKOIvcl4+sh0HVFYAkgeSK+UQ/OL7A=,iv:INDkmEXv58OKEerqgkOh73wh5Fc0OxjMRIF8m9zSW3g=,tag:dUxszBcd5RY4chBiIINpow==,type:str] 26 | pgp: [] 27 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 28 | version: 3.9.4 29 | -------------------------------------------------------------------------------- /kubernetes/homepage/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/clusterrole.json 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | name: homepage 7 | namespace: homepage 8 | labels: 9 | app.kubernetes.io/name: homepage 10 | rules: 11 | - apiGroups: 12 | - "" 13 | resources: 14 | - namespaces 15 | - pods 16 | - nodes 17 | verbs: 18 | - get 19 | - list 20 | - apiGroups: 21 | - extensions 22 | - networking.k8s.io 23 | resources: 24 | - ingresses 25 | verbs: 26 | - get 27 | - list 28 | - apiGroups: 29 | - traefik.io 30 | resources: 31 | - ingressroutes 32 | verbs: 33 | - get 34 | - list 35 | - apiGroups: 36 | - gateway.networking.k8s.io 37 | resources: 38 | - httproutes 39 | - gateways 40 | verbs: 41 | - get 42 | - list 43 | - apiGroups: 44 | - metrics.k8s.io 45 | resources: 46 | - nodes 47 | - pods 48 | verbs: 49 | - get 50 | - list 51 | --- 52 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/clusterrolebinding.json 53 | apiVersion: rbac.authorization.k8s.io/v1 54 | kind: ClusterRoleBinding 55 | metadata: 56 | name: homepage 57 | namespace: homepage 58 | labels: 59 | app.kubernetes.io/name: homepage 60 | roleRef: 61 | apiGroup: rbac.authorization.k8s.io 62 | kind: ClusterRole 63 | name: homepage 64 | subjects: 65 | - kind: ServiceAccount 66 | name: homepage 67 | namespace: homepage 68 | -------------------------------------------------------------------------------- /kubernetes/actual/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/bjw-s/helm-charts/main/charts/other/app-template/schemas/helmrelease-helm-v2.schema.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: actual 7 | namespace: actual 8 | spec: 9 | interval: 1h 10 | chartRef: 11 | kind: OCIRepository 12 | name: app-template 13 | namespace: flux-system 14 | values: 15 | controllers: 16 | actual: 17 | containers: 18 | app: 19 | image: 20 | repository: ghcr.io/actualbudget/actual-server 21 | tag: 25.12.0 22 | ports: 23 | - containerPort: 5006 24 | env: 25 | - name: ACTUAL_PORT 26 | value: "5006" 27 | securityContext: 28 | allowPrivilegeEscalation: false 29 | capabilities: 30 | drop: ["ALL"] 31 | seccompProfile: 32 | type: RuntimeDefault 33 | probes: 34 | liveness: 35 | enabled: true 36 | custom: true 37 | spec: 38 | exec: 39 | command: 40 | - node 41 | - src/scripts/health-check.js 42 | initialDelaySeconds: 20 43 | periodSeconds: 60 44 | timeoutSeconds: 10 45 | failureThreshold: 3 46 | service: 47 | app: 48 | controller: actual 49 | ports: 50 | http: 51 | port: 5006 52 | persistence: 53 | data: 54 | existingClaim: actual 55 | -------------------------------------------------------------------------------- /kubernetes/monitoring/loki/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/helm.toolkit.fluxcd.io/helmrelease_v2.json 3 | # TODO: Investigate object storage options for persisted data e.g. minio 4 | apiVersion: helm.toolkit.fluxcd.io/v2 5 | kind: HelmRelease 6 | metadata: 7 | name: loki 8 | namespace: monitoring 9 | spec: 10 | chart: 11 | spec: 12 | chart: loki 13 | version: 6.49.0 14 | sourceRef: 15 | kind: HelmRepository 16 | name: loki 17 | interval: 1h 18 | timeout: 10m 19 | values: 20 | deploymentMode: SingleBinary 21 | loki: 22 | auth_enabled: false 23 | analytics: 24 | reporting_enabled: false 25 | server: 26 | log_level: info 27 | commonConfig: 28 | replication_factor: 1 29 | ingester: 30 | chunk_encoding: snappy 31 | limits_config: 32 | retention_period: 14d 33 | storage: 34 | type: filesystem 35 | schemaConfig: 36 | configs: 37 | - from: "2025-01-01" 38 | store: tsdb 39 | object_store: filesystem 40 | schema: v13 41 | index: 42 | prefix: loki_index_ 43 | period: 24h 44 | singleBinary: 45 | replicas: 1 46 | persistence: 47 | enabled: true 48 | size: 20Gi 49 | gateway: 50 | replicas: 0 51 | backend: 52 | replicas: 0 53 | read: 54 | replicas: 0 55 | write: 56 | replicas: 0 57 | chunksCache: 58 | enabled: false 59 | resultsCache: 60 | enabled: false 61 | lokiCanary: 62 | enabled: false 63 | test: 64 | enabled: false 65 | -------------------------------------------------------------------------------- /kubernetes/gitea/gitea.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | type: Opaque 5 | metadata: 6 | name: gitea-secrets 7 | namespace: gitea 8 | labels: 9 | app.kubernetes.io/name: gitea 10 | stringData: 11 | username: ENC[AES256_GCM,data:MVuktoI=,iv:7XRxg9hBVVD3gkELkOzUkUf9AGI4Rf1bN87dlxPYAS8=,tag:qoelmZGXwXHe7QBatNtiYw==,type:str] 12 | password: ENC[AES256_GCM,data:BoYvwbRmlB4dWrBD1pEV,iv:cKUHcK6KBCG5k+au5gHAGaVuAuDZdOmhh3TtJ2PWBIM=,tag:8GJfOVUnvG7OtvQ/YjKqdw==,type:str] 13 | email: ENC[AES256_GCM,data:TWsOtBKgnT+gDZkTM9I+ftMJH6wN,iv:zKhyqLQ3yTrKyyDWi6dUHfQBsd98Y/MfS+vrYFYdIcA=,tag:4WZTLe6pNCxgH3wNbveygA==,type:str] 14 | sops: 15 | age: 16 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 17 | enc: | 18 | -----BEGIN AGE ENCRYPTED FILE----- 19 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSNVBuRlFaMCtSWmZaUkll 20 | djZLZUd1TVpmdE9IakRVdVNmUzdlNmttYjNrCmxqZDJVbWdxMmFQeVdqbFlReW5k 21 | Y1VDd1BuRGc0OXJtOWJJTGpCR3l4K2sKLS0tIG12VWNzSzkzQkx0THRoaWV0cmRa 22 | RGJ1WkJ6SnA0MWl2RFZMUjZGV1l2VmcKTA9coWV6b360yupTqnhShg6qNzRa5vem 23 | NScVT+w/QVOv+Zi9wu2X1BGwzAslq2zNv/GmPssA/N1Yj3bSrxjxeg== 24 | -----END AGE ENCRYPTED FILE----- 25 | lastmodified: "2025-10-24T21:52:42Z" 26 | mac: ENC[AES256_GCM,data:UH2+Cc09DyngRRk7dbfNrNJFRoyM0H4U4jXJhNtR7qmtIXKtDng5lTp4BA/+BOHcNfNWK97dI1jt7tKVV75lNpwsdqoLD8rHx8PVPVgKlctSwXH3xbzW5HEtHZnJa3Bo7KJM7UQm0SrnWQMHEVPLdD6UrWpRCxxOyQoa47Vf9IM=,iv:6moGKo9XyDgPMAJ2uDG3wF+9Bt8wVX/eUp9AJsuwgyc=,tag:8wP5+SziyU41sK/SrUtTeA==,type:str] 27 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 28 | version: 3.11.0 29 | -------------------------------------------------------------------------------- /kubernetes/homepage/config/bookmarks.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://gethomepage.dev/configs/bookmarks/ 3 | - dev: 4 | - axis: 5 | - icon: si-github 6 | href: https://github.com/scottmckendry/axis 7 | - nix: 8 | - icon: si-github 9 | href: https://github.com/scottmckendry/nix 10 | - windots: 11 | - icon: si-github 12 | href: https://github.com/scottmckendry/windots 13 | - akahu-actual: 14 | - icon: si-github 15 | href: https://github.com/scottmckendry/akahu-actual 16 | - entertainment: 17 | - youtube: 18 | - icon: si-youtube 19 | href: https://youtube.com 20 | - twitch: 21 | - icon: si-twitch 22 | href: https://twitch.tv 23 | - imdb: 24 | - icon: si-imdb 25 | href: https://imdb.com 26 | - tmdb: 27 | - icon: si-themoviedatabase 28 | href: https://themoviedb.org 29 | - tools: 30 | - bitwarden: 31 | - icon: si-bitwarden 32 | href: https://vault.bitwarden.com 33 | - backblaze b2: 34 | - icon: si-backblaze 35 | href: https://secure.backblaze.com/b2_buckets.htm 36 | - renovate: 37 | - icon: si-renovate 38 | href: https://developer.mend.io/ 39 | - copilot chat: 40 | - icon: si-githubcopilot 41 | href: https://github.com/copilot/ 42 | 43 | - docs: 44 | - talos: 45 | - icon: si-talos 46 | href: https://talos.dev/docs/ 47 | - kubernetes: 48 | - icon: si-kubernetes 49 | href: https://kubernetes.io/docs/home/ 50 | - helm: 51 | - icon: si-helm 52 | href: https://helm.sh/docs/ 53 | - flux: 54 | - icon: si-flux 55 | href: https://fluxcd.io/docs/ 56 | -------------------------------------------------------------------------------- /kubernetes/media/recyclarr/config/recyclarr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/recyclarr/recyclarr/master/schemas/config-schema.json 3 | sonarr: 4 | sonarr: 5 | base_url: http://sonarr.media.svc.cluster.local 6 | api_key: !env_var SONARR_API_KEY 7 | delete_old_custom_formats: true 8 | replace_existing_custom_formats: true 9 | include: 10 | - template: sonarr-v4-quality-profile-web-1080p 11 | - template: sonarr-v4-custom-formats-web-1080p 12 | - template: sonarr-v4-quality-profile-web-2160p 13 | - template: sonarr-v4-custom-formats-web-2160p 14 | 15 | # Custom Formats: https://recyclarr.dev/wiki/yaml/config-reference/custom-formats/ 16 | custom_formats: 17 | - trash_ids: 18 | - 47435ece6b99a0b477caf360e79ba0bb # x265 (HD) 19 | - 9b64dff695c2115facf1b6ea59c9bd07 # x265 (no HDR/DV) 20 | assign_scores_to: 21 | - name: WEB-1080p 22 | score: 100 23 | - name: WEB-2160p 24 | score: 100 25 | 26 | radarr: 27 | radarr: 28 | base_url: http://radarr.media.svc.cluster.local 29 | api_key: !env_var RADARR_API_KEY 30 | 31 | include: 32 | - template: radarr-quality-profile-hd-bluray-web 33 | - template: radarr-custom-formats-hd-bluray-web 34 | - template: radarr-quality-profile-uhd-bluray-web 35 | - template: radarr-custom-formats-uhd-bluray-web 36 | 37 | custom_formats: 38 | - trash_ids: 39 | - dc98083864ea246d05a42df0d05f81cc # x265 (HD) 40 | - 839bea857ed2c0a8e084f3cbdbd65ecb # x265 (no HDR/DV) 41 | assign_scores_to: 42 | - name: HD Bluray + WEB 43 | score: 100 44 | - name: UHD Bluray + WEB 45 | score: 100 46 | -------------------------------------------------------------------------------- /.taskfiles/volsync.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://taskfile.dev/schema.json 3 | version: 3 4 | 5 | # Constraints: 6 | # 1. Replication source MUST be named "[namespace]-[pvc-name]". 7 | # 2. Restic repo secret MUST be named "restic-[namespace]-[pvc-name]". 8 | # 3. Backups in the environment variables below MUST be named "[namespace]_[pvc-name]", replacing ALL hypens with underscores. 9 | 10 | # backups are defined as environment variables. We check if they exist to ensure only valid names are used. 11 | # new backups should be added (and tested!) here. 12 | env: 13 | actual_actual: --name actual --namespace actual --manage-pods --runner-id 1000 14 | ccinvoice_ccinvoice: --name ccinvoice --namespace ccinvoice --manage-pods --runner-id 1000 15 | gitea_gitea: --name gitea --namespace gitea --manage-pods --runner-id 1000 16 | home_assistant_home_assistant: --name home-assistant --namespace home-assistant --manage-pods --runner-id 1000 17 | media_prowlarr: --name prowlarr --namespace media --manage-pods --runner-id 1000 18 | media_radarr: --name radarr --namespace media --manage-pods --runner-id 1000 19 | media_sonarr: --name sonarr --namespace media --manage-pods --runner-id 1000 20 | 21 | # TODO: add manual backup task/s that can be run on demand. 22 | tasks: 23 | restore-*: 24 | desc: "Restore a PVC from a restic backup. example: task restore-ccinvoice" 25 | vars: 26 | NAME: "{{index .MATCH 0}}" 27 | preconditions: 28 | - sh: '[ "${{.NAME}}" != "" ]' # check if NAME env var is not empty 29 | cmds: 30 | - cmd: ./scripts/restore.sh {{.CLI_ARGS}} ${{.NAME}} 31 | 32 | interactive-restore: 33 | desc: "Interactive restore for PVCs. Pick a namespace and restore point" 34 | interactive: true 35 | cmds: 36 | - cmd: ./scripts/pick-backup.sh 37 | -------------------------------------------------------------------------------- /kubernetes/media/recyclarr/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/bjw-s/helm-charts/main/charts/other/app-template/schemas/helmrelease-helm-v2.schema.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: recyclarr 7 | namespace: media 8 | spec: 9 | interval: 1h 10 | chartRef: 11 | kind: OCIRepository 12 | name: app-template 13 | namespace: flux-system 14 | values: 15 | controllers: 16 | recyclarr: 17 | type: cronjob 18 | cronjob: 19 | schedule: 0 0 * * * 20 | backoffLimit: 0 21 | concurrencyPolicy: Forbid 22 | successfulJobsHistory: 1 23 | failedJobsHistory: 1 24 | ttlSecondsAfterFinished: 86400 25 | containers: 26 | app: 27 | image: 28 | repository: ghcr.io/recyclarr/recyclarr 29 | tag: 7.5.2@sha256:2550848d43a453f2c6adf3582f2198ac719f76670691d76de0819053103ef2fb 30 | envFrom: 31 | - secretRef: 32 | name: recyclarr-secret 33 | args: ["sync"] 34 | securityContext: 35 | allowPrivilegeEscalation: false 36 | readOnlyRootFilesystem: true 37 | capabilities: { drop: ["ALL"] } 38 | pod: 39 | restartPolicy: Never 40 | defaultPodOptions: 41 | securityContext: 42 | runAsNonRoot: true 43 | runAsUser: 1000 44 | runAsGroup: 1000 45 | fsGroup: 1000 46 | fsGroupChangePolicy: OnRootMismatch 47 | persistence: 48 | config-file: 49 | type: configMap 50 | name: recyclarr-configmap 51 | globalMounts: 52 | - path: /config/recyclarr.yml 53 | subPath: recyclarr.yml 54 | readOnly: true 55 | -------------------------------------------------------------------------------- /kubernetes/gitea/backup/backblaze.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | type: Opaque 5 | metadata: 6 | name: restic-gitea-gitea 7 | namespace: gitea 8 | stringData: 9 | RESTIC_REPOSITORY: ENC[AES256_GCM,data:HXWOGFj5SDQFrb3MfOHsWafG0X+cA9zfm706AiO0gEsRIkiOs4lhj7j1G2muNUXwbevy4L0BXHL8FA==,iv:uakX1LNy/BZCjjeP4GQJyq+U2sHQhQzwOcsoTjcj40I=,tag:dgVCvnc8Nq1JwCQxvT8WTw==,type:str] 10 | RESTIC_PASSWORD: ENC[AES256_GCM,data:bv7LNSm9i8fw+wnjVc+xLmwQUE4x3OZJh2FNkRHQI3A=,iv:Cy5f2sgl16dtEbu5BnqPVc3Ua+6zzqdXn/EKJA/cMi0=,tag:ABsdunwJB1VUwZnXm923WQ==,type:str] 11 | AWS_ACCESS_KEY_ID: ENC[AES256_GCM,data:4SqElR3swZ8ktaRBx4hVLHQzSzY041OUCw==,iv:hFPiFQimogmHBBw2btcBIto9yVt9hXuzuQSSW2CbxNM=,tag:R11S8209CVkvY1Thui1B8A==,type:str] 12 | AWS_SECRET_ACCESS_KEY: ENC[AES256_GCM,data:W1tMZGr7lIZtMFbg5RaKPyhPaqNPq5DOH2SsOJu1rw==,iv:n72acdCEN/cV1R4PQRFli6XOavmELH25Zc44B1wi41U=,tag:ZEQUwVZnmz1ENtsFMWlitQ==,type:str] 13 | sops: 14 | age: 15 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 16 | enc: | 17 | -----BEGIN AGE ENCRYPTED FILE----- 18 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArWlVnOWdFTkJHOEpjQ202 19 | RzJ2WERjbWNRZ2wvWFY0dFpPT0NvU1dWYXo4Cm1saGE5QTljazhVcTFOWHZSdmR4 20 | aVJKa2taVi9vOTZXamxJVTlKZFl1UTAKLS0tIHpJL2RyNXl3YUEyL3g1amdZSFJE 21 | R1BuTllHUE5xazlFWlBDVE9iNWp1aUkKYMejWAdBEOdxAL0aa0xcBdS91y86o1jw 22 | HwDQiov54MvTaLdLyixJMyJ2l3WmRSksi1+yb7X4+eXPhQSfEp6mRQ== 23 | -----END AGE ENCRYPTED FILE----- 24 | lastmodified: "2025-10-24T23:40:38Z" 25 | mac: ENC[AES256_GCM,data:QXPZVzFy5XFNk4EFQgk6ELoTdh7I/GucQBi80zu9Lz5bbe9C+UO/e4E5k8cWnglke19QHcdhWpMISMF4sh5cJASHHEfaSw07Uhcz/a1gbEbxzIIBKfgh9PNq9MQ6OshArJyXZTJMDfYBNPjAwzTb4ELrePGC9+8Z7NKE1Kz7iCg=,iv:p6KxYz/cWizRBtmHOysy+/iuvD2WDxWYm2dSa4bAKyI=,tag:x07ThGhkGD+DWWGhs1hGtQ==,type:str] 26 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 27 | version: 3.11.0 28 | -------------------------------------------------------------------------------- /kubernetes/media/radarr/backup/backblaze.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | type: Opaque 5 | metadata: 6 | name: restic-media-radarr 7 | namespace: media 8 | stringData: 9 | RESTIC_REPOSITORY: ENC[AES256_GCM,data:02VMG8SctYiE4rWupmjzXuWNpxJAdwuA/44vFQGfLRM7gfwH+FjpdEwdIUS4QxxY2ac7dvuZyMU47Yg=,iv:TVUCkyvzWDQaubQjGhfM4saV6kLsOyCPzcqigHKZlTs=,tag:5NWfnqbnI3gS/FYNjejLgQ==,type:str] 10 | RESTIC_PASSWORD: ENC[AES256_GCM,data:0Zo0cuvZJyEgRFgm79+8TXHkrpCpEh+1DZI6CDZnmXM=,iv:8IIUz1Un65bExOA3NypnL1jwfkUsKxzvoorYs4uuFLE=,tag:vDJ7JBKb4sxrJXY1e3wdRw==,type:str] 11 | AWS_ACCESS_KEY_ID: ENC[AES256_GCM,data:fpwPolvqKd1mOCU+ZcdlDImxflluZ7SxxA==,iv:yq+whk1tWxYmuhyWsTnpya1ofbDblM5ndUpZZYVz1R4=,tag:CxlOLkuJQN2vbdotPOg7Dg==,type:str] 12 | AWS_SECRET_ACCESS_KEY: ENC[AES256_GCM,data:fGrMP4lggNGNXxrSn2jo0odFchGlLA4Z+vYKu5dfUw==,iv:YPuVr+Hd3eLfWTXp8MNnDyKLY7KXygJXcMzrGaRFK+U=,tag:HmxGlafyTX54fSn/nh36sA==,type:str] 13 | sops: 14 | age: 15 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 16 | enc: | 17 | -----BEGIN AGE ENCRYPTED FILE----- 18 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiYlJWOHBUSGZxNDlBNzFF 19 | cys0KzhDa1QwdUpQa0RxejZYRGQzYzVIOVNjCm4zWXdZNEkyRkE0Qjc0cy9uL1F1 20 | M0oxcWhTV3BPdVJiQjVKdmJ6YWUzalkKLS0tIG1sNExpUlJhbllQL3FlQmVBdmg4 21 | MURyWU1ybUZ1QWRIRE1aUXltbXBicFUKq2R/aEOEqvkinylIjYc4RiI8xOSes0te 22 | BjB8wRbsrcgfCOY6OPBKH1unwMSTYoDDPyzKJBvenljvmQeabmMk6A== 23 | -----END AGE ENCRYPTED FILE----- 24 | lastmodified: "2025-05-07T07:46:32Z" 25 | mac: ENC[AES256_GCM,data:OjXdK6LvCsAfBOVwaSL9/ERiMLkq6auUMnClZLfFnLalqBx504uzTMyDtjmQSLTcGYQBcp9+adECTLqEDCY0Z1Kbji0Ts3vje3qZ7Sc2I7E6CyyX04bYevlLocb57lJ23LdzHTY9LBFIEIASKQJ9hoiv6avJg4ZLTgEVkvqX2Wg=,iv:6LHLUa97q/qETX9n70aaWKSJuBOc146o3WbqBE8hm2Q=,tag:5Pc11Zj2/pq8cEkfOFW+Rw==,type:str] 26 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 27 | version: 3.10.2 28 | -------------------------------------------------------------------------------- /kubernetes/media/sonarr/backup/backblaze.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | type: Opaque 5 | metadata: 6 | name: restic-media-sonarr 7 | namespace: media 8 | stringData: 9 | RESTIC_REPOSITORY: ENC[AES256_GCM,data:/JieuI31g8dBh+q7khHT1OjqNiDyjWdWPH7UGueFeJAqBB7kqAVCUSFLVZdwcfnbNI/KIZjMdRRn4Aw=,iv:a7wBm4fHKXsdO/wzxPcZMx+jfcDBBY4pzAAscMjYeYM=,tag:Z/RarFb66VGgX3Sde6hRYA==,type:str] 10 | RESTIC_PASSWORD: ENC[AES256_GCM,data:HKAjp7PgXbFKRAlnze0+u9RMwWPM61ec8yyykid6mkU=,iv:pri9Ft3teieZ+a4g9qlQV70/mKGWhA1S/iab3wXsKfM=,tag:tWHhrgA/37mYJq1P4mrnUA==,type:str] 11 | AWS_ACCESS_KEY_ID: ENC[AES256_GCM,data:Cn8Tb5kVxpfI524NaLVJNLByWvuMHzYUpA==,iv:2srLvNJn0mYOIHf+9NjSl/Jj6QhUZYWQGy0EWiCp7lk=,tag:vXzdpbuxLUvLEYPkonsfmg==,type:str] 12 | AWS_SECRET_ACCESS_KEY: ENC[AES256_GCM,data:b6wHky5lvWx8ASTAj9YPpXLt5Ynli13L6NQzRzh/Gw==,iv:kE9oyw8BmcUK13DEodM40N0EpHDDQLGAtxG9hRhF5Uo=,tag:5Nkuf44anoW0vycRR8RuEA==,type:str] 13 | sops: 14 | age: 15 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 16 | enc: | 17 | -----BEGIN AGE ENCRYPTED FILE----- 18 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRQkhkRW56T2dUalE3bFpG 19 | bXVwT2g4WU8xU2IycVZuMXVMbkFPaU1oY2lvClk2R25nQjBaeGp1REJWb0dwYk5P 20 | cUJGMVRKSjBxMWpQYmNaK3FsMERJOEEKLS0tIGF3eTg0eTBIMUgyTHRveWM5eEVB 21 | QWUyQlJYZ3RVTnE4cFRhbXdocmdFUFkK+omZTB57pQBUoZNLLS9BCzVhuBdW7pB8 22 | ZjvHfnDdRofOFs/zxfe9dy53GJ7pI3BP00Hj5cvR7orAVWR3tde7hw== 23 | -----END AGE ENCRYPTED FILE----- 24 | lastmodified: "2025-05-05T19:23:17Z" 25 | mac: ENC[AES256_GCM,data:vBBt5zCVALGGsefpFTs6qp4nJkINWvPz0h5V2qfpcIeUprb5TUd4aIyDOKZZJR86mPZgNikJrahlAvw1Z4/069XjBIYaCTQV0/GpwV3vZvG49LoLTm4ipsQQm6WeC8Jc1aOIXiP7D7+uObtJQWxg+W2J5a5LAdSRCUnaMgeexy8=,iv:EKW+K9ojIAqxyoi/DltYwWVtZNbx16QwFboKqb4N9Do=,tag:1EYX3U5CjpcgrmN49iae4Q==,type:str] 26 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 27 | version: 3.10.2 28 | -------------------------------------------------------------------------------- /kubernetes/media/prowlarr/backup/backblaze.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | type: Opaque 5 | metadata: 6 | name: restic-media-prowlarr 7 | namespace: media 8 | stringData: 9 | RESTIC_REPOSITORY: ENC[AES256_GCM,data:CXR2D8yF1Vdqw3F5buizHP5/7HaryMbInFVfT5m3/AlWcOd0typOIo8gxi3C01KWRaikZU0hdSPNeUCWwQ==,iv:Imk9lYobEhOUb9lGa4v+5J+HEtGUKJ+L9lTwVMM6vTw=,tag:Omtk/r5O0oOxoSilYiF23g==,type:str] 10 | RESTIC_PASSWORD: ENC[AES256_GCM,data:L+oxZOig1J5QIKMwgGpsvUccu/onc8TbrGAsq9QpzTs=,iv:65N2cjHlH4QBpeOjr0lle4To6RozqsvDCvYapjLmVTk=,tag:m64TsTTgkEu2RyiRsTuhUg==,type:str] 11 | AWS_ACCESS_KEY_ID: ENC[AES256_GCM,data:HInpIMrhmSi/8HQ+sdnZ09T0avR2+mgzmw==,iv:0MxFvx8uZ1aH8PCunT1mUJKGk1OBhrNHUr02rCk+D48=,tag:+P17WZTxqQHn2yf+ZMqXow==,type:str] 12 | AWS_SECRET_ACCESS_KEY: ENC[AES256_GCM,data:9d98lhNAKL9w+YNbFfuYviA161hRV51CesmRrxDnIA==,iv:hq21ytptPSWxA2UQ3OGZ1HAuPBK5ocl2Upw2qcPgrYQ=,tag:dUjHANQnZL6xsYJv6ehIUg==,type:str] 13 | sops: 14 | age: 15 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 16 | enc: | 17 | -----BEGIN AGE ENCRYPTED FILE----- 18 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxVTN4dExtZHBGZ0dwaDlu 19 | N253WFY1QjE4V3M0SlNsNWhQcEh1MWJpQlJrClpBZU1naGVzN3kwNmVUaHRMWjBL 20 | TTBReVVHWGZXOFNoR29ETi83SnUxUTQKLS0tIGdZOWUxV3F0dmpnckJTODlySEcy 21 | dmNGSWNLdUEvemcrUTF3aUVXdXBaQTgK57ZWqvJXH50qYyEVXKL9M2ofRYyOHz5x 22 | 3zKRt8GnPWrahEQ74no9D6/QtTH702EMk35gaNFwKZDHSVq7lUeBvg== 23 | -----END AGE ENCRYPTED FILE----- 24 | lastmodified: "2025-05-05T19:23:17Z" 25 | mac: ENC[AES256_GCM,data:ogcbM3v4m8KZ3qRp8FgpP5KkXSKoxFPiUdlOKX4cqxl/tYoPiMBTsTSFLttgG4mtYkwaiptAM622iHyXsvEE9rWfXdm2obLhVcnD8z3aksXDbWlEpXKarbe2W7DI69gFYKAoNVaBWv5jyjdOt8NhwbHts4spcyzWmfWdFlvIsms=,iv:JBp+fCOxxKvxZhkLoA3KlJjQLUK/mqk5LVZ+CYmHLiE=,tag:Vq2T8o45BKiav+Gs+WS0dg==,type:str] 26 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 27 | version: 3.10.2 28 | -------------------------------------------------------------------------------- /kubernetes/media/radarr/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/bjw-s/helm-charts/main/charts/other/app-template/schemas/helmrelease-helm-v2.schema.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: radarr 7 | namespace: media 8 | spec: 9 | interval: 1h 10 | chartRef: 11 | kind: OCIRepository 12 | name: app-template 13 | namespace: flux-system 14 | values: 15 | controllers: 16 | radarr: 17 | containers: 18 | app: 19 | image: 20 | repository: ghcr.io/home-operations/radarr 21 | tag: 6.1.0.10309@sha256:10f94c85f605d42e5e78de928f24758b5750a4e99eee8d0ef9ff150f7a2df033 22 | env: 23 | RADARR__APP__INSTANCENAME: Radarr 24 | RADARR__APP__THEME: dark 25 | RADARR__AUTH__METHOD: External 26 | RADARR__AUTH__REQUIRED: DisabledForLocalAddresses 27 | RADARR__LOG__DBENABLED: "False" 28 | RADARR__LOG__LEVEL: info 29 | RADARR__SERVER__PORT: 80 30 | RADARR__UPDATE__BRANCH: develop 31 | TZ: Pacific/Auckland 32 | securityContext: 33 | allowPrivilegeEscalation: false 34 | readOnlyRootFilesystem: true 35 | capabilities: { drop: ["ALL"] } 36 | defaultPodOptions: 37 | securityContext: 38 | runAsNonRoot: true 39 | runAsUser: 1000 40 | runAsGroup: 1000 41 | fsGroup: 1000 42 | fsGroupChangePolicy: OnRootMismatch 43 | service: 44 | app: 45 | controller: radarr 46 | ports: 47 | http: 48 | port: 80 49 | persistence: 50 | config: 51 | existingClaim: radarr 52 | config-cache: 53 | existingClaim: radarr-cache 54 | globalMounts: 55 | - path: /config/MediaCover 56 | media: 57 | type: nfs 58 | server: 10.0.6.1 59 | path: /mnt/TrueBlue/media 60 | globalMounts: 61 | - path: /media 62 | -------------------------------------------------------------------------------- /kubernetes/media/sonarr/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/bjw-s/helm-charts/main/charts/other/app-template/schemas/helmrelease-helm-v2.schema.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: sonarr 7 | namespace: media 8 | spec: 9 | interval: 1h 10 | chartRef: 11 | kind: OCIRepository 12 | name: app-template 13 | namespace: flux-system 14 | values: 15 | controllers: 16 | sonarr: 17 | containers: 18 | app: 19 | image: 20 | repository: ghcr.io/home-operations/sonarr 21 | tag: 4.0.16.2943@sha256:0a1b8f5cb5b072f7ad8c6ff64a25dc512115d1a114ecf19a58a3b4f870207ddd 22 | env: 23 | SONARR__APP__INSTANCENAME: Sonarr 24 | SONARR__APP__THEME: dark 25 | SONARR__AUTH__METHOD: External 26 | SONARR__AUTH__REQUIRED: DisabledForLocalAddresses 27 | SONARR__LOG__DBENABLED: "False" 28 | SONARR__LOG__LEVEL: info 29 | SONARR__SERVER__PORT: 80 30 | SONARR__UPDATE__BRANCH: develop 31 | TZ: Pacific/Auckland 32 | securityContext: 33 | allowPrivilegeEscalation: false 34 | readOnlyRootFilesystem: true 35 | capabilities: { drop: ["ALL"] } 36 | defaultPodOptions: 37 | securityContext: 38 | runAsNonRoot: true 39 | runAsUser: 1000 40 | runAsGroup: 1000 41 | fsGroup: 1000 42 | fsGroupChangePolicy: OnRootMismatch 43 | service: 44 | app: 45 | controller: sonarr 46 | ports: 47 | http: 48 | port: 80 49 | persistence: 50 | config: 51 | existingClaim: sonarr 52 | config-cache: 53 | existingClaim: sonarr-cache 54 | globalMounts: 55 | - path: /config/MediaCover 56 | media: 57 | type: nfs 58 | server: 10.0.6.1 59 | path: /mnt/TrueBlue/media 60 | globalMounts: 61 | - path: /media 62 | -------------------------------------------------------------------------------- /kubernetes/actual/backup/backblaze.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | type: Opaque 5 | metadata: 6 | name: restic-actual-actual 7 | namespace: actual 8 | stringData: 9 | RESTIC_REPOSITORY: ENC[AES256_GCM,data:JP0iym8/xDOMX1aivRjVd+f7TMzq4KWRcw7f5VQUSJV9HgkMZ7sQBF+j1G8YMksGtP/qSAVJTPnEp5Hn,iv:CASrJig2mBFArGtw56P1fIPmtvKqjdSSQMMzwOPQOc4=,tag:SXwUF8TW9vItPmwW8o1r0Q==,type:str] 10 | RESTIC_PASSWORD: ENC[AES256_GCM,data:cpJJVBE7KQaND5+zdr4mR3h1352mZbX4ee1HwMV/Fv8=,iv:eQRi/cQm9SOuqk4QwAT8joFBv1jNs5I69+ScIUjRoCI=,tag:oWvW8i/AEugV0AlzesQOew==,type:str] 11 | AWS_ACCESS_KEY_ID: ENC[AES256_GCM,data:uhP4MxZD8nQI0BgNbugqc2jlzu7x/Zqk+Q==,iv:YI4/V7ihYhY8QTUFsZsdQ3Z1kAhQXtDGEx1XXH8whDk=,tag:g+D4wTvuENjjn6XhT+8ZnQ==,type:str] 12 | AWS_SECRET_ACCESS_KEY: ENC[AES256_GCM,data:bqH5qaByO/u4zqYd5VmF7RyT8ZmjupkuLZBHRXAOfg==,iv:NKWSZJXUmlrbc0wcqP86otv9S151+/XPvJDbm4cYkp0=,tag:RC5iXms13W6iIxnHXCNEDQ==,type:str] 13 | sops: 14 | kms: [] 15 | gcp_kms: [] 16 | azure_kv: [] 17 | hc_vault: [] 18 | age: 19 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 20 | enc: | 21 | -----BEGIN AGE ENCRYPTED FILE----- 22 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKdGppS3UzbCtWTnJYckZi 23 | SlFpbVBIcDJscnNOSngzUWtIWXV5azBlTVNFCjlXZWc2dWErR0E1a3B5Rm1GQk1C 24 | UGtMWVRjQmJqRThhVWlkczI0ZndZYTAKLS0tIHVYZGxNaGc3MXZHYXUyN2Q2b0l3 25 | dndFSWNiZElOZHZESzBWKzJNZVl1S0kKRBpKM7i0ooEh7snaqvjKg/8EfdYis93U 26 | U7MPd3fIdHKtsz40P+YXcEDiGv1ScN4FRXnS/5tlCSavpxfzRLKYlg== 27 | -----END AGE ENCRYPTED FILE----- 28 | lastmodified: "2025-05-06T06:44:25Z" 29 | mac: ENC[AES256_GCM,data:rj7M32WwglybtLzo7qrbLNp7qyEDIecNLltju/WwAeqqYda5zyf3StEpTO0Hg360k5XrCZ51xKs5wQZ0/jAsLnC8WNgfbm2b/v+tKc42KA2+XTZVBkZ+8Pc+qfa9jz970Bl1gZNgnHF6lD21yXknBTYtdZ6UWwib/tJk9GF/HUE=,iv:KFl9jLL4FFSXt11y2IeNhXefb78Ui1HeBOf0ZunuCgs=,tag:NB7dgjHFqYSXrN2bagaPcQ==,type:str] 30 | pgp: [] 31 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 32 | version: 3.9.4 33 | -------------------------------------------------------------------------------- /kubernetes/monitoring/kube-prometheus-stack/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/helm.toolkit.fluxcd.io/helmrelease_v2.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: kube-prometheus-stack 7 | namespace: monitoring 8 | spec: 9 | interval: 1h 10 | chartRef: 11 | kind: OCIRepository 12 | name: kube-prometheus-stack 13 | values: 14 | # https://github.com/prometheus-community/helm-charts/blob/main/charts/kube-prometheus-stack/values.yaml 15 | namespaceOverride: monitoring 16 | 17 | defaultRules: 18 | rules: 19 | kubeControllerManager: false 20 | kubeSchedulerAlerting: false 21 | kubelet: false 22 | disabled: 23 | KubeletPodStartUpLatencyHigh: true 24 | CPUThrottlingHigh: true 25 | 26 | alertmanager: 27 | alertmanagerSpec: 28 | externalUrl: https://am.axis.scottmckendry.tech 29 | replicas: 3 30 | alertmanagerConfiguration: 31 | name: alertmanager 32 | global: 33 | resolveTimeout: 5m 34 | 35 | grafana: 36 | enabled: false 37 | forceDeployDashboards: true 38 | 39 | prometheus: 40 | prometheusSpec: 41 | externalUrl: https://prometheus.axis.scottmckendry.tech 42 | podMonitorSelectorNilUsesHelmValues: false 43 | probeSelectorNilUsesHelmValues: false 44 | ruleSelectorNilUsesHelmValues: false 45 | scrapeConfigSelectorNilUsesHelmValues: false 46 | serviceMonitorSelectorNilUsesHelmValues: false 47 | storageSpec: 48 | volumeClaimTemplate: 49 | spec: 50 | resources: 51 | requests: 52 | storage: 10Gi 53 | 54 | prometheusOperator: 55 | kubeletService: 56 | enabled: false 57 | 58 | kubeEtcd: 59 | service: 60 | selector: 61 | component: kube-apiserver 62 | 63 | kubeProxy: 64 | enabled: false 65 | kubeControllerManager: 66 | enabled: false 67 | kubeScheduler: 68 | enabled: false 69 | -------------------------------------------------------------------------------- /kubernetes/ccinvoice/backup/backblaze.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | type: Opaque 5 | metadata: 6 | name: restic-ccinvoice-ccinvoice 7 | namespace: ccinvoice 8 | stringData: 9 | RESTIC_REPOSITORY: ENC[AES256_GCM,data:LcJHV5kRf5+w/AWoNUeSUjFVgrx8ZiP1YvCHZgqsQrGwNoXYytmPIY0ENI7q6flPGHIbk8YoueDAJGAm6MNOPFtu,iv:lIunLI6p0hsELq3rCByC1t6qVnA38pgOyWSAVXAJGFI=,tag:bLrPs449W2MCEsQBhYklBw==,type:str] 10 | RESTIC_PASSWORD: ENC[AES256_GCM,data:6tPA918YTE2vhah4Rg4hqAYXkKfzusEP3extF3j2BF8=,iv:fE9mo0yedEtyNx/Ufe79b1mFSVkcWggdYOf7avYtybY=,tag:RqKFEBj/QAr4DCvOhgvOfA==,type:str] 11 | AWS_ACCESS_KEY_ID: ENC[AES256_GCM,data:RS5Tm+FL3fH30MwW9ga5Il/u/506fkEEAg==,iv:guePuq/xWkBP400fHMvqdY2HJD4DE7LOUzWcX7Xj9pE=,tag:ZkxDg3RJ/LDbL0cP0YgFjQ==,type:str] 12 | AWS_SECRET_ACCESS_KEY: ENC[AES256_GCM,data:/leEx/FDAvfhNaWOxQNaM5Mtv+Jbvp+kdsvYLuq5uQ==,iv:CSG9L/uAB2b8JvwX66qaeyw/G8ilrJjyKFJh08oGicE=,tag:DM1nufX5QoAHTT84TWDJaQ==,type:str] 13 | sops: 14 | kms: [] 15 | gcp_kms: [] 16 | azure_kv: [] 17 | hc_vault: [] 18 | age: 19 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 20 | enc: | 21 | -----BEGIN AGE ENCRYPTED FILE----- 22 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1RWprVnFXeFNOcUE1V3FH 23 | ZFd3UTUwWVBIaWZ4NDdsZlAzZW9RRGZRQlhjCkFxL3BQaFBCTGxHZU1zTUJhb3I3 24 | bTdUT25mNG43YndJNy9WWWRvUHlROWsKLS0tIFp4TGVSTlpIeFRUSUxQUStGaHVI 25 | TGVwZ3lGUzVCdFpBWm44WEkrSE1HWm8K7igmamO7bJPa/y1BjfJEjEsD5PHjwRLA 26 | q8LJDlTlUw6NMFcgKDPRewcBZZ7LE07C3BfXqPydyjYvdC224nTryg== 27 | -----END AGE ENCRYPTED FILE----- 28 | lastmodified: "2025-05-05T17:55:07Z" 29 | mac: ENC[AES256_GCM,data:rs4rtzdb1ihziWfTfG1BpONzbGNLe5bARfHGtYONJS04u7FPJmGMCsERMuyV+fLJlPUzkRX8UaEU7YC8lChPy3WCNMrsTbDMfkDhB7Wxu6aQgBNkmE93WnCPJLFhf9inZZ4hDcB7DsXdyIMhI7ATOS1t220eF3UQnG4EhlgifuA=,iv:zWseDP/iM5s1yp5So4WNKEmopGnUKnRf/3F4ExTt1Xo=,tag:rRaRzPzZ2UAXpFF40yc+yg==,type:str] 30 | pgp: [] 31 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 32 | version: 3.9.4 33 | -------------------------------------------------------------------------------- /kubernetes/home-assistant/backup/backblaze.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | type: Opaque 5 | metadata: 6 | name: restic-home-assistant-home-assistant 7 | namespace: home-assistant 8 | stringData: 9 | RESTIC_REPOSITORY: ENC[AES256_GCM,data:kalc4/rolNfc6Z7Pwi/ZYCg73E9Y9G6Ql8u9XBWIHbYaClhLCacrHU2nAdNSySzuNKocX6ZiaCnE1m3xfxvjWJ48IQPQv+tvpu0Uhw==,iv:mPDco+Y+QF7qgRkiLx+I1408WYL8jsEDZKF7CVNINF4=,tag:y+WIRVqy1TCeYfWJ99S+hQ==,type:str] 10 | RESTIC_PASSWORD: ENC[AES256_GCM,data:EMlmaomWugBoUw8HJuto5e+6hkmM8gW+Xy3Csd3bDTU=,iv:5tEIVEtEnXe/Ko5yfgCeZhClwhYIkV6H3R1DQ+d4Mqk=,tag:f70SfYKiIX5Dh1yxNWhNVQ==,type:str] 11 | AWS_ACCESS_KEY_ID: ENC[AES256_GCM,data:cSfePeQdXJt5wRcs1+pFuPRSMN3IVgCpIg==,iv:DX8PJkb8F8c/hKiUdrc3toqCdCV9kYkeEpVSSX4xOho=,tag:KphhuVaqmb9w6e5/9/dvWw==,type:str] 12 | AWS_SECRET_ACCESS_KEY: ENC[AES256_GCM,data:R/FLAetPFsO/wJl75vDn4LtTb6ym5Z1iZQq6xanmhg==,iv:oKL2rKEWfX4JSWhFP1fxEEY0C0YX3Wzf5R2Q6Uwb63E=,tag:p/ZFeiTStY4Edb8wBqTs9A==,type:str] 13 | sops: 14 | kms: [] 15 | gcp_kms: [] 16 | azure_kv: [] 17 | hc_vault: [] 18 | age: 19 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 20 | enc: | 21 | -----BEGIN AGE ENCRYPTED FILE----- 22 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEU2tFTlAzUHpuZXcwSklB 23 | WGZlNnpiUURRVStSNDhGZHdxRTU4T2ZyblZvCmhCWWpSWVh4NWNqV3hQOXI1anJw 24 | SmU5WGd2ZmdkZmw5WmtyYnJZNHVBdkEKLS0tIGExNjNyNXk2enVhMHhnaEdMaCtr 25 | eFNUdmE2ZnByb3E1VWNkRXBGV1hobVEK7xZAP8B9Zspz65AQc1RXT8J93Ss+ynr9 26 | YSxNdLnMzRtiB+HHLkrufk76jztoDg4j+6mfw+jQJ0QO/zP+dE1e6Q== 27 | -----END AGE ENCRYPTED FILE----- 28 | lastmodified: "2025-05-05T17:55:07Z" 29 | mac: ENC[AES256_GCM,data:URLuhdJZbBwxPHKZBMA3CcQnxm4sdgMDvTfGBKgk5OgYwICy9Yhg51X6BggOxvPro8X5NimsnDisaTJ11f50XTHYk53xJoqqkW2XzRHjK2j1ZcV66iGiMp+yr2KflY8uGZclY0WnmzO2A0G8gDJAgaHOzaFNxYwrVUvNl5UqHA8=,iv:TkBAqxnzHS7ztsHdluS50n9Iee3NIjztdyLO7cogIPU=,tag:fF/c9yh/6wRNcYnUj/4RMg==,type:str] 30 | pgp: [] 31 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 32 | version: 3.9.4 33 | -------------------------------------------------------------------------------- /kubernetes/media/qbittorrent/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/bjw-s/helm-charts/main/charts/other/app-template/schemas/helmrelease-helm-v2.schema.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: qbittorrent 7 | namespace: media 8 | spec: 9 | interval: 1h 10 | chartRef: 11 | kind: OCIRepository 12 | name: app-template 13 | namespace: flux-system 14 | values: 15 | controllers: 16 | qbittorrent: 17 | containers: 18 | app: 19 | image: 20 | repository: ghcr.io/home-operations/qbittorrent 21 | tag: 5.1.4@sha256:25fc7caf22101f85276ede9b1f76a46b7a93971bb6b70b6c58d14d6ab7f51415 22 | env: 23 | TZ: Pacific/Auckland 24 | QBT_WEBUI_PORT: 80 25 | QBT_TORRENTING_PORT: 50413 26 | probes: 27 | liveness: 28 | enabled: true 29 | readiness: 30 | enabled: true 31 | startup: 32 | enabled: true 33 | spec: 34 | failureThreshold: 30 35 | periodSeconds: 10 36 | securityContext: 37 | allowPrivilegeEscalation: false 38 | readOnlyRootFilesystem: true 39 | capabilities: { drop: ["ALL"] } 40 | defaultPodOptions: 41 | securityContext: 42 | runAsNonRoot: true 43 | runAsUser: 1000 44 | runAsGroup: 1000 45 | fsGroup: 1000 46 | fsGroupChangePolicy: OnRootMismatch 47 | service: 48 | app: 49 | controller: qbittorrent 50 | ports: 51 | http: 52 | primary: true 53 | port: 80 54 | bittorrent: 55 | enabled: true 56 | port: 50413 57 | protocol: TCP 58 | persistence: 59 | config: 60 | existingClaim: qbittorrent 61 | downloads: 62 | type: nfs 63 | server: 10.0.6.1 64 | path: /mnt/TrueBlue/media 65 | globalMounts: 66 | - path: /media/Downloads 67 | subPath: Downloads 68 | -------------------------------------------------------------------------------- /kubernetes/homepage/deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/deployment.json 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: homepage 7 | namespace: homepage 8 | labels: 9 | app.kubernetes.io/name: homepage 10 | spec: 11 | replicas: 1 12 | selector: 13 | matchLabels: 14 | app.kubernetes.io/name: homepage 15 | template: 16 | metadata: 17 | labels: 18 | app.kubernetes.io/name: homepage 19 | spec: 20 | serviceAccountName: homepage 21 | automountServiceAccountToken: true 22 | dnsPolicy: ClusterFirst 23 | enableServiceLinks: true 24 | containers: 25 | - name: homepage 26 | image: ghcr.io/gethomepage/homepage:v1.8.0@sha256:7dc099d5c6ec7fc945d858218565925b01ff8a60bcbfda990fc680a8b5cd0b6e 27 | imagePullPolicy: Always 28 | env: 29 | - name: HOMEPAGE_ALLOWED_HOSTS 30 | value: axis.scottmckendry.tech 31 | ports: 32 | - name: http 33 | containerPort: 3000 34 | protocol: TCP 35 | volumeMounts: 36 | - mountPath: /app/config/bookmarks.yaml 37 | name: homepage-config 38 | subPath: bookmarks.yaml 39 | - mountPath: /app/config/kubernetes.yaml 40 | name: homepage-config 41 | subPath: kubernetes.yaml 42 | - mountPath: /app/config/services.yaml 43 | name: homepage-config 44 | subPath: services.yaml 45 | - mountPath: /app/config/settings.yaml 46 | name: homepage-config 47 | subPath: settings.yaml 48 | - mountPath: /app/config/widgets.yaml 49 | name: homepage-config 50 | subPath: widgets.yaml 51 | - mountPath: /app/config/logs 52 | name: logs 53 | envFrom: 54 | - secretRef: 55 | name: homepage-secrets 56 | volumes: 57 | - name: homepage-config 58 | configMap: 59 | name: homepage 60 | - name: logs 61 | emptyDir: {} 62 | -------------------------------------------------------------------------------- /kubernetes/gitea/mirror-release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/bjw-s/helm-charts/main/charts/other/app-template/schemas/helmrelease-helm-v2.schema.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: gitea-mirror 7 | namespace: gitea 8 | spec: 9 | interval: 1h 10 | chartRef: 11 | kind: OCIRepository 12 | name: app-template 13 | namespace: flux-system 14 | values: 15 | defaultPodOptions: 16 | securityContext: 17 | runAsUser: 1000 18 | runAsGroup: 1000 19 | fsGroup: 1000 20 | runAsNonRoot: true 21 | fsGroupChangePolicy: OnRootMismatch 22 | controllers: 23 | gitea-mirror: 24 | containers: 25 | app: 26 | image: 27 | repository: ghcr.io/raylabshq/gitea-mirror 28 | tag: v3.9.2@sha256:6e3edc29884c11a8a7bb3f65adae266d4f51c32d829c857152372e7dba35a410 29 | env: 30 | BETTER_AUTH_URL: https://gitea-mirror.axis.scottmckendry.tech 31 | BETTER_AUTH_TRUSTED_ORIGINS: https://gitea-mirror.axis.scottmckendry.tech 32 | PUBLIC_BETTER_AUTH_URL: https://gitea-mirror.axis.scottmckendry.tech 33 | NODE_ENV: production 34 | DATABASE_URL: file:data/gitea-mirror.db 35 | HOST: 0.0.0.0 36 | PORT: 4321 37 | GITEA_MIRROR_INTERVAL: 24h 38 | envFrom: 39 | - secretRef: 40 | name: gitea-mirror-secret 41 | securityContext: 42 | allowPrivilegeEscalation: false 43 | readOnlyRootFilesystem: true 44 | capabilities: { drop: ["ALL"] } 45 | probes: 46 | readiness: 47 | enabled: true 48 | custom: true 49 | spec: 50 | httpGet: 51 | path: /api/health 52 | port: 4321 53 | initialDelaySeconds: 10 54 | periodSeconds: 30 55 | timeoutSeconds: 5 56 | failureThreshold: 5 57 | service: 58 | app: 59 | controller: gitea-mirror 60 | ports: 61 | http: 62 | port: 4321 63 | persistence: 64 | data: 65 | existingClaim: gitea-mirror-data 66 | globalMounts: 67 | - path: /app/data 68 | -------------------------------------------------------------------------------- /kubernetes/traefik/tinyauth.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | type: Opaque 5 | metadata: 6 | name: tinyauth-secrets 7 | namespace: traefik 8 | labels: 9 | app.kubernetes.io/name: tinyauth 10 | stringData: 11 | APP_URL: ENC[AES256_GCM,data:4taYWBSFplQA2CgM9+1K2fTAsHq0NmdTRy/Ts0sCcd9tekb5,iv:AcHNeVHlr9fdOUmq7iLZXDmBNIRXjhKklCl61PpeG6k=,tag:ZXGQkXflMNrMBaGehK8R0Q==,type:str] 12 | PROVIDERS_GITHUB_CLIENT_ID: ENC[AES256_GCM,data:UBEtbCZeLl3Fm5Td+d1IYqYNf4o=,iv:juvqkjkUX+NIMOmeVDf6gc3KIkRsbT+XVD6ahSWzxYc=,tag:TnB5BmnynhwG3YrWsp6kig==,type:str] 13 | PROVIDERS_GITHUB_CLIENT_SECRET: ENC[AES256_GCM,data:mylE4x3wY0Ij15Q0GLHnrhPi6e1jAsMzrJtf1lYSn61qv0m1MiXusg==,iv:Xm2CtUv/m1LAEsWku/VkdzezJNHtpM+FVcLgKbVzhnY=,tag:QeWu04u57+u7MM8seZBVaw==,type:str] 14 | OAUTH_WHITELIST: ENC[AES256_GCM,data:FG9kjxgA1Ht4+L6Bg/8LJ3qwMYEl,iv:uj5ndrtxfkw+WFB7g4SM9KL66xlhLzxo3MCWVUiDgIE=,tag:WK7/Q9FpOvazjBQnu0vLBg==,type:str] 15 | DISABLE_CONTINUE: ENC[AES256_GCM,data:MJiDfg==,iv:aC4vfktIUS89us0P50MO4YUUXrnD8CqJddtkJR7vmLE=,tag:Ll0JdeUfUbO9E/BATyVFsg==,type:str] 16 | SESSION_EXPIRY: ENC[AES256_GCM,data:wx12o00s,iv:9ht/f5nE0T7IZmhFCJw6FhAyZX6zjQ9QyA2JbCxqasE=,tag:FgTQMss6K7dxz786X+6j1Q==,type:str] 17 | APP_TITLE: ENC[AES256_GCM,data:h6/iyw==,iv:Hy8URsYX1p1HKRrFylJDF/tKREbxQF/ffjYdx4/Atm0=,tag:4NG67a98I15FYCZyQlwFfA==,type:str] 18 | sops: 19 | age: 20 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 21 | enc: | 22 | -----BEGIN AGE ENCRYPTED FILE----- 23 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBuV211YnRlbVpVZVhBQ0FF 24 | SThOM0o2Vy9sdVFWV0gySDNYekNkZ1hXVEdzCjQvVnp4dWpMVmc4RkhxM0pOaDJu 25 | U2kyLzF0aXppSUFFYmdqR2kzVEZoUEkKLS0tIFZjNVI1VkdvZ0pqNlNkT0VTM2R1 26 | b0UxT3JoMGMwUGc3M2JoTkd5YmJaOVkKtJidi5EIKHyu2pvn+kNZRpq+K+ShpkmR 27 | iGkJywE7Qcja3DT/bF05CXiQpaA4uLM6kiQu5oSTbm+J4q8DZxXLWw== 28 | -----END AGE ENCRYPTED FILE----- 29 | lastmodified: "2025-10-12T06:20:33Z" 30 | mac: ENC[AES256_GCM,data:XQ4ehL3omKpeX+k97Nu5W9LoeBVDPy4bs5zMIoaVWGmzOO1BxWFmp50Fp4IgUHyYmu/pHKWM1pTDje+ZHfg8M0Vgp2DziO3OxJ8SkPrAFQCMTfDbYS5LgPOW+tVhR6yF+3OjNMGOvVdyiAV2qZrMVM/DAwIOCA/gZzen4OOOzJk=,iv:O+UCtvARKyydgWEwj2EJsqcRYOUXha5IVu7u+7GKEU0=,tag:93x618eBE9aJJ8MPdsDQeA==,type:str] 31 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 32 | version: 3.11.0 33 | -------------------------------------------------------------------------------- /kubernetes/traefik/tinyauth.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/deployment.json 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: tinyauth 7 | namespace: traefik 8 | labels: 9 | app: tinyauth 10 | spec: 11 | replicas: 1 12 | selector: 13 | matchLabels: 14 | app: tinyauth 15 | template: 16 | metadata: 17 | labels: 18 | app: tinyauth 19 | spec: 20 | # securityContext: 21 | # fsGroup: 1000 22 | # fsGroupChangePolicy: OnRootMismatch 23 | containers: 24 | - name: tinyauth 25 | image: ghcr.io/steveiliop56/tinyauth 26 | tag: v4.0.1@sha256:815a53c517b4b8f29c02245fc89185186d12162f6bfc2e7566acf888c72ff14b 27 | securityContext: 28 | runAsUser: 1000 29 | runAsGroup: 1000 30 | allowPrivilegeEscalation: false 31 | runAsNonRoot: true 32 | capabilities: 33 | drop: ["ALL"] 34 | seccompProfile: 35 | type: RuntimeDefault 36 | ports: 37 | - containerPort: 3000 38 | envFrom: 39 | - secretRef: 40 | name: tinyauth-secrets 41 | volumeMounts: 42 | - name: data 43 | mountPath: /data 44 | livenessProbe: 45 | httpGet: 46 | path: /api/healthz 47 | port: 3000 48 | readinessProbe: 49 | httpGet: 50 | path: /api/healthz 51 | port: 3000 52 | volumes: 53 | - name: data 54 | emptyDir: {} 55 | --- 56 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/service.json 57 | apiVersion: v1 58 | kind: Service 59 | metadata: 60 | name: tinyauth 61 | namespace: traefik 62 | spec: 63 | ports: 64 | - port: 3000 65 | targetPort: 3000 66 | protocol: TCP 67 | selector: 68 | app: tinyauth 69 | --- 70 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 71 | apiVersion: traefik.io/v1alpha1 72 | kind: IngressRoute 73 | metadata: 74 | name: tinyauth 75 | namespace: traefik 76 | spec: 77 | routes: 78 | - match: Host(`auth.axis.scottmckendry.tech`) 79 | kind: Rule 80 | services: 81 | - name: tinyauth 82 | namespace: traefik 83 | port: 3000 84 | tls: 85 | secretName: axis-tls 86 | -------------------------------------------------------------------------------- /kubernetes/home-assistant/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/bjw-s/helm-charts/main/charts/other/app-template/schemas/helmrelease-helm-v2.schema.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: home-assistant 7 | namespace: home-assistant 8 | spec: 9 | interval: 1h 10 | chartRef: 11 | kind: OCIRepository 12 | name: app-template 13 | namespace: flux-system 14 | values: 15 | defaultPodOptions: 16 | securityContext: 17 | runAsNonRoot: true 18 | runAsUser: 1000 19 | runAsGroup: 1000 20 | fsGroup: 1000 21 | fsGroupChangePolicy: OnRootMismatch 22 | 23 | controllers: 24 | home-assistant: 25 | containers: 26 | app: 27 | image: 28 | repository: ghcr.io/home-operations/home-assistant 29 | tag: 2025.12.4@sha256:c1743f2ed4c2cac05693525d477b73fb45386100660f3f1d6ac23b1720f48023 30 | securityContext: 31 | allowPrivilegeEscalation: false 32 | readOnlyRootFilesystem: true 33 | capabilities: { drop: ["ALL"] } 34 | code: 35 | dependsOn: 36 | - app 37 | image: 38 | repository: ghcr.io/coder/code-server 39 | tag: 4.107.0@sha256:4b622c4cd1b0f559fec0df67fcf09042d63e9ce84c575102066afd9511695627 40 | args: 41 | - --auth 42 | - "none" 43 | - --user-data-dir 44 | - "/config/.vscode" 45 | - --extensions-dir 46 | - "/config/.vscode" 47 | - --port 48 | - "8081" 49 | - "/config" 50 | 51 | service: 52 | app: 53 | controller: home-assistant 54 | ports: 55 | http: 56 | port: 8123 57 | code: 58 | controller: home-assistant 59 | ports: 60 | http: 61 | port: 8081 62 | persistence: 63 | config: 64 | existingClaim: home-assistant 65 | globalMounts: 66 | - path: /config 67 | config-cache: 68 | existingClaim: home-assistant-cache 69 | globalMounts: 70 | - path: /config/.venv 71 | config-logs: 72 | type: emptyDir 73 | globalMounts: 74 | - path: /config/logs 75 | config-tts: 76 | type: emptyDir 77 | globalMounts: 78 | - path: /config/tts 79 | tmp: 80 | type: emptyDir 81 | globalMounts: 82 | - path: /tmp 83 | -------------------------------------------------------------------------------- /scripts/sops.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 🔐 SOPS Encryption/Decryption Helper 4 | # Manages secret files in the repository using SOPS 5 | 6 | set -euo pipefail 7 | 8 | RED='\033[0;31m' 9 | GREEN='\033[0;32m' 10 | BLUE='\033[0;34m' 11 | BOLD='\033[1m' 12 | NC='\033[0m' # No Color 13 | 14 | check_dependencies() { 15 | local missing_deps=() 16 | command -v sops >/dev/null 2>&1 || missing_deps+=("sops") 17 | command -v age >/dev/null 2>&1 || missing_deps+=("age") 18 | 19 | if [ ${#missing_deps[@]} -ne 0 ]; then 20 | echo -e "${RED}Error: Missing required dependencies: ${missing_deps[*]}${NC}" 21 | exit 1 22 | fi 23 | } 24 | 25 | check_age_keys() { 26 | local config_dir="${XDG_CONFIG_HOME:-$HOME/.config}" 27 | local keys_path="$config_dir/sops/age/keys.txt" 28 | 29 | if [ ! -f "$keys_path" ]; then 30 | echo -e "${RED}Error: Age keys file not found at: $keys_path${NC}" 31 | exit 1 32 | fi 33 | } 34 | 35 | usage() { 36 | cat < 41 | 42 | Commands: 43 | encrypt Encrypt all *.secret.yaml files to *.secret.sops.yaml 44 | decrypt Decrypt all *.secret.sops.yaml files to *.secret.yaml 45 | 46 | EOF 47 | exit 1 48 | } 49 | 50 | # Validate arguments 51 | if [ $# -ne 1 ] || [[ ! "$1" =~ ^(encrypt|decrypt)$ ]]; then 52 | usage 53 | fi 54 | 55 | MODE="$1" 56 | 57 | check_dependencies 58 | check_age_keys 59 | 60 | echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" 61 | 62 | if [ "$MODE" = "encrypt" ]; then 63 | echo -e "${BOLD}🔒 Encrypting secret files...${NC}" 64 | # Find all unencrypted secret files and encrypt them if encrypted version doesn't exist or is different 65 | find . -type f -name "*.secret.yaml" | while read -r file; do 66 | encrypted_file="${file/.secret.yaml/.secret.sops.yaml}" 67 | if [ ! -f "$encrypted_file" ] || ! sops --decrypt "$encrypted_file" 2>/dev/null | diff - "$file" >/dev/null 2>&1; then 68 | echo -e " ${GREEN}↳${NC} Encrypting $file -> $encrypted_file" 69 | sops --encrypt "$file" >"$encrypted_file" 70 | fi 71 | done 72 | else 73 | echo -e "${BOLD}🔓 Decrypting secret files...${NC}" 74 | # Find all encrypted files and decrypt them 75 | find . -type f -name "*.secret.sops.yaml" | while read -r file; do 76 | decrypted_file="${file/.secret.sops.yaml/.secret.yaml}" 77 | echo -e " ${GREEN}↳${NC} Decrypting $file -> $decrypted_file" 78 | sops --decrypt "$file" >"$decrypted_file" 79 | done 80 | fi 81 | 82 | echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" 83 | -------------------------------------------------------------------------------- /kubernetes/media/prowlarr/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/bjw-s/helm-charts/main/charts/other/app-template/schemas/helmrelease-helm-v2.schema.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: prowlarr 7 | namespace: media 8 | spec: 9 | interval: 1h 10 | chartRef: 11 | kind: OCIRepository 12 | name: app-template 13 | namespace: flux-system 14 | values: 15 | controllers: 16 | prowlarr: 17 | containers: 18 | app: 19 | image: 20 | repository: ghcr.io/home-operations/prowlarr 21 | tag: 2.3.1.5238@sha256:4394dff2311238bf5c31e006872122ed4e5f6bf042d33528d80acd44a09c57f7 22 | env: 23 | PROWLARR__APP__INSTANCENAME: Prowlarr 24 | PROWLARR__APP__THEME: dark 25 | PROWLARR__AUTH__METHOD: External 26 | PROWLARR__AUTH__REQUIRED: DisabledForLocalAddresses 27 | PROWLARR__LOG__DBENABLED: "False" 28 | PROWLARR__LOG__LEVEL: info 29 | PROWLARR__SERVER__PORT: 80 30 | PROWLARR__UPDATE__BRANCH: develop 31 | TZ: Pacific/Auckland 32 | securityContext: 33 | allowPrivilegeEscalation: false 34 | readOnlyRootFilesystem: true 35 | capabilities: { drop: ["ALL"] } 36 | flaresolverr: 37 | containers: 38 | app: 39 | image: 40 | repository: ghcr.io/flaresolverr/flaresolverr 41 | tag: v3.4.6@sha256:7962759d99d7e125e108e0f5e7f3cdbcd36161776d058d1d9b7153b92ef1af9e 42 | ports: 43 | - containerPort: 8191 44 | securityContext: 45 | allowPrivilegeEscalation: false 46 | runAsNonRoot: true 47 | capabilities: { drop: ["ALL"] } 48 | seccompProfile: 49 | type: RuntimeDefault 50 | defaultPodOptions: 51 | securityContext: 52 | runAsNonRoot: true 53 | runAsUser: 1000 54 | runAsGroup: 1000 55 | fsGroup: 1000 56 | fsGroupChangePolicy: OnRootMismatch 57 | service: 58 | app: 59 | controller: prowlarr 60 | ports: 61 | http: 62 | port: 80 63 | flaresolverr: 64 | controller: flaresolverr 65 | ports: 66 | http: 67 | port: 8191 68 | persistence: 69 | config: 70 | existingClaim: prowlarr 71 | advancedMounts: 72 | prowlarr: 73 | app: 74 | - path: /config 75 | readOnly: false 76 | -------------------------------------------------------------------------------- /kubernetes/traefik/external-ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/service.json 3 | apiVersion: v1 4 | kind: Service 5 | metadata: 6 | name: ext-truenas 7 | namespace: traefik 8 | spec: 9 | type: ExternalName 10 | externalName: 10.0.6.1 11 | --- 12 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 13 | apiVersion: traefik.io/v1alpha1 14 | kind: IngressRoute 15 | metadata: 16 | name: ext-truenas 17 | namespace: traefik 18 | spec: 19 | routes: 20 | - match: Host(`truenas.axis.scottmckendry.tech`) 21 | kind: Rule 22 | middlewares: 23 | - name: ipallowlist 24 | services: 25 | - name: ext-truenas 26 | port: 80 27 | tls: 28 | secretName: axis-tls 29 | --- 30 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 31 | apiVersion: traefik.io/v1alpha1 32 | kind: IngressRoute 33 | metadata: 34 | name: ext-truenas-jellyfin 35 | namespace: traefik 36 | spec: 37 | routes: 38 | - match: Host(`jellyfin.axis.scottmckendry.tech`) 39 | kind: Rule 40 | middlewares: 41 | - name: ipallowlist 42 | services: 43 | - name: ext-truenas 44 | port: 30013 45 | tls: 46 | secretName: axis-tls 47 | --- 48 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 49 | apiVersion: traefik.io/v1alpha1 50 | kind: IngressRoute 51 | metadata: 52 | name: ext-truenas-craftycontroller 53 | namespace: traefik 54 | spec: 55 | routes: 56 | - match: Host(`crafty.axis.scottmckendry.tech`) 57 | kind: Rule 58 | middlewares: 59 | - name: ipallowlist 60 | - name: forwardauth 61 | - name: default-headers 62 | services: 63 | - name: ext-truenas 64 | port: 30146 65 | scheme: https 66 | tls: 67 | secretName: axis-tls 68 | --- 69 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/service.json 70 | apiVersion: v1 71 | kind: Service 72 | metadata: 73 | name: ext-proxmox 74 | namespace: traefik 75 | spec: 76 | type: ExternalName 77 | externalName: 10.0.20.1 78 | --- 79 | # yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json 80 | apiVersion: traefik.io/v1alpha1 81 | kind: IngressRoute 82 | metadata: 83 | name: ext-proxmox 84 | namespace: traefik 85 | spec: 86 | routes: 87 | - match: Host(`proxmox.axis.scottmckendry.tech`) 88 | kind: Rule 89 | middlewares: 90 | - name: ipallowlist 91 | - name: forwardauth 92 | - name: default-headers 93 | services: 94 | - name: ext-proxmox 95 | port: 8006 96 | scheme: https 97 | -------------------------------------------------------------------------------- /.taskfiles/talos.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://taskfile.dev/schema.json 3 | version: 3 4 | 5 | vars: 6 | CLUSTER_NAME: "axis" 7 | CLUSTER_VIP: "10.0.10.10" 8 | # renovate: datasource=github-releases depName=siderolabs/talos 9 | TALOS_VERSION: v1.12.0 10 | 11 | includes: 12 | sops: ./sops.yaml 13 | 14 | tasks: 15 | generate: 16 | desc: Generate Talos config 17 | dir: talos 18 | vars: 19 | PATCHES: 20 | sh: "ls patches/*.yaml 2>/dev/null | sed 's/.*/--config-patch @&/g' | tr '\n' ' '" 21 | IMAGE_SCHEMATIC_ID: 22 | sh: "curl -fsSL -X POST --data-binary @image-schematic.yaml https://factory.talos.dev/schematics | jq -r '.id'" 23 | cmds: 24 | - task: sops:decrypt 25 | - cmd: mkdir -p rendered/ 26 | silent: true 27 | - > 28 | talosctl gen config {{.CLUSTER_NAME}} https://{{.CLUSTER_VIP}}:6443 29 | --talos-version {{.TALOS_VERSION}} 30 | --install-image factory.talos.dev/installer/{{.IMAGE_SCHEMATIC_ID}}:{{.TALOS_VERSION}} 31 | --with-secrets talos.secret.yaml 32 | --output-types controlplane 33 | --output rendered/controlplane.yaml 34 | --force 35 | {{.PATCHES}} 36 | 37 | apply: 38 | desc: Apply Talos config 39 | dir: talos 40 | cmds: 41 | - task: generate 42 | - for: ["10.0.10.11", "10.0.10.12", "10.0.10.13"] 43 | task: apply_node 44 | vars: { NODE_IP: "{{.ITEM}}" } 45 | 46 | apply_node: 47 | internal: true 48 | desc: Apply Talos config to individual node 49 | dir: talos 50 | vars: 51 | NODE_IP: '{{default "" .NODE_IP}}' 52 | prompt: "Apply configuration to node {{.NODE_IP}}?" 53 | cmds: 54 | - cmd: echo "" 55 | silent: true 56 | - talosctl apply -f rendered/controlplane.yaml -n {{.NODE_IP}} 57 | - cmd: echo "" 58 | silent: true 59 | 60 | upgrade: 61 | desc: Upgrade Talos nodes to specified version 62 | dir: talos 63 | vars: 64 | VERSION: "{{default .TALOS_VERSION .VERSION}}" 65 | cmds: 66 | - for: ["10.0.10.11", "10.0.10.12", "10.0.10.13"] 67 | task: upgrade_node 68 | vars: { NODE_IP: "{{.ITEM}}" } 69 | 70 | upgrade_node: 71 | internal: true 72 | desc: Upgrade individual Talos node 73 | dir: talos 74 | vars: 75 | NODE_IP: '{{default "" .NODE_IP}}' 76 | IMAGE_SCHEMATIC_ID: 77 | sh: "curl -fsSL -X POST --data-binary @image-schematic.yaml https://factory.talos.dev/schematics | jq -r '.id'" 78 | prompt: "Upgrade node {{.NODE_IP}} to version {{.TALOS_VERSION}}?" 79 | cmds: 80 | - cmd: echo "" 81 | silent: true 82 | - talosctl upgrade --nodes {{.NODE_IP}} --image factory.talos.dev/installer/{{.IMAGE_SCHEMATIC_ID}}:{{.TALOS_VERSION}} 83 | - cmd: echo "" 84 | silent: true 85 | -------------------------------------------------------------------------------- /kubernetes/ccinvoice/ccinvoice.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: ccinvoice-secrets 6 | namespace: ccinvoice 7 | type: Opaque 8 | stringData: 9 | SMTP_HOST: ENC[AES256_GCM,data:Hu/Z3dyra/gHRpivwDo=,iv:08gouABi3P/cuUCHco2cM8/ziXzSec2fKbQZb10SMD8=,tag:1wlBiy1IAfz8n06ii5+2jw==,type:str] 10 | SMTP_PORT: ENC[AES256_GCM,data:K+Lm,iv:C0nPgsjhxAtqLvBHkPLD/pFSpQD18zrrkAQ4AJO/q6o=,tag:RpmrZtCH9gAHA+s6rzhDfA==,type:str] 11 | SMTP_USER: ENC[AES256_GCM,data:vymuST2AYAcoNbLvjyRCUYZsyE+GCg==,iv:UcCjtu0e8aPendTSfSnuUytPYQRA9uiXBk2evVksD+A=,tag:9J3ZJVjvQAC+MEskTm4Qkg==,type:str] 12 | SMTP_PASS: ENC[AES256_GCM,data:dY0JVzCfad/Gs2EyxlRG/At3Mw==,iv:LhBY+rQ7H2FfX6J+2G/cjR8RFgV2Wh5phtPg/flx7d4=,tag:FEFI5IJXmFYIYAJPLUd/LQ==,type:str] 13 | FROM_NAME: ENC[AES256_GCM,data:hknJ8dLpp1GVFVNiWhcMTg==,iv:/Ec128AxwLy9STxo+ItPRDwKDL6daF3tPgLFWokjz7I=,tag:TuCdWMGCUsxkE34sxmixJg==,type:str] 14 | FROM_ADDRESS: ENC[AES256_GCM,data:jloxwhHlKCgNyZ5Lmp5SjGaxGEHU,iv:z5QtegUlPeTJ48r07D5n2qlZ0JdopBGLViXlnarQJv4=,tag:ZgtHzZVZ0ipjNd0fXM34iQ==,type:str] 15 | FROM_CITY: ENC[AES256_GCM,data:rKDkE9/WrQQ8,iv:m0IVtDgink9PSaLUbEkjR/ew7er5FUKsQVhXsL7cHKo=,tag:lHe658FRif2P2f5GkeQp7A==,type:str] 16 | ACCOUNT_NUMBER: ENC[AES256_GCM,data:6keYEb3LaoBf06biZFJwsqge,iv:919d4ShbrYV9N3j17gGl3jzct2NSW/PvziNwcYO9RwM=,tag:9cYBcUru0G7soUCZ74h27g==,type:str] 17 | BASE_URL: ENC[AES256_GCM,data:7rnygNXpOKcsm9w5bQhI1GSFhKSFy8X6zSKIxG7fycWm7gO4sdruSA==,iv:C9+1PC0xpatrM7z76VDBf9qW909EyEUUh8hV/oi5NYw=,tag:TVeWG4i8/z2B68ecoQVHfw==,type:str] 18 | TZ: ENC[AES256_GCM,data:qdyMCuLnMMGkmnDd13y3lg==,iv:pgXygPTkg19cS4Y1lS6213zQKf7Nv6viIwen9L4uf+U=,tag:6SjS1RvJHTv8gMP87ihcjw==,type:str] 19 | sops: 20 | kms: [] 21 | gcp_kms: [] 22 | azure_kv: [] 23 | hc_vault: [] 24 | age: 25 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 26 | enc: | 27 | -----BEGIN AGE ENCRYPTED FILE----- 28 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVMkdwdDBtRHZ0M05IdHJl 29 | UnRlQmg3ck9ObFlPd3dDOVB2NVJOd1FSM2xZCnIxdGpadk9TY0daSHFkcWU0d0ZB 30 | d084aUlWZ2pUMU1hR05QcXFBQmRBSlEKLS0tIHNYdzZGMkl4RzZTOXBHY24xaXFU 31 | OXFxVlRFUy82dU0vYUlTWXVGd1VaNkUKtyScQcLv8Nh8K7HglnphxmXr7I7yvJtA 32 | tvrusVYkMxZhuEJoBDHEPSYcU1JT6xmD9MhgLCXmeXTaBg+ZPd3+0g== 33 | -----END AGE ENCRYPTED FILE----- 34 | lastmodified: "2025-05-15T08:22:13Z" 35 | mac: ENC[AES256_GCM,data:3Z+ZGZIWLXVevcS18Ozytzzy8HGeMEpW/8yUIFvqXBr7hWw4B70MOr/vIQeBMJAKbm7Fk4zlaXFbTwwMEjRWmDorJ0Sr/zRMzjy+Nswqbugq8ypNypyOrZepyr548eitZAmblN7LpVn7QL2BxWsjaAUFBbHVuwOv2y43AlITtnI=,iv:bqA8JdZkmV1tIuraCt3d9KyojYIjtgqsi7IMjUs5CVk=,tag:cMenBBnfjd51dDL/HaynRQ==,type:str] 36 | pgp: [] 37 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 38 | version: 3.9.4 39 | -------------------------------------------------------------------------------- /scripts/pick-backup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 🔍 Backup Selection Helper 4 | # Interactively select a backup timestamp from restic snapshots 5 | # requires: restic, jq, fzf 6 | # https://volsync.readthedocs.io/en/stable/usage/restic/index.html#restore-options 7 | 8 | set -euo pipefail 9 | 10 | # Colors and formatting 11 | BLUE='\033[0;34m' 12 | GREEN='\033[0;32m' 13 | BOLD='\033[1m' 14 | NC='\033[0m' # No Color 15 | 16 | echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" 17 | echo -e "${BOLD}🔍 Backup Selection Helper${NC}" 18 | 19 | # Get secret name from argument or default 20 | echo -ne " ${GREEN}↳${NC} Enter namespace for restore: " 21 | read -r NAMESPACE 22 | echo -ne " ${GREEN}↳${NC} Enter PVC name to restore: " 23 | read -r PVC_NAME 24 | SECRET_NAME="restic-$NAMESPACE-$PVC_NAME" 25 | echo -e " ${GREEN}↳${NC} Loading credentials from ${BOLD}$SECRET_NAME${NC}..." 26 | 27 | SECRET_PROPS=( 28 | AWS_ACCESS_KEY_ID 29 | AWS_SECRET_ACCESS_KEY 30 | RESTIC_PASSWORD 31 | RESTIC_REPOSITORY 32 | ) 33 | 34 | # Export required variables from kubernetes secret 35 | for prop in "${SECRET_PROPS[@]}"; do 36 | if kubectl get secret -n "$NAMESPACE" "$SECRET_NAME" &>/dev/null; then 37 | value=$(kubectl get secret -n "$NAMESPACE" "$SECRET_NAME" -o jsonpath="{.data.$prop}" | base64 --decode) 38 | export "$prop=$value" 39 | else 40 | echo "Secret $SECRET_NAME not found in namespace $NAMESPACE" >&2 41 | exit 1 42 | fi 43 | done 44 | 45 | echo -e " ${GREEN}↳${NC} Removing any stale locks..." 46 | restic unlock 47 | echo -e " ${GREEN}↳${NC} Loading available backups..." 48 | 49 | # Fetch and format backup list 50 | backup_list=$(restic snapshots --json | 51 | jq -r '.[] | "\(.time) [\(.summary.total_bytes_processed/1024/1024 | floor)MB, \(.summary.total_files_processed) files] \(.paths[0])"') 52 | 53 | [ -z "$backup_list" ] && { 54 | echo "No backups found" 55 | exit 1 56 | } 57 | 58 | # Select from the backup list 59 | selected=$(echo "$backup_list" | 60 | fzf --height 40% --tac) 61 | 62 | # Extract just the timestamp part (everything before the first '[') 63 | timestamp=$(echo "$selected" | cut -d'[' -f1 | xargs) 64 | 65 | # Round up to the nearest minute for the "restoreAsOf" option 66 | adjusted=$(date -d "$timestamp + 1 minute" +"%Y-%m-%dT%H:%M:00Z") 67 | 68 | # Cleanup environment 69 | for prop in "${SECRET_PROPS[@]}"; do 70 | unset "$prop" 71 | done 72 | 73 | if [[ -z "$adjusted" ]]; then 74 | echo "No valid date selected. Exiting." 75 | exit 1 76 | fi 77 | 78 | echo -e " ${GREEN}↳${NC} Selected backup timestamp: ${BOLD}$adjusted${NC}" 79 | echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" 80 | 81 | # replicate hyphens in namespace to underscores for compatibility with env var lookups in child task 82 | # e.g. home-assistant -> volsync_restore_home_assistant 83 | TASK_NAME="${NAMESPACE}-${PVC_NAME}" 84 | task volsync:restore-${TASK_NAME//-/_} -- --restore-date "$adjusted" 85 | -------------------------------------------------------------------------------- /kubernetes/actual/akahu-actual/akahu-actual.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema: https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: akahu-actual-secret 6 | namespace: actual 7 | type: Opaque 8 | stringData: 9 | ACCOUNT_MAPPINGS: ENC[AES256_GCM,data:Kmgh02hWr83EycLF3z6BtiRtU5nwCAUebv26HvlLcpfkzD3IbYM3rGdWlM1v6zi1rDDLKKi8mWDa1vn8nq0TK+yX+J/TydMw16JDnKU4WF00wgdqODjYec9TllgLu0RdOMtNtG+4h4dUNLTV4PNAcoLv+8FjpoecWJB7PrukjmzFdwC7hAWh/8I9TV1Y+fycFdI66LPI7jg7JN41K/26nh4e2/VZEPOYeuSpSlkF7Mv2PCKMlvrh6WYkzncWS9c9ifx6jYIPW7LUy8Dy8UBg1AQOCizjlJjfMlJ41DH2zqDJrIEMTBcru9wc6RjAeW2u3LUoHu1uAl1USlsc5uH9+AlT35oepd0FpUStRNzDrOyT6olYoduEip0zzrPbkOOGveJPvvV0LrSc31tDpOOkVgYkLiS/oX6+x9TqhrxoETOJiqiMeB4NQ2Dy27jWoWOKGS5exbPcSXxWcl5c0JXnUof3jcDb6cj8oL2u9pUfN6Lnzshm4QnbOGg9E8CLnkUjA3d69bIjtvP+1mL4ldwmcJL4IGuR3Ri4/FsuGbdE+fvISowMJnR0zNAJIFMLnj+q+yqkLk9iaCrkF42u2QKoz+WJWi/lJIFRv4rnbpj48FRjuWLbUdFbBsWTIhLbltdOVeqOeKeMxcPAt0vDTEWktggYhAFPhw71DQgnAKDO9zHL,iv:zPBtNORB14v1p+sdIK0Ri39qoa6AvXEvsaFwmULteVE=,tag:9wo2lOzBSach2kPWvOkfcA==,type:str] 10 | RECONCILE_ACCOUNT_IDS: ENC[AES256_GCM,data:rHw/KAWkfQkHctBO4WBoZByo11Sh9nBQUibFwz0kj7uDooqXj6jYntBo+0SCkRfvIPCTopmFh2CQcVf5HRFA6LVNP6kCiq9oS0YoNacvhMkZvR7qpvf/teWPqX4for9XpqCx,iv:v2EBQKNEhLr88lXo3qLvB50IGnJtSeg0TkD9rxjJoZo=,tag:m8Uc5M/+GmPa1HxT2K98Uw==,type:str] 11 | AKAHU_APP_TOKEN: ENC[AES256_GCM,data:N/qRbS69UhTCh2t5dbw9xCT4+f9RI8vUly6lTWL/jaaFvbo=,iv:P36HVzdlP6qcoxC2xYHObwf+YmCn1H+rCz6CnOv2XDI=,tag:SDwSbdXCazFyRBS4WKCLCQ==,type:str] 12 | AKAHU_USER_TOKEN: ENC[AES256_GCM,data:n0mD+IRJfyHFjDn8TJWoU2YcOAHv/PvQUN8pJHUdZFTIOMKK,iv:8TWuptur/tMhW6EU8Oe3H7IQozu3YnUBKk65ietvaoI=,tag:MK6TdkisMSIBdcPS9sIDqg==,type:str] 13 | ACTUAL_SERVER_URL: ENC[AES256_GCM,data:eDHlUN8IlG3aQtEstqMdnfHoweRhDUEpuE5ZaJeMRnCxpDokoWbndf//Sw==,iv:Ke/I/++OwUuzuyJRRDfKAO3dcrwNLptlDKE8WZORnvc=,tag:Laredc07t5RxlhAFgn+Okg==,type:str] 14 | ACTUAL_PASSWORD: ENC[AES256_GCM,data:9pCCgg8Od2VL6reQOdgayDXLtZ5fbQRKrQ==,iv:A2gC73788qObGgEc2KH5UJ8TKbhhEq1rstoRTsUJT8w=,tag:jhOjKyEqP7A4f3YsfEpACg==,type:str] 15 | ACTUAL_SYNC_ID: ENC[AES256_GCM,data:SIKeqLtQMf71igb2gqLy7bktRnqrCQcQ9+E/342AHzv3TUxP,iv:ObXd5nvAVMUsaKIWTluab5knSa5NTCbzDuslNVBS0Qo=,tag:WLihf5MpCIxgTQXjZ/XgWg==,type:str] 16 | sops: 17 | age: 18 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 19 | enc: | 20 | -----BEGIN AGE ENCRYPTED FILE----- 21 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBudWtHa0RjazJaa0padFpi 22 | Q0g0dHhSS0JiNFFEUGVIL2dFMVplZi9kcW04Clo3WmJnYVF3V0ZURktKOVoyVTFz 23 | UmtqQ3JBdU5LQUNwSmx3RVJtTDM2RWMKLS0tIGZFRFUrSFYydnRSZ2wyMkpRQkRo 24 | aVNIc2E5cHZVZnpOL1lZMzdkbFIvakUKnXU9H9Uaw7HeqhLFsp6rdE/QuicHkEAi 25 | dsDMt1+2TvJBenhRrJkiWZ0pYOGt12r8GLVfxHhVLL2vEecOF4g+Zw== 26 | -----END AGE ENCRYPTED FILE----- 27 | lastmodified: "2025-12-07T21:39:24Z" 28 | mac: ENC[AES256_GCM,data:tW1kWpj+Jafz4s/Iuhg9ricOD3Kf3gvbYXvfLfh6IFOVQP+wj64cAwQ2VoggMjhhQT4PF55kiGsyCwAuAH0QHXSXMhL0jL0BHV9VbH6QZqUagwymuBstwf+Ypl35gasxR2xuZ3B6yQR3VLWnlA0X091+7W8Z4uELt/un3qQB6fQ=,iv:/+aSSVCij0uyPbybRZbgKEuNAt+iC2Pn5FxvzaBv1+g=,tag:aHBbUGZM55lV2VLft1mt1g==,type:str] 29 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 30 | version: 3.11.0 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 |

AXIS

7 |

8 | Automated eXperimental Infrastructure System 9 |

10 |

11 | 12 | --- 13 | 14 | Mono-repo for my GitOps-driven K8s homelab 🏠 15 | 16 | ## 🧭 Overview 17 | 18 | AXIS is my GitOps Kubernetes cluster home lab. The repository contains all cluster manifests, Helm repositories and releases, Talos OS configuration, backup policies, and secrets management tooling via SOPS. 19 | 20 | - **GitOps**: Flux v2 manages state from this repo 21 | - **Manifests**: Kustomize overlays per app/namespace under `kubernetes/` 22 | - **OS**: Talos for immutable Kubernetes nodes (see `talos/`) 23 | - **Control plane**: 3 Talos control-plane nodes in HA behind a VIP (MetalLB) 24 | - **Ingress**: Traefik + cert-manager (Let’s Encrypt via Cloudflare) 25 | - **Storage**: democratic-csi (TrueNAS) and local-path-provisioner 26 | - **Monitoring**: kube-prometheus-stack, Grafana, Loki/Promtail, Alertmanager 27 | - **Backups**: VolSync (restic) with Backblaze B2 28 | - **Secrets**: SOPS with age 29 | 30 | ## 🔐 Secrets management (SOPS + age) 31 | 32 | Common operations: 33 | 34 | ```sh 35 | # Decrypt secrets 36 | task sops:decrypt 37 | 38 | # Re-encrypt all secrets 39 | task sops:encrypt 40 | 41 | # Low-level helper (used by the tasks) 42 | scripts/sops.sh encrypt|decrypt 43 | ``` 44 | 45 | Secret file conventions: 46 | 47 | - Secrets end with `.secret.sops.yaml` 48 | - Decrypted secrets end with `.secret.yaml` (wildcard in `.gitignore`) 49 | 50 | image 51 | 52 | ## ♻️ Backups and restores (VolSync) 53 | 54 | VolSync is used to snapshot and synchronize PVCs to object storage (Backblaze B2). Each app declares its backup policy under its directory, typically `backup/` with a `backblaze.secret.sops.yaml` for credentials and a `backup.yaml` defining ReplicationSource/ReplicationDestination. 55 | 56 | - Configure credentials in the corresponding `backblaze.secret.sops.yaml` (encrypted with SOPS) 57 | - Validate VolSync resources with kustomize/kubeconform as usual 58 | 59 | Operational tasks: 60 | 61 | ```sh 62 | # Interactive restore workflow 63 | task volsync:interactive-restore 64 | 65 | # App-specific restore shortcuts (if defined) 66 | task volsync:restore- 67 | ``` 68 | 69 | Example backup locations in this repo: 70 | 71 | - `kubernetes/home-assistant/backup/` 72 | - `kubernetes/media/*/backup/` 73 | - `kubernetes/actual/backup/` and `kubernetes/ccinvoice/backup/` 74 | 75 | Notes: 76 | 77 | - Restores will temporarily scale down workloads and restore PVC contents 78 | - Ensure network egress for B2 and that credentials are valid 79 | 80 | image 81 | 82 | ## 🛡️ Talos lifecycle and upgrades 83 | 84 | Talos is configured under `talos/` with patches in `talos/patches/`. Use Taskfile helpers for generating machine configs, applying changes, and upgrading node images. 85 | 86 | Common operations: 87 | 88 | ```sh 89 | # Generate Talos machine configs from image schematic and patches 90 | task talos:generate 91 | 92 | # Apply generated configs to the cluster 93 | task talos:apply 94 | 95 | # Upgrade Talos across control plane and workers 96 | task talos:upgrade 97 | ``` 98 | 99 | Patches of interest in `talos/patches/` include networking (VIP, DHCP), storage mounts for local-path-provisioner, and permissions for running certain workloads on control-plane nodes. 100 | -------------------------------------------------------------------------------- /kubernetes/homepage/homepage.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json 2 | apiVersion: v1 3 | kind: Secret 4 | type: Opaque 5 | metadata: 6 | name: homepage-secrets 7 | namespace: homepage 8 | labels: 9 | app.kubernetes.io/name: homepage 10 | stringData: 11 | HOMEPAGE_VAR_GITEA_API_KEY: ENC[AES256_GCM,data:sedv/K1byTECzwVmr/nuKxUWFJL1ArScGL6BXcizwp8cPs5mxEIbrQ==,iv:vCLZ/KDYB1/A8auyVdIWoGMwdxl1qjG8Of2zAPwdmVQ=,tag:52P1xWXKL47eZv0nGnrWcg==,type:str] 12 | HOMEPAGE_VAR_GRAFANA_PASSWORD: ENC[AES256_GCM,data:ke+Ay0BDIaiPzJ+CGNuT,iv:EjrgB/GxmoqynpWuhRWWioFTaDyGsuk4Zv0wAKai2pQ=,tag:/0TUhITWchhsI7ZBmbHG9A==,type:str] 13 | HOMEPAGE_VAR_GRAFANA_USERNAME: ENC[AES256_GCM,data:m1wl1Hs=,iv:XR5/DxPm1NyKXKNakHz/MZl3IP8PB9qpCH9r9dllSZ8=,tag:/XkOkKd4BgdR8x9GcLWLFA==,type:str] 14 | HOMEPAGE_VAR_HASS_KEY: ENC[AES256_GCM,data:NMaD8heh72s9tTNyaq7utuzqz2mFKDchJ82ik9wmz28AI04Xzpl5xkSdpfoFBk8sE0tQZY3ChA/vtsCSUdlO45srqpXek5JJsz1mMJ7xviRDx4eU72aGaeUK7tUTX7/JFWR+hZerLJowdIMz/dgKZ7FKQyMX9jYVs56emAnux8u+t6D7lATxrFyjnrDRXgAjR0Aer3trvQXP8nuT4pzHPu0/7WW0Yy2Envg+ul9EXxQ9sg41isKc,iv:DhcS+PA+MfVjsWsFWoOT9VnAzoslwjYq3WhDdBFkNwQ=,tag:PcQHmavJZ54PDS8Foiwurw==,type:str] 15 | HOMEPAGE_VAR_JELLYFIN_API_KEY: ENC[AES256_GCM,data:OVk/M34ZpQLbLDqk9vTxwWtR6imzRykZV1N+3+HckiA=,iv:CKuLBI32H0ljqQirB2O2RKpMdsAgpEvDxBj1a3C5Vcw=,tag:0VE4pvH6Limrjh1B8+BvCA==,type:str] 16 | HOMEPAGE_VAR_PROWLARR_API_KEY: ENC[AES256_GCM,data:sOokQC+GVC6dFS+8/zQBd6AEDmoi+NflVHExzujiIks=,iv:L+FbmcCXwiMWLbnCYy0HR1VbH+r4J5Vjb4qDsikljhs=,tag:Q+Vsdyp8mF/0gE4zF0hBrw==,type:str] 17 | HOMEPAGE_VAR_PROXMOX_TOKEN_ID: ENC[AES256_GCM,data:wDzi++zXNDQhOnWMU5foVOecZXMm,iv:FCsvb7NxZVs3CoCdZeMpsaFmuY16z87EGaj9vqUYDpw=,tag:XOaF4ecqPdk4Bc96ANRuow==,type:str] 18 | HOMEPAGE_VAR_PROXMOX_TOKEN_SECRET: ENC[AES256_GCM,data:oFBuaf6zyQtwW6TodqF+k5/KSI+XiykRQ+kccwdws+TbhVkD,iv:0IM26BtPrlzUANzPB9s5Z4VBTtgSytUzsc5S/hG4774=,tag:ETq3+eQVAKVtQI/EtDaKWA==,type:str] 19 | HOMEPAGE_VAR_QBITTORRENT_PASSWORD: ENC[AES256_GCM,data:9uUTTKsGk4nLPVunZc0Y,iv:u/vyWKmHvNoyAglTGYt9lMWnruPtpER1ZEodbH0Mpok=,tag:Tz3Nc8ODjWIL4fkv0WQDmg==,type:str] 20 | HOMEPAGE_VAR_QBITTORRENT_USERNAME: ENC[AES256_GCM,data:eCTX71A=,iv:HnwZTjeSpOslp0WZu/tQy4nTONODdJC1iVcxaXafpgc=,tag:HgE/Wnx+LwDus08WG2BEIw==,type:str] 21 | HOMEPAGE_VAR_RADARR_API_KEY: ENC[AES256_GCM,data:AEGqmwciU7GzbLrB+DYIYQ/Jido7g4b8vD5qRkXRC0w=,iv:FUqPKNcZZuAtkXP9ZM6frtzffxDzss+4/gv6Loghxfg=,tag:zJa+DtS69HVsXd23yVqpUg==,type:str] 22 | HOMEPAGE_VAR_SONARR_API_KEY: ENC[AES256_GCM,data:8vhIcYw/e6CiHLODCPZSnZQhSC4orkazCuu7ndDpLRk=,iv:bM0Nnt5x007dTUxuWCApnv2hbIqxk6KHD2fVbU1R7z8=,tag:b95epzhM7a0QdCSlxtNVeQ==,type:str] 23 | HOMEPAGE_VAR_TRUENAS_API_KEY: ENC[AES256_GCM,data:wam8BoXPTpAchAlJir+O141hzZu1+87ld+4R+fJvCKzZA/cf6h3Y0XxUVmgZQGUk2riYklvT1hSR17zE9F+5crQH,iv:e8an7ukJTCLavEv4RkX9Ze5SI/lBsjw8oiFkOI4EpAI=,tag:ixkqfT2FIJjeMp+O+QAv1A==,type:str] 24 | HOMEPAGE_VAR_UNIFI_KEY: ENC[AES256_GCM,data:z16LoHETIWm2gKBrx7XvAOb6BBvzh2YTlMn5Ct7joMw=,iv:Duq6uaOJ19D2gtdrFlMbEq2b7yOENSOnzAaU8iHIaSw=,tag:IlvdG4caLICNO/nY4Pd3CQ==,type:str] 25 | HOMEPAGE_VAR_UNIFI_URL: ENC[AES256_GCM,data:ixGpY4jHlKq5uUoumhH+K1TN4C7PoTtV0e74Cq1O5ZLsyyL2v6FhdPr/2O/NN/XtfF2kGz2QNVE89/hASyzAQgkKpguDxQddMToJ7xixGTQ0xtwS6/UJ8lBG6B8J8Efuwy3Smr3IO9+wgKlaL+X+CdKn5WajmEoJ+nhLEFrIjw==,iv:wRCOY0mcEIVcrIpqL8I8W9jCw4B1LQKfYHNhNVZe5kk=,tag:mPFboPOeMvkm4ihbKsalug==,type:str] 26 | sops: 27 | age: 28 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 29 | enc: | 30 | -----BEGIN AGE ENCRYPTED FILE----- 31 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJUERxQWN2elBVTEJPRDRB 32 | YUxub1YvUDUrTGZRYzF3dWh1VVJxekNXTmpBCjNIc1VVV2RUOEFZdzJNODhKWkl3 33 | cEVMVmtVWTkyYnpIYzc0M0JWRjlqUzgKLS0tIE5lTzl2T3dWMGFsYzYxeHNjempn 34 | aDB0ekpINmViaEVqU25xaEN3c3I2RGMKe0JsrrEuotHPTrRzhKtz83oJNkmiNkP3 35 | wKC6d46zqRVi6WvRHlvvyX5MFNtGf3KA1f6BWZwJ/sxo0nVqQel/Sg== 36 | -----END AGE ENCRYPTED FILE----- 37 | lastmodified: "2025-10-25T03:24:16Z" 38 | mac: ENC[AES256_GCM,data:xLrz5mFTiSNm6sXPNh7RPhPUPKbO2wjs0shGMfH3Pr4Byfwpvnkd7V9VsQDFh7qK/lL7U8fLlN1P/hRJLllTvNvPMtNzW7VrEXsACkDE4lmX1OmH3Px3O8DINtjLAt8WePXdW/Tr6bB6CkNETDgw5Bivz80lsz8BrAV4CoVPRM4=,iv:6+VSwnnr8HwaqMVao20JVfT1+TrYmo6bM+vl9sf/74M=,tag:xF6ry0S+9BpAiszFu/r8fQ==,type:str] 39 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 40 | version: 3.11.0 41 | -------------------------------------------------------------------------------- /kubernetes/storage/democratic-csi/iscsi.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/helm.toolkit.fluxcd.io/helmrelease_v2.json 2 | apiVersion: helm.toolkit.fluxcd.io/v2 3 | kind: HelmRelease 4 | metadata: 5 | name: iscsi 6 | namespace: storage 7 | spec: 8 | chart: 9 | spec: 10 | chart: democratic-csi 11 | version: 0.15.0 12 | sourceRef: 13 | kind: HelmRepository 14 | name: democratic-csi 15 | interval: 1h 16 | timeout: 10m 17 | values: 18 | csiDriver: 19 | name: iscsi 20 | storageClasses: 21 | - name: iscsi 22 | defaultClass: true 23 | reclaimPolicy: Delete 24 | volumeBindingMode: Immediate 25 | allowVolumeExpansion: true 26 | parameters: 27 | fsType: ext4 28 | detachedVolumesFromSnapshots: "false" 29 | mountOptions: [] 30 | secrets: 31 | provisioner-secret: null 32 | controller-publish-secret: null 33 | node-stage-secret: null 34 | node-publish-secret: null 35 | controller-expand-secret: null 36 | driver: 37 | config: 38 | driver: freenas-api-iscsi 39 | instance_id: null 40 | httpConnection: 41 | protocol: http 42 | host: ENC[AES256_GCM,data:wTfvmQcDtTY=,iv:iLdptNqRBWiaFDuzzid9XgRPWO5zH30MComHP2paPxI=,tag:KQ8/swkQdzyWok4VlqsSLA==,type:str] 43 | port: 80 44 | allowInsecure: true 45 | apiKey: ENC[AES256_GCM,data:Liq1HEj/CVf2rEQBa9EBQCu8s4PkHRi5JziPxlrWzl7ivqM42Kc8sdjz+dCatcQY7FhTQ/pgYJmSv0XMY7iyBA4+,iv:TVJP+fpOXB2+X/k+1TSBIHW0YqwYutVzDEYiA8hhwko=,tag:YOAdCZxtoZR73HrE9jHE8Q==,type:str] 46 | zfs: 47 | datasetParentName: TrueBlue/k8s/iscsi/v 48 | detachedSnapshotsDatasetParentName: TrueBlue/k8s/iscsi/s 49 | zvolCompression: null 50 | zvolDedup: null 51 | zvolEnableReservation: false 52 | zvolBlocksize: null 53 | iscsi: 54 | targetPortal: 10.0.6.1:3260 55 | interface: null 56 | namePrefix: csi- 57 | nameSuffix: -axis 58 | targetGroups: 59 | - targetGroupPortalGroup: 2 60 | targetGroupInitiatorGroup: 6 61 | targetGroupAuthType: None 62 | targetGroupAuthGroup: null 63 | extentInsecureTpc: true 64 | extentXenCompat: false 65 | extentDisablePhysicalBlocksize: true 66 | extentBlocksize: 512 67 | extentRpm: SSD 68 | extentAvailThreshold: 0 69 | # fix for https://github.com/democratic-csi/democratic-csi/issues/479 70 | controller: 71 | driver: 72 | enabled: true 73 | image: 74 | registry: docker.io/democraticcsi/democratic-csi 75 | tag: next 76 | # Talos-specific config - https://github.com/democratic-csi/democratic-csi?tab=readme-ov-file#talos 77 | node: 78 | hostPID: true 79 | driver: 80 | extraEnv: 81 | - name: ISCSIADM_HOST_STRATEGY 82 | value: nsenter 83 | - name: ISCSIADM_HOST_PATH 84 | value: /usr/local/sbin/iscsiadm 85 | iscsiDirHostPath: /var/iscsi 86 | iscsiDirHostPathType: "" 87 | sops: 88 | kms: [] 89 | gcp_kms: [] 90 | azure_kv: [] 91 | hc_vault: [] 92 | age: 93 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 94 | enc: | 95 | -----BEGIN AGE ENCRYPTED FILE----- 96 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMdEIrRUlOd0d0UGxicFNs 97 | TFpySEJBWUliZ2VEeXV6ZlplSloyMWx2K1JVCktBT3MwSEZObEZtbmo1TU51eXBh 98 | cXJ2YTYwUTdJSXN3Zm1wODRJN2NUelEKLS0tIHRJUEpnalJGNlcvN1lITUhwR09V 99 | aG1lekZzMHlIZzZHYkpMM0o3Z3ZEdlUK6NlHwyx7/zxG6G98+fadmn++9OFQgIvg 100 | oDZEM6in/XaPxJHLMbbH53AVr52CLSmvZxb8GwYgmSwgmhqDBdUQDg== 101 | -----END AGE ENCRYPTED FILE----- 102 | lastmodified: "2025-05-17T20:15:53Z" 103 | mac: ENC[AES256_GCM,data:7+7RA843/eg89QzjxQHCOZKqCSh7bGz6t8EhUj8g5nv0YmmtDxrFxDUUh6HPfx+rGCfQ1FcNP86ablRToUNaPElGvotWV7YiAuvDuIthG3BPiCCg7Fe26YJIFl9qST6SiZsFtHj9ra/K7qGgmceomiyFibLz2lDdOTi+wp5QpjU=,iv:7thlDxrJiqKyMeCye2cqzYNUdOTKa7VNuZ/GeG9AwkI=,tag:VontB/6XTEAMU7rG5zoU/w==,type:str] 104 | pgp: [] 105 | encrypted_regex: ^(data|stringData|email|host|apiKey)$ 106 | version: 3.9.4 107 | -------------------------------------------------------------------------------- /kubernetes/monitoring/grafana/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/helm.toolkit.fluxcd.io/helmrelease_v2.json 3 | apiVersion: helm.toolkit.fluxcd.io/v2 4 | kind: HelmRelease 5 | metadata: 6 | name: grafana 7 | namespace: monitoring 8 | spec: 9 | interval: 1h 10 | chartRef: 11 | kind: OCIRepository 12 | name: grafana 13 | values: 14 | deploymentStrategy: 15 | type: Recreate 16 | admin: 17 | existingSecret: grafana-admin-credentials 18 | env: 19 | GF_DATE_FORMATS_USE_BROWSER_LOCALE: true 20 | GF_EXPLORE_ENABLED: true 21 | GF_SECURITY_ANGULAR_SUPPORT_ENABLED: true 22 | GF_SERVER_ROOT_URL: https://grafana.axis.scottmckendry.tech 23 | grafana.ini: 24 | analytics: 25 | check_for_updates: false 26 | check_for_plugin_updates: false 27 | reporting_enabled: false 28 | news: 29 | news_feed_enabled: false 30 | dashboards: 31 | default_home_dashboard_path: /var/lib/grafana/dashboards/default/kubernetes-global.json 32 | datasources: 33 | datasources.yaml: 34 | apiVersion: 1 35 | deleteDatasources: 36 | - { name: Prometheus, orgId: 1 } 37 | - { name: Loki, orgId: 1 } 38 | - { name: Alertmanager, orgId: 1 } 39 | datasources: 40 | - name: Prometheus 41 | type: prometheus 42 | uid: prometheus 43 | access: proxy 44 | url: http://prometheus-operated.monitoring.svc.cluster.local:9090 45 | isDefault: true 46 | - name: Loki 47 | type: loki 48 | uid: loki 49 | access: proxy 50 | url: http://loki-headless.monitoring.svc.cluster.local:3100 51 | - name: Alertmanager 52 | type: alertmanager 53 | uid: alertmanager 54 | access: proxy 55 | url: http://alertmanager-operated.monitoring.svc.cluster.local:9093 56 | jsonData: 57 | implementation: prometheus 58 | dashboardProviders: 59 | dashboardproviders.yaml: 60 | apiVersion: 1 61 | providers: 62 | - name: default 63 | orgId: 1 64 | folder: "" 65 | type: file 66 | disableDeletion: false 67 | editable: true 68 | options: 69 | path: /var/lib/grafana/dashboards/default 70 | dashboards: 71 | default: 72 | cert-manager: 73 | # renovate: depName="Cert-manager-Kubernetes" 74 | gnetId: 20842 75 | revision: 3 76 | datasource: Prometheus 77 | kubernetes-api-server: 78 | # renovate: depName="Kubernetes / System / API Server" 79 | gnetId: 15761 80 | revision: 20 81 | datasource: Prometheus 82 | kubernetes-coredns: 83 | # renovate: depName="Kubernetes / System / CoreDNS" 84 | gnetId: 15762 85 | revision: 22 86 | datasource: Prometheus 87 | kubernetes-global: 88 | # renovate: depName="Kubernetes / Views / Global" 89 | gnetId: 15757 90 | revision: 43 91 | datasource: Prometheus 92 | kubernetes-namespaces: 93 | # renovate: depName="Kubernetes / Views / Namespaces" 94 | gnetId: 15758 95 | revision: 44 96 | datasource: Prometheus 97 | kubernetes-nodes: 98 | # renovate: depName="Kubernetes / Views / Nodes" 99 | gnetId: 15759 100 | revision: 40 101 | datasource: Prometheus 102 | kubernetes-pods: 103 | # renovate: depName="Kubernetes / Views / Pods" 104 | gnetId: 15760 105 | revision: 37 106 | datasource: Prometheus 107 | kubernetes-volumes: 108 | # renovate: depName="K8s / Storage / Volumes / Cluster" 109 | gnetId: 11454 110 | revision: 14 111 | datasource: Prometheus 112 | node-exporter-full: 113 | # renovate: depName="Node Exporter Full" 114 | gnetId: 1860 115 | revision: 42 116 | datasource: Prometheus 117 | prometheus: 118 | # renovate: depName="Prometheus" 119 | gnetId: 19105 120 | revision: 8 121 | datasource: Prometheus 122 | traefik: 123 | # renovate: depName="Traefik Official Kubernetes Dashboard" 124 | gnetId: 17347 125 | revision: 9 126 | datasource: Prometheus 127 | serviceMonitor: 128 | enabled: true 129 | persistence: 130 | enabled: false 131 | -------------------------------------------------------------------------------- /scripts/restore.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 🔄 Kubernetes Volume Restore Helper 4 | # Restores PVC data using VolSync ReplicationDestination 5 | 6 | # TODO: suspend flux reconciliations during restore 7 | 8 | set -euo pipefail 9 | 10 | # Colors and formatting 11 | RED='\033[0;31m' 12 | GREEN='\033[0;32m' 13 | BLUE='\033[0;34m' 14 | BOLD='\033[1m' 15 | MAGENTA='\033[0;35m' 16 | NC='\033[0m' # No Color 17 | 18 | # Default values 19 | NAME="" 20 | NAMESPACE="default" 21 | RESTORE_DATE="" 22 | MANAGE_PODS="false" 23 | RUNNER_ID="" 24 | 25 | # Parse command line arguments 26 | while [[ $# -gt 0 ]]; do 27 | case $1 in 28 | --name) 29 | NAME="$2" 30 | shift 2 31 | ;; 32 | --namespace) 33 | NAMESPACE="$2" 34 | shift 2 35 | ;; 36 | --restore-date) 37 | RESTORE_DATE="$2" 38 | shift 2 39 | ;; 40 | --runner-id) 41 | RUNNER_ID="$2" 42 | shift 2 43 | ;; 44 | --manage-pods) 45 | MANAGE_PODS="true" 46 | shift 47 | ;; 48 | *) 49 | echo -e "${RED}Error: Unknown parameter: $1${NC}" 50 | exit 1 51 | ;; 52 | esac 53 | done 54 | 55 | # Validate required parameters 56 | if [[ -z "$NAME" ]]; then 57 | echo -e "${RED}Error: Required parameters missing!${NC}" 58 | echo -e "Usage: $0 --name [--namespace ] [--restore-date ] [--runner-id ] [--manage-pods]" 59 | exit 1 60 | fi 61 | 62 | echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" 63 | echo -e "${BOLD}🔄 Starting volume restore process...${NC}" 64 | 65 | # Stop pods in NAME if requested 66 | if [[ "$MANAGE_PODS" == "true" ]]; then 67 | echo -e " ${GREEN}↳${NC} Scaling down deployments in ${BOLD}$NAMESPACE${NC}..." 68 | 69 | # Store current replica counts and scale down 70 | kubectl get deployment -n "$NAMESPACE" -o json | jq -r '.items[] | "\(.metadata.name) \(.spec.replicas)"' >/tmp/replica-counts.txt 71 | kubectl get deployment -n "$NAMESPACE" -o name | xargs -r kubectl scale -n "$NAMESPACE" --replicas=0 72 | 73 | # Wait for active (non-completed) pods to terminate before proceeding 74 | while true; do 75 | pod_count=$(kubectl get pods -n "$NAMESPACE" -o json | jq '[.items[] | select(.status.phase!="Succeeded" and .status.phase!="Failed")] | length') 76 | if [[ "$pod_count" -eq 0 ]]; then 77 | break 78 | fi 79 | echo -ne " ${GREEN}↳${NC} Waiting for active pods to terminate... ($pod_count remaining)\r" 80 | sleep 3 81 | done 82 | fi 83 | 84 | # Generate ReplicationDestination manifest 85 | echo -e " ${GREEN}↳${NC} Generating ReplicationDestination manifest..." 86 | cat </tmp/replication-dest.yaml 87 | apiVersion: volsync.backube/v1alpha1 88 | kind: ReplicationDestination 89 | metadata: 90 | name: ${NAME}-restore 91 | namespace: ${NAMESPACE} 92 | spec: 93 | trigger: 94 | manual: restore-once 95 | restic: 96 | repository: restic-${NAMESPACE}-${NAME} 97 | destinationPVC: ${NAME} 98 | copyMethod: Direct 99 | storageClassName: $(kubectl get pvc ${NAME} -n ${NAMESPACE} -o jsonpath='{.spec.storageClassName}') 100 | EOF 101 | 102 | # Add runner_id if specified 103 | if [[ -n "$RUNNER_ID" ]]; then 104 | echo " moverSecurityContext:" >>/tmp/replication-dest.yaml 105 | echo " runAsUser: ${RUNNER_ID}" >>/tmp/replication-dest.yaml 106 | echo " runAsGroup: ${RUNNER_ID}" >>/tmp/replication-dest.yaml 107 | echo " fsGroup: ${RUNNER_ID}" >>/tmp/replication-dest.yaml 108 | fi 109 | 110 | # Add restore date if specified 111 | if [[ -n "$RESTORE_DATE" ]]; then 112 | echo " restoreAsOf: \"${RESTORE_DATE}\"" >>/tmp/replication-dest.yaml 113 | fi 114 | 115 | # Apply the ReplicationDestination 116 | echo -e " ${GREEN}↳${NC} Applying ReplicationDestination..." 117 | kubectl apply -f /tmp/replication-dest.yaml 118 | 119 | # Wait for restore to complete 120 | echo -e " ${GREEN}↳${NC} RepicationDestination created. Waiting for restore to complete..." 121 | echo -e " 📝 ${MAGENTA}if this is taking longer than expected, check the ReplicationDestination with the following:" 122 | echo -e " kubectl describe replicationdestination ${NAME}-restore -n $NAMESPACE ${NC}\n" 123 | echo -ne " ${GREEN}↳${NC} Checking status" 124 | while true; do 125 | status=$(kubectl get replicationdestination "${NAME}-restore" -n "$NAMESPACE" -o jsonpath='{.status.latestMoverStatus.result}') 126 | if [[ "$status" == "Successful" ]]; then 127 | break 128 | fi 129 | echo -n "." 130 | sleep 5 131 | done 132 | echo -e "\n ${GREEN}✓${NC} Restore completed successfully!" 133 | 134 | # Show logs from the restore 135 | echo -e "\n${BOLD}📋 Restore logs:${NC}" 136 | logs=$(kubectl get replicationdestination "${NAME}-restore" -n "$NAMESPACE" -o jsonpath='{.status.latestMoverStatus.logs}') 137 | echo -e "$logs\n" 138 | 139 | # Cleanup ReplicationDestination 140 | echo -e " ${GREEN}↳${NC} Cleaning up ReplicationDestination..." 141 | kubectl delete -f /tmp/replication-dest.yaml 142 | 143 | # Start pods if they were stopped 144 | if [[ "$MANAGE_PODS" == "true" ]]; then 145 | echo -e " ${GREEN}↳${NC} Scaling up deployments in ${BOLD}$NAMESPACE${NC}..." 146 | # Restore original replica counts 147 | while read -r deploy replicas; do 148 | kubectl scale -n "$NAMESPACE" deployment/"$deploy" --replicas="$replicas" 149 | done \\S+) depName=(?\\S+)( repository=(?\\S+))?\\n.+(:\\s|=)(&\\S+\\s)?(?\\S+)', 200 | 'datasource=(?\\S+) depName=(?\\S+)\\n.+/(?(v|\\d)[^/]+)', 201 | ], 202 | datasourceTemplate: '{{#if datasource}}{{{datasource}}}{{else}}github-releases{{/if}}', 203 | }, 204 | { 205 | description: 'Process Grafana dashboards', 206 | customType: 'regex', 207 | managerFilePatterns: [ 208 | '/(^|/)kubernetes/.+\\.ya?ml$/', 209 | ], 210 | matchStrings: [ 211 | 'depName="(?.*)"\\n(?\\s+)gnetId: (?\\d+)\\n.+revision: (?\\d+)', 212 | ], 213 | autoReplaceStringTemplate: 'depName="{{{depName}}}"\n{{{indentation}}}gnetId: {{{packageName}}}\n{{{indentation}}}revision: {{{newValue}}}', 214 | datasourceTemplate: 'custom.grafana-dashboards', 215 | versioningTemplate: 'regex:^(?\\d+)$', 216 | }, 217 | ], 218 | } 219 | -------------------------------------------------------------------------------- /talos/talos.secret.sops.yaml: -------------------------------------------------------------------------------- 1 | cluster: 2 | id: ENC[AES256_GCM,data:x0x9FClhJdsY2rSdgy0H18O3AvBcRwZJHRz9IDWJz884/vW+W3C8jYLuB5c=,iv:eySrQ1cWzrQlyBvGliwj3Zgt2gF4KbxwzcqLJGvFEkw=,tag:+5avkIksht02pSD56BYsFQ==,type:str] 3 | secret: ENC[AES256_GCM,data:elK3fafCNSROsJRMifBve4b0GlYfoaLIbrg07KVwqHyNXVIdmCrWgPaaAS0=,iv:7tk4KEU+lfNi9FizwTihLy4fW/H2334u2GUOWwb7KTA=,tag:Cspi5XnElQiWfinVYQkGJQ==,type:str] 4 | secrets: 5 | bootstraptoken: ENC[AES256_GCM,data:SZT0d1qFSDqvpSRBqN/bQlcjuqigFjg=,iv:IEdjeLRfLPWyOheVuFfvZUF2/D0sZhJ0vCDtxKRnWyI=,tag:HgRM/yGP5/hpk+D0o9+SZw==,type:str] 6 | secretboxencryptionsecret: ENC[AES256_GCM,data:kuCJuUtFhN8nBhERRuU0AzYBd5LjeIcIgaRhhJDL3kkwcEH9N97bD3xPbOI=,iv:xCyFQy6EqQ5dX4WA9Y3La8HAlbzKasNDvuBOSU+jFxU=,tag:hIuRzaP2pKcmEtXewwfslg==,type:str] 7 | trustdinfo: 8 | token: ENC[AES256_GCM,data:XoAM6RPTPuFXzzdhHrrcGVFHDE6UVR8=,iv:wxUEc2MwcBCdpIfUxU0zakkINbhv+EF5mDk7iL7QlDA=,tag:G7+9Di2IO+/hXIm/+9377A==,type:str] 9 | certs: 10 | etcd: 11 | crt: ENC[AES256_GCM,data:dxYQaWtjThmXucF9141okhujdp4dm69XApoQ2vGgd2L1DvUcIZVxpwIMs0RfJYjTHi+gegCuOulnUTekQsv89iwnlevohWagadISJ3ljUgHA0tmhRRBEgiFmAxFDWJEVsanP6AxlnaJtpLAi71Tw55KaWVCx46GCH06p0YtsppXVOqckmhS17EDm7KO9yYQt5r33D4VccGvtKlzD2ROrgWnRQlLBmULAZpKVyfAB4m9DqpGL6kQ9umP1/CCqifIXhQBOTv12RiEH5y/SqxYtvc++49mnEqssfqePpl/fr44c0bZDQfpaD4VwHLBDpre8ZVfdTG/gY8ZGrJxRwF9BnG501Pxg3dZEsXk82UuHkrMOAGyUuA0z7qLsyoWqalUpnVF0cdrAFOdK9WbOk3Ovh8rTgwqX8nMmK2geSAAzye1F7rFaO5b6vgY7YPkjGnTuhXLFPz6v0ZZ8WQ4CgoaaMl+qW/P+RN47j2VLNVleplQICfC/YvN7aurxdMwTG6Jdd9qGwrAAUFb+643UAcPxZB/FadTm2mHgaWVyp1Q4R62a3mPl6orFL6Tag9yI8DJgC1fUN9uZja2az10Hhu7U96WuLzMY+tfYNVkuJMPfP7nvImJ4F3pbqoo9BzswJLPfeQ5SLYnbveVSBhVEhNbPzPltiQ/ThIFQAm5EM+UvdBy0VDOxf0TfuvqtJxr6Xo85XOfUM3unNo/JJPdm8pzT+x/ezpI66uYmaP6BnI+c4p4ZSTqd+/6PVzs53j7/AhcLvOt8OxFIO1LpXyuhSCN61U/1+3DXGL9e2UF1CQDf0wGyVtWuIrT/0bBCqU4w1TPEyqdRFZlYXbWJqYBY9aP27Do9ZqlzY3WTWddz9SSqBjZoLhynFIJyeTtcryrx3+zwthVwjFSnMcxv0XQ1quTOshFzTisQsog0cSJxZOUMnp/P97pJKm3+WG5ojJ2fJkb3xIbe4OKlLW2Y6Q7OrbHPJYFgZfHjEERTm3XBVH73HnJcgfqH0tqRrx6pzOh3ccrz,iv:eL6ReGb0xHmPFttLmrUKxZPHtkfkz7JDdCLCQwb8YeY=,tag:f68MxNY+QvuJlKBFubfvug==,type:str] 12 | key: ENC[AES256_GCM,data:rNCpyyqG70NniylxA9mUPskxj+L4wY113k0eyFaffm6glA/fgTjAe9RxPVIEaWX+SbfY+cbHeAIbKO5KH0S4M4QuI749BifbwRFYMM2dT62e97lEdussw8qpu7lxslPv+WTw330ti7w5EglsABJGc/Uy+g9N6/zf492nwr/TZAVJ2ipvy8crM+78ym5Tqgyhi1xKmnEdPhvwLIOcPYGOpO03qrP+dRRp2K1uW70cvRkAW+r9s5q8qfOBMfxqm5Yx+OFjFs/t/KSb5q58Le8Jr3Shhr9Mbu2QajBOuyHVCcnr1+bi0/rBrVvO0Veq0Ek2zwE4s2Pjz/8+YEx8QafT7dDojMf3viTjEWDj6mX8OORlf4EqIKEUHQZbJWJVcnd1dZXEgSbbHynsPlrXkVDdgQ==,iv:2XNhdprgDijYLvl8qi20TaasL/SN+CFtqos6Zz+BT4o=,tag:skjLpI+vI9v24ou36Bv3eg==,type:str] 13 | k8s: 14 | crt: ENC[AES256_GCM,data:row0scc9kr3vFRzqa75nKmA133wVTI8ME3dLNUnjTHzjHctPxzyC268WPi9azlQu4XYHN5JVEVZDRhuDBVQDIRylAgbHat8Pd48w/3hkpURKzWJQdFSNyV5duBG+L8kYmV5eo/pz+kTD7+fQke+B5aTBra7AdsPJil1+ZGekSmerTNsN5Zar9sZDhOflCUJq3AfpYKQ1eydMRiXW75BTYq770ArRqgx8GqC0RY/BmKyLZd37Z8+uk85N4EwrF7jFEJ+8n20X0wQAL6+YPJv16gPfAdRCIv5qdb/JrDAUPRO5aoHF4wmNn9MZu+1B5runw/M+A0hwqNYfmvMUQDizDRyLrNrzaMUtx2VadCVConLQcpXV0j//MKlsOdlaV7hjiAIxDE81bEuSGMlrjZiRGh5Izi2dulbyoxp4EXYY9V5A9fzERRpmPo0py6gDf2CfB8/er19S7ILl7eXOh+JiX9RFAn3G2ojdJwKkaYJS/OIFF8jjYE/QWOKL1tKRM7SG/Rd3VV6Hi2i4W8p2DV9sJRbpU8RIKgIV1YqwCeGiHhW/uixC6M402N8bG6Rlqt4sDEllvajOBpbzDECDVZpQ+6UJDq6R5/lveEhhyRiFYtIOgODGLjA+3sxiQPO/ZiilALC4zGQnaZ9PaPQzyd4MzNWLNzekvh3fHLMSNZ7thQEdifhUVCx4bFuvJQjOBrqI/LqCBZ3G8mavSHwBnFF27vWY2CPxYONXzM7M6vIvEqj8YzNEG2+pMG9dytQoCAKuV8aBzgUlwrBMoqjZiri59UGPblWZAyUnrG3C6WG+dGYGMs1WmFs6CQbf340ooyBQHHctK0y+e5eTTUuLzScYiBAFk3wiyCM1iDLnD5T3RJrf9ZysLuZbKLf4nGuTAzm+v+eqQRt2B/u9TenvODB/TUA+fXjZ0w6Kwq2ffutXyRkt8A8uWe0AiNNyJnYrGh+b2UXqUoAt7g40rs9zNy8fnBOtwyULkUnYGK5oVJ88gWnUtAAzZlU44vtbYcc9xBFK7oPwwWG/J/e7Fyofz5NEYnshkKVOsULfr5tn2A==,iv:dlB2f4mA5NYbazC84SaX2m0Hl1m8PD5ryWRwhcNPDHI=,tag:yPOBCmjxo/PndH9RyXq/jg==,type:str] 15 | key: ENC[AES256_GCM,data:3RihE5SGUs59pT0DqhgRKIqCQI/3vULfPhdsIZkmdcThg6N7bO/BlsfECqI10arOHh6S1DHdG7rKBl/1BuxoJB9B9I3njaDLDJFM1pGr7hOtCoKnEnkhJTSOZccFwqozDDJtXYd2jCsVSEFDB6cSxIqqBX1vFkuTJz6Cinp7W2PeSC2yiGc6QpNpOJc1vM+KzWvGnHnT+4mh7NyF8QzdvQ69F7rdBrrGNgZub5vUuPf+nidXF/aFfrdfCUZUEInDrT7UEzsfu+WD0dPMy9vwNldu/hc+tNsQSCvW8sxZbKxyEtVQ4LKA3qkGXDp1XOZXLX0omDSBImJ+8oOs0KPQc6HT6JrYAvoIGLFXWplP9a9/OdY0AiqKsy/9so9VIRMRmaGCNEqnHY1wHChs8LkFlw==,iv:JFQVDcVqEqy56s8F7/aQZpSxkqgPDoAbuL4P8P69qXQ=,tag:rKP0fYt3d9WEalxkiEnrNg==,type:str] 16 | k8saggregator: 17 | crt: ENC[AES256_GCM,data:8lDMo9LueKUJgCjmeFOa+zVLpptny0IvbzhDGDhZqJhpFVw2jadMTxb/7bu0gfJJ5zn6aT53MjLTpD4PPidRXDYGrirPU+j1bxRog2tl5c1TyurSRq54EVoJpFwY/N6BsAEeW+ROMF+SjpWNx2R2AxZt/Bus+HOhGqIc+9bFFQpaldmMygeQl6hStKJzzPdMW/r4ENCadSnHZIGaUtTKnGJKkMifUh4U44P6zdbFRRm4YEqu9DEF4ezQCNPE/iTDNOe+iYUuX21Alb+LHKG22MOOAJNMPrAqYwyAeDJJw0dau6+Bh9BPJr/YExxjxpipwl31+bV8V7I3A5Zxxio6xDOuCfS2T/gNzd9GcDswDCFWj4qpPFc/D11ghvi6OD1SljHJfb1GVRGF3MA2qu+V8d9+xjx0B8klAItz8WLuui1lcx+rfE9iZ2Jl4CSBaAnC0I/VBlsRj/Nqf9vNdgXwsRkoMoP2AqAb3rnqhQv0dRBQDF0MezmfB9FPc/XlFWhacLJAigmmyB0NawNrPorY1CXKStsDvgDWsIbm1AuXqSwQyTPdyazZInp8iDZPXoV7Iv57tzrPoyanYPSvlPiLPmud9b8gwoRJC3DOT0MzuVbDi41S+NHYqL+A+rLdbLAARKdPKjcPL3ng+LqbkRgM1ZNeS33d2GRQyI36nc762NGFwbjV31mbv7w/sBi97MT1Z/IeH53lB//DPVgTY+e0jX4t8yQE24cDGmQLBW219/jYvwLSOlYrj2D+vdJNqDxwZgpyS++nBOtvZndbhG2YTbZZ1NkndXpzB3JcwDYpZ/F+BU/VQWcyTadLQVtzo2uCT4EpHtS2k8h3WgaLqmXttGMYS4PEp3DxKGXH2SYrlu6mt8cUAV/rlNsX69Bip6q8f+PQkn94Ia1IRE+JMqNCtlKiaIAnlnasWPvcgoH6iVOxPYrfJwfA9TlsPXbe3T28,iv:qkOed545n/u78oSvuiZiuy+d9XYHAIHuT6QeRHYam7E=,tag:aUusEjq9uy7YvX2Wf2h2ZA==,type:str] 18 | key: ENC[AES256_GCM,data:pk3Pgj+MfnRHQtqHJ8NAsmzlxoiP3LmTlOX3+xf/0AlsOovdFNmsd1z53eC0snHz2DQ/gM2gM6GOagjez5s35pNZcsaYDXE9HmLWZv38iVh4arU4bc0+AiO6jDGisZE5iZoAyS5UQOmTWsPOEdxObo6UvkgRFm918fhpTpGt546lDn9P7Owir4ViynBPVU07IBVIHY57qd3SgRgFzUN0FZXiOeBjyLz3KAgoF4u2J5IBESgboJkd0cBE6FPoJYvmSUW2XBoxvaARm8kOVGB2ZCZ/ZC75rGdsWQYcS9tbij5BpWMkp/al4YqGQ82tEIJyESB0GFpui5ApucsBdvlBfHo08XdTZFuRh+yzPHKXwjsSdfJ4gVcmqFPn6O+ddC/wgrfCXGfdlQqvhnkMC9jO3Q==,iv:9ISlLsAf39uNhnAd+FptgdFeZMfs0ifz7KSSFOURBgY=,tag:Tgo3KqRe4gP4ygWnzQStRQ==,type:str] 19 | k8sserviceaccount: 20 | key: ENC[AES256_GCM,data:L+gVOnXrxoytQcQdDdCXuD65Zw610FilwTB/a1xiiELVDRftlhci/c/ZkYpr8NVb97dlUW1Ima+Gpo9ZXEP7tdoBWMrF5SkBOyIYcaUx21Xx4VB1dlv/1rKy9ej9SgYKYCIuoj9Sxr1tjmCjlSIRhXG5DkuCQDev2w6/6s5HlRzNxf9J6fbppG8U/zdbox7oTBkM5dkxRexHyEdH/+8YDd/7bFfpf2qxa18uuBJYM9TW3rGGfSGgaQh9qw1h5OSw7EawHHeE6xENi2rTxgn28bDwt7H2mRj3Gt/8NTdz5uuoS4miSutMI8yv3Mwv/x4quce0oRVEIvVqGf61usXibrwskcffUpc+Pkqlv6an+hfFcXNq2suJE/0C3+Sb8o5vJKrp0RBoHIbn/jhJ3sVfwsl1pZMTvBpamSBONOnTsK66fxGVyy8EWpAAj6fFpA5B9/izZQPVaRUopb6xHTGeoqbIfHh2DLRDTzpFjNbSeXFwOhsDIMDA3+ypYMCQ3BZ2sXzahnpHodE0x+z8TNLXat9xrvecdSmCkB2/W8p2qmx+TJFH2tlCRWhtLD0SuSbWZHdU5rwdzNuCNsSYu3tqjx6SR6TBxG06mIGZ20H3NXJy6FdgqTuzttR+xtbiHvYLw1SzgMYWpEjlqs2wt9iwB0TKLS8R7XNrACn547I9uhFThd7nOah6zVWC4vMtQfkeljSU8z5SOYhVxnQcQC5SNvyY/dOP+97Wc+Zgf7Y1KKFAbh3I3PtKt3mIPE/qZxov3RMSrACEjDmLPhm2gNnDTDU26u7HP6kacD7iIiCfLDh4lhjIcvjtrAxfq1AR4aw238dSAPHI2GRGBFirC0RPs+w0OK8vAYbSynScLm+eUlALKyNFXq2DauXx0W2eXDncI0BVLyqEi1t+W4rbRRowOWtiPQdEFM70MyRsSUPuLt4YcBwbWwr4wGVfKRfG6QmF9pBIl92JafLyU+QvgcTJ8ncWgFyhiTZFEGyfSS95hHx93J0GxUkZtrFiY/skLJprxxa1pBPNoeBQZYWDPafXTvhH60pHjcVWJNm/ccBbQZdU+mSopGzR5JJbkl98BM3+7Py02P1929gfXeRhAfnTWIf5Su75hkH3ffXwrA0s8J42PAhOXXobBvK664wP8/t3HrbETHb7Y1BXcyZwdd0xuWGRtZpEH2b8v3B69kR8jRyI4hIRgHERzF7oRBMxqkSX+OxYT1NqtqccMdDodj2+LPsEsRvRSTSD3zDrrvbSvvXOnnlvVYDKwxNx1PYs+oj7M1P9S5E/zBzHmump6hTVwwrl0qLNMmWKtFz66898zHtjw5MacirRYWU0ldVcVSoEmEDtH91WyBc+3GGo8dC7mJUikGjQ76J7RaBH5VTMp8GUKkhhBFlaarNQqgpy6xTxxgobf4CSpks5OXzmMCfh3sKkfzMdzo7Rby+rZCSZfJLTdvpUOQ+brmn5iRiT6DlAnN9A8ehDCkj4RXoUTQ/EI7zbvEIT4sf677EaNiGlUVJzKEsOA+67GZtOigzIrcWjpSFoO07KsR6P8aNhGETLN2odWLi/kPUHJd4IdDs7Cx2DeN4gEf6MwnuEGwXijiJI9mX34TXF+tr5rJzxYetuB8hMx1bEMzmYUfNRUH7RzWI9svkyKTWCFWjol/J27PX09GW+VZ3kNo0GDyec+UTS8kFPH7am817c4i9TfMrkG59StlJZxCDhloftm5tJLlq4SvSUB4uYqMKo1xXihfZk9hQF3MDzBbt3WhgmbysrWsaK28Ubdp133tXB6f/kU2zc0ofeSeVcmN4ML3n6fsFNiQwVpcGyElr6eoq2kvcz/4BBEKy1odxK1Fh9kDPbJhrTmRSmmHvde8dDwPdAtK1e0gM8Q6GTNGZywxPK2KHadcPv3HPRZ7W2x97jYK1sU9A4ZxnhWnK496RM5+jKr5eY04QsXg37SRBZrUdbi9giz5Fd7s1W6YpFGXCdZhxSgoXhJkCWFgd8bgJYjWGM57lMjcFtDixh/RLjfHHcjxaGmPMSLOCVhO9eXzF1vHxqjyNMeNEmj8jqZc6eRwd4shnYoYlHzD1tLd98BsjJfISpw+MzEh/2FEwapkLOPRUre0+tXRoot3IRBz3e/sRZS2jn8oBshDaxJq044M6rvwlpgAHHxHUxJ8BUt3y9Yk6v7UhpEPg16AVCZ+4qM6tEa71N6zBeAsf+yGsC9qt+tfuddpZsoF+XQhD+gHT3IrmQnmDkh0/JYWy8x8T+XagNiGeRFnYcfju7ck/wIxtF2ADD3BKJZFJaLkyRpayBW2iYLL2mViwELfbMygw814W3l60RQ5ERmMpV5CKfx1Gs5sS4DIX9fDPtGHOSsNcly7Haf/t2z+4/C1Rf0i8ABa0qmBvBwu4P7wOOzmaXlTmyVM8b196xsD/fyOe/2Mk/qtGEfBKKNLE1MPQbHetf7Uwhm1Z1BJq0Zff+lL8PpIeggJ4M7Ks32RIyRvqrWw3+lU0Bg39iMpjXKmvdZRvsuoBwv+KO4yglokrG9yV3lLjEAKEPWL6jFdlRj8JixbdNIGGfQVQNh4l+RhAnglMbwVGFmH9QOSkfv291FRAhYvU9qf9GBMq4xvjwUEYE60czdQB5gCsOTDqD/Es0qJ5NB8DgKAi/m9Q803CEjOL+33OAQmKilro02+Hn2AYhKe05bLEbeP/3/miHuwjDtmsPy+rEpAjZySMA8mVHif2nRdJ3jak4O1mcUJQRilyxDe74GOSQ5tzNbOWUJx9ByHsen91oG3YX4Vlm84vjHBIYNP0ZMt0xSB3byHAKJqxc2AWE2c8NloA6vOEj1KCUhpywFJu2ZvT665EwfgdKU1Li3/c3LEFZhURII5fGgGuUsGi9+ieL1yIC1lOp0DfqrXH3YlI26j5Y1uqEz/5GC7T7pxhkkjuvnI7ktnF0wx9C7+YO4Jzm2opNr+rCMnjEXKUuiaazJw1HowHxjKWyiJxrPVJuIkhU5i/YKVUS3pxHUB49R1bpT3rB5k6TvjyTZeYTgvSFSGYEWqx0LgW/99u2YWEQJy8ZKvabKxJTXb6AxpcOAl5nh8MO0NT5QeTLGVvdePp0789LyGQskNr8ICzRWB+SOpvVi5C3ECte1r2nyR2vv3m12tmgI7S9kApEhoBrjO/a0hTyQhEX5DYwlDt2TAnqKJHEWdsfqPhnTvAlKnAvepET+FVoLYO/Rwo3fB5u1EkrV74t/88OXlYdgFdKSALX3f0ODbx/o2fzejZdvgI1u4MDC11LQV/v16QUE0WNRDRaAbkE0fAVvxInoW1844p5anahNE11ZWCGhocMhGoX/udXzs9ojtGQmZ4PRCrxeOA1tA8MWRTSHQW1jymjy3fKdpHXeWk9fEi9ahbFzz7w1Te/MOo8Qsn8xvOV3L1OyT6O1bTUQKSXvngMZwTHF5+YvmsewgG8JC/mCy1IhwTlMO/xL9toWysXdKDp4fPgpIqZiGuqNiQAU0Rc+SXMcMSS0v5qcD4JY+Ne8bZilOb9W+7xh1Pp+wibSjGd7/j+0AE5sWC//aFRUrKxUP1b8ahw8BM5dr5FYy3rmtLWboznEr236mb0Bfppc8WmVExT0S8QP6oHRfhd1kctEq3sigkFBQr7iha5ZWVacBN0jhxr8e8UUfOuKNurlY9DJmVPTm9UnXP1nCfEhbrw553FYSieyzvE34XXzNMwPHE5pkkwrSteTkVCX0Aqm1mJup6FDbSQKg4Q6krE+7Val+3YMWYciKQKd5s8tyW78S8cZhbQkEFZo4N6lWW1+6iikVTei6hVdefnMc53qTAVKY8GcIDZacQRBtjBheWG0vMb2/8OCMEFo42BRHkA+d0iCr9J4zYxDEznjIfmGxg3G2ddwGQvjaBZDBJQL445n/mpVoPKnAwQivqlc9pXBaDfYGHgR0tQv3yoM04dCwSYgem9+6MYg3O55O0oN2NvsbZDsMnxIYLWINvawFdNatLQlB27Ing9zf5E6jyuhqR2WxkVfDKX173o5Pn4DfEAhVR5Ut2evu2eDP31ZeAUzPmTxhgPbA0P9uI+O5xs2h64ny2vr9uwWF30lkX5ThviymlfFDdxcJVtU0bL4x8ySwIzmlMvVtzHc7Y3hyX4Am3rx4kdPAgdr4JcqOm/zLADye0ZjEmFSfEuATPyO2blwptJamJ0LPoG4C2XYYfLd+1uExvAqsm2SAJ4rBaEme1pfHY2lEAjzSnMOxUF2I00GbZnxO3isAORALK2a5PifduzXfdgemZQLYcPFgA63M6iZ1G4TA9BhDUa6DDKjFfJ6FnR8/0zy8ZCccT5v24T0uRhKQZS28HSeDFx72xKfXCMI6VgDqHhAS3TaVdJjzeJh5BZMy52D+aN1/3KfeykhBD6TG1iLenniyDPy6sIVf4G5xBWZILEv/oQXL9A1JduY1ExKrv1vBdUYoELreSLATFbn0aP+If0zdaPJBl1qq8VCBU/Q4DHI765aPshsoJAh2XqlegagYNj2hvjzpx9ZwaQAapfkxpd7hQnt/qgcKG7MzDSbYTMR/LVWiNE8iaM02gZO4ASvmU7YeeVACtZhII7Sep7AM8hyrT0XPPyh6xTYOBJkszC6LAYbU2J6prHLwmaCo9C7bjRq6uOkVQolEAESN6T1hcnYRe5R14fSsskE6mUaDxKjbedHfAFLvf+uyLm0rNizKU/msjYWhc0GUpwcVBqEX7xpnmPrCmQ9CG7Ph1Fq1V0ySwZr5XE+CzQZSOonTRxZvvy8Q1BXiSZcr1PNDv13pzVMWx7R45WT+LAr6X4/RIL/tlBZVl6v1c6Zq5NUqLrRbaCO9n0F/urRSFUmJTxG7pq3zpcOBZs4b+7BamozD0C8yl1vCaJh04F38XtPlZHR2ZAgf+K2sRW+QRL3Qz2jrKFNbRgyxCbbhBo35k0TOZhWxEpyobODxJHXP/qGkHJK90bJ+z+5J2+CyavnzEAjX1hq+axq3v2YfazugdpqOMc+pvYsBGsrD3cUMpuI6JK3WVoD6kMHAcGUeHFND/uPl1b59RZnyDYLJUo0f8jrcHFtzo/d+WGfHpxAoNcdawTYIt0qTKCUrzKpDSEamJ6nYYtb0WMrPncEfKDXQfJDVLA7Uo0cHwZ+qn9spcQBCb+MQXgllrF42o2ai7dEyn132MPMAZLcqlORuNSyAq/JR28Jz0eFW0MoEAFX6e/DXzFUOsU5c1rphAS4APm5SzO8QnX2QisxbkcqxKQet5htqXMRDhXqtHVh0BvFvoEzb6kJaYJMv82tkO20cvH1OhmY/DlXJBK+lvBXgc8eduKWlYUOK5ZElhzlNwO120/B4/0weZBcapKA0Ci2a5Xgva9urKH0yGbUhjYvi6sLj7HASHb4Wydv9m9vzpiDjHd/ENaSM3Le8O3qSlzMPSTOA4TBiO89meParTGPNUxo/FB/SvghIRyGH4GJAUue+0QDJYxWIQ8fH1b8NtSF7XkpnjlFj1u9YxmRpquVI9PP0aer6XZjIWMDldR7UxvXDgOtdq0H0fbe0rZY02lPhaSyDIxvDI3MMDbtHCp8zg/RJgMD2FhRllMjD7A1f0RzcIGerwAIEHIUSIBphlj778P1bHNy+iMf7Wo1cOiC90J3qee0JW5TjyyltGWqL3Hz/mdb3Ysn8zboGIMIWUXlIxz5NSC8aGR9nKEentfckvYXLEkD9enCEyh82PoaPnFaZWWyeH/o68EiMvyUvp+iC40ZUAjJz1MzBlLCvHi5ihZqUZfHcCo7VtYuBwryP0+qA==,iv:IGb4qh9E2ffFnzRF2wvXjaibNT8coN+9FT/P9ytDUCk=,tag:GWYL8aft4H43aOqhKmURQA==,type:str] 21 | os: 22 | crt: ENC[AES256_GCM,data:OjRSaSpuIB8XLFmOFyKWFb/GuzuffSJVLJTKEg2OJpagd1Vbq4uqusnlr37uOxBCaJJKGEGVTXEVKbC5KRM8wVcRuMbPyNQEI5sCYUJ19DszOqFOg1DE5NtnLdkj4Bg60E21+L1HbFNKpSJxzCTvOtehY8F8ouKhrLITJ9vMs8AOt144HOYYlMW3bLe6Gtib3Dy8uoI+jPxZw/iowMh1E7uhMkN2ULk9yUfTggB61B/XoYXUChuAujgbY9yFjoptR6aTnNVGMi5E1nd1VKMGk/lH5asLPhFmUIG3WciceKUTK4TGWRpE1RJpw+h009ivieC6J5bi0E2EG4YYQWqBt6eUD0RdhSHX0xDFLToWPjtwNfEfG0TIw2ewFtLp+y4rRgk6ulB0nI5nxbRqIEb4InjRC0/mA8FF/xCBwe6oDTnBRTi4/8X7Gc2rjc+nY6I8wbSQ8o5KSifNesmDEmq04TY0Jwx5fNJH1JL/C4nBr2SM4S5y1HpUDVZMBPYjhhtvacK8Y8SWQDDgaZj45rEXo925VpD0JydjndPp7DOWnW9KW5b+TAaCIQLciwq6n1qHb5XW/TsRFW/NTpEiYuddCG+z9xhXxEVQr/9bFneXjSxJvYzrc1basEKcVSMxe4MURzqm2I8LHlaGhWYtseESCuIMxFdKc5WD1xUFfj97uPWGdDcn0G5X8+2Sb5BdbMk5maEpCV3CkW3I9rjmK7wwLYWfDO114Ad3HyAPipa/XYuYyP1HbJky+Cv52HQ2VmXcGTCdZlWF4Z9J3jvj7sLn92VU2BxDgTxj4VVuZvyYLI1Se4WLwsI1hFu8xy2TYZ5GL2Bv5NCTSSgebwTCi7YzIUcGfxLtizJAsp+13B2uAGFGwKDr,iv:FlGGA+oIhBu36dKfcYoPzVx6rr3G/Y4Jd7EW9PaL+XA=,tag:FQCeXeTGOUokNpxOckmSCw==,type:str] 23 | key: ENC[AES256_GCM,data:vEtjyEXDLcNEcNil7/qqak3+tfyxuvuC6ZERbWQxl3N8zcEkVpAYUv2VIIP0AzXSc1BDQFhbI4j1lca9JGvtYJspXoqoXNLQ71snKLM7YK6wezecZLgcarhDANWgXFcdH54Ax4V17S9cJ8L1x/fxeDn0L7aKAJdkFR23Siz4e0EApfAWhrhp2Fmzqf7o5lYYqYxW3EmJZY45eWmvvIplFp3dRCHRZqfJlG0Bm1to7gxjncCq,iv:75BLFq2CrRBxWvfvkoqdn13hoq9azUDD5c0QacAB02g=,tag:/phZCtrUrugqA7j6fByC+Q==,type:str] 24 | sops: 25 | kms: [] 26 | gcp_kms: [] 27 | azure_kv: [] 28 | hc_vault: [] 29 | age: 30 | - recipient: age1ht2mpnqakafawr4akvukz2ketzdlwuwkhdzwsy8hl7rwfkhkh90qp36und 31 | enc: | 32 | -----BEGIN AGE ENCRYPTED FILE----- 33 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCSWlyclQyTS9NcUY0Lzly 34 | MTYyL3FMV2pPQ09rRUtuZVFLeHR5Y3ZyazJnCldjckZKKzYyWGNTYnlzT0dBUk5D 35 | bWY3MTdiWDc0dENUem5OTGRxcDloMVEKLS0tIE91NWFTU0V4QmEwZkZNQzZ3WFpz 36 | UGpmN1k3NCt3bmN6ZlFkdHN4bjhZQlUKvsoa4bKFiql5EeSrVO/ILQCGV15es5yR 37 | Jp/vFkFAEM5JLE3aFUVZGtiXoRtiCnBiZQVFbpIVgnDWl2h48e9biQ== 38 | -----END AGE ENCRYPTED FILE----- 39 | lastmodified: "2025-04-05T23:29:03Z" 40 | mac: ENC[AES256_GCM,data:kSiFwX7z6kjXb1UvpzzBdcqqivz5enHjbDOi+6TTTeA/nSZLVHmm1GVd77RlrkEKqdNrGa9KeU36NVUPrNGfOEqZKEhgzuFnhDvqveRQHIc/OtKYM9UBQLLd6KwG9nHiMCwPf0EGgDhc/turC56T/f4FSvK9qkqojjYsKkEMMcM=,iv:dT8V1e5MCFnOh6ySCu4T2yI7Ht0rElGHZ3jk+bT8Eko=,tag:GomTfvORzWnHTdW1AZi4TQ==,type:str] 41 | pgp: [] 42 | encrypted_regex: ^.*(id|key|secret|password|token|passphrase|ca|crt|pem).*$ 43 | version: 3.9.4 44 | --------------------------------------------------------------------------------