├── .markdownlintignore ├── .yamllint ├── infrastructure ├── roles │ ├── ca │ │ ├── meta │ │ │ └── main.yml │ │ ├── defaults │ │ │ └── main.yml │ │ ├── files │ │ │ └── req.conf │ │ ├── vars │ │ │ └── main.yml │ │ └── tasks │ │ │ ├── main.yml │ │ │ └── directory.yml │ ├── containers │ │ ├── storage │ │ │ ├── vars │ │ │ │ └── main.yml │ │ │ └── defaults │ │ │ │ └── main.yml │ │ ├── network │ │ │ ├── flannel │ │ │ │ ├── files │ │ │ │ │ └── .gitkeep │ │ │ │ ├── meta │ │ │ │ │ └── main.yml │ │ │ │ └── tasks │ │ │ │ │ └── reset.yml │ │ │ ├── cilium │ │ │ │ ├── templates │ │ │ │ │ ├── cilium │ │ │ │ │ │ ├── sa.yml.j2 │ │ │ │ │ │ ├── secret.yml.j2 │ │ │ │ │ │ └── crb.yml.j2 │ │ │ │ │ ├── cilium-operator │ │ │ │ │ │ ├── sa.yml.j2 │ │ │ │ │ │ └── crb.yml.j2 │ │ │ │ │ ├── 000-cilium-portmap.conflist.j2 │ │ │ │ │ └── hubble │ │ │ │ │ │ └── sa.yml.j2 │ │ │ │ └── tasks │ │ │ │ │ ├── main.yml │ │ │ │ │ ├── reset.yml │ │ │ │ │ └── reset_iface.yml │ │ │ └── meta │ │ │ │ └── main.yml │ │ ├── runtime │ │ │ ├── files │ │ │ │ └── mounts.conf │ │ │ ├── meta │ │ │ │ └── main.yml │ │ │ ├── vars │ │ │ │ └── main.yml │ │ │ ├── templates │ │ │ │ ├── crictl.yaml.j2 │ │ │ │ ├── http-proxy.conf.j2 │ │ │ │ ├── config.json.j2 │ │ │ │ ├── unqualified.conf.j2 │ │ │ │ └── registry.conf.j2 │ │ │ ├── handlers │ │ │ │ └── main.yml │ │ │ └── tasks │ │ │ │ └── reset.yml │ │ └── registry │ │ │ └── templates │ │ │ ├── registry-sa.yml.j2 │ │ │ ├── registry-ns.yml.j2 │ │ │ ├── registry-secrets.yml.j2 │ │ │ ├── registry-cm.yml.j2 │ │ │ ├── registry-cr.yml.j2 │ │ │ ├── registry-crb.yml.j2 │ │ │ ├── registry-pvc.yml.j2 │ │ │ ├── registry-ing.yml.j2 │ │ │ └── registry-svc.yml.j2 │ ├── apps │ │ ├── templates │ │ │ ├── pvc.yaml.j2 │ │ │ ├── storage.yaml.j2 │ │ │ ├── application.yaml.j2 │ │ │ ├── kustomization.yaml.j2 │ │ │ └── secret-generator.yaml.j2 │ │ ├── meta │ │ │ ├── main.yaml │ │ │ └── argument_specs.yaml │ │ ├── tasks │ │ │ ├── pre-start │ │ │ │ ├── keycloak.yml │ │ │ │ ├── linkace.yml │ │ │ │ ├── nextcloud.yml │ │ │ │ ├── linkding.yml │ │ │ │ ├── mealie.yml │ │ │ │ ├── wger.yml │ │ │ │ ├── paperless-ngx.yml │ │ │ │ ├── photoprism.yml │ │ │ │ ├── wallabag.yml │ │ │ │ ├── immich.yml │ │ │ │ ├── bazarr.yml │ │ │ │ ├── cert-manager.yml │ │ │ │ ├── lidarr.yml │ │ │ │ ├── radarr.yml │ │ │ │ ├── sonarr.yml │ │ │ │ ├── prowlarr.yml │ │ │ │ ├── readarr.yml │ │ │ │ ├── linkwarden.yml │ │ │ │ └── homepage.yml │ │ │ ├── reset.yml │ │ │ └── post-launch │ │ │ │ ├── ingress-nginx.yml │ │ │ │ └── cloudnative-pg.yml │ │ └── README.md │ ├── common │ │ ├── meta │ │ │ └── main.yml │ │ ├── tasks │ │ │ ├── main.yml │ │ │ └── os_vars.yml │ │ └── vars │ │ │ ├── ubuntu.yml │ │ │ ├── os.yml │ │ │ └── debian.yml │ ├── server │ │ ├── meta │ │ │ └── main.yml │ │ ├── kvm │ │ │ ├── meta │ │ │ │ └── main.yml │ │ │ └── templates │ │ │ │ ├── libvirt_dir_pool.xml.j2 │ │ │ │ └── libvirt_rbd_pool.xml.j2 │ │ ├── nvr │ │ │ ├── meta │ │ │ │ └── main.yml │ │ │ └── tasks │ │ │ │ └── main.yml │ │ └── tasks │ │ │ └── partitions.yml │ ├── dns │ │ └── meta │ │ │ └── main.yml │ ├── workstation │ │ ├── meta │ │ │ └── main.yml │ │ ├── vars │ │ │ └── main.yml │ │ └── tasks │ │ │ ├── terraform.yml │ │ │ ├── main.yml │ │ │ └── debian.yml │ ├── kubernetes │ │ ├── node │ │ │ ├── meta │ │ │ │ └── main.yml │ │ │ └── tasks │ │ │ │ ├── debian.yml │ │ │ │ ├── redhat.yml │ │ │ │ └── accounts.yml │ │ ├── control_plane │ │ │ ├── meta │ │ │ │ └── main.yml │ │ │ ├── tasks │ │ │ │ ├── reset.yml │ │ │ │ └── approve-cert.yml │ │ │ ├── templates │ │ │ │ └── csr.yml.j2 │ │ │ └── defaults │ │ │ │ └── main │ │ │ │ └── etcd.yml │ │ ├── client │ │ │ └── defaults │ │ │ │ └── main.yml │ │ └── kubelet │ │ │ ├── handlers │ │ │ └── main.yml │ │ │ ├── files │ │ │ ├── kubelet.service │ │ │ └── 10-kubeadm.conf │ │ │ └── templates │ │ │ └── node-kubeconfig.yaml.j2 │ ├── opnsense │ │ └── meta │ │ │ └── main.yml │ ├── download │ │ ├── meta │ │ │ └── main.yml │ │ ├── templates │ │ │ └── kubeadm-images.yaml.j2 │ │ └── tasks │ │ │ └── prep_download.yml │ ├── crypto_device │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ ├── close.yml │ │ │ └── open.yml │ ├── dvb │ │ ├── files │ │ │ └── environment.conf │ │ └── tasks │ │ │ ├── main.yml │ │ │ └── build.yml │ └── user │ │ ├── templates │ │ ├── crb.yml.j2 │ │ └── kubeconfig.yml.j2 │ │ ├── README.md │ │ └── meta │ │ └── argument_specs.yaml ├── inventory │ ├── host_vars │ │ ├── parche │ │ │ ├── config.yml │ │ │ ├── packages.yml │ │ │ └── node-labels.yml │ │ ├── localhost │ │ │ └── packages.yml │ │ └── tirante │ │ │ └── packages.yml │ ├── inventory.k8s.yaml │ ├── group_vars │ │ ├── kubectl │ │ │ └── 05-container-storage.yml │ │ ├── workstation │ │ │ └── packages.yaml │ │ ├── all │ │ │ ├── git-repo.yml │ │ │ ├── pki │ │ │ │ └── 00-all.yml │ │ │ └── 00-all.yml │ │ └── ipaserver │ │ │ └── config.yml │ └── libvirt.yaml ├── terraform │ ├── ipa │ │ ├── provider.tf │ │ ├── main.tf │ │ ├── ipa_network_config │ │ ├── variables.tf │ │ ├── ipa.tf │ │ ├── resources.tf │ │ └── ipa_cloud_init.tftpl │ └── kvm │ │ ├── main.tf │ │ ├── resources.tf │ │ └── README.md └── _shared │ └── networks.yaml ├── .markdownlint.yaml ├── cluster ├── apps │ ├── services │ │ ├── homepage │ │ │ ├── config │ │ │ │ ├── docker.yaml │ │ │ │ ├── bookmarks.yaml │ │ │ │ ├── kubernetes.yaml │ │ │ │ ├── settings.yaml │ │ │ │ └── widgets.yaml │ │ │ ├── secret-generator.yaml │ │ │ └── kustomization.yaml │ │ ├── wger │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ └── values.yaml │ │ ├── linkace │ │ │ ├── secret-generator.yaml │ │ │ └── kustomization.yaml │ │ ├── linkding │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ └── linkding.svg │ │ ├── wallabag │ │ │ ├── secret-generator.yaml │ │ │ └── kustomization.yaml │ │ ├── linkwarden │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ └── storage.yaml │ │ └── stirling-pdf │ │ │ └── values.yaml │ ├── db │ │ ├── mysql │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ ├── values.yaml │ │ │ └── storage.yaml │ │ ├── cloudnative-pg │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ ├── Dockerfile │ │ │ ├── service.yaml │ │ │ ├── values.yaml │ │ │ └── cluster.yaml │ │ └── redis │ │ │ └── values.yaml │ ├── home │ │ ├── mealie │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ └── storage.yaml │ │ ├── paperless-ngx │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ ├── hp-scan-to-cm.yaml │ │ │ └── storage.yaml │ │ ├── mosquitto │ │ │ ├── mosquitto.conf │ │ │ ├── secret-generator.yaml │ │ │ └── kustomization.yaml │ │ ├── grocy │ │ │ ├── svc.yaml │ │ │ ├── cm.yaml │ │ │ ├── kustomization.yaml │ │ │ ├── ingress.yaml │ │ │ ├── README.md │ │ │ └── storage.yaml │ │ ├── homebox │ │ │ └── storage.yaml │ │ ├── zwave-js-ui │ │ │ └── storage.yaml │ │ └── home-assistant │ │ │ └── storage.yaml │ ├── media │ │ ├── immich │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ └── storage.yaml │ │ ├── photoprism │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ └── storage.yaml │ │ ├── calibre │ │ │ └── storage.yaml │ │ └── jellyfin │ │ │ └── storage.yaml │ ├── downloads │ │ ├── bazarr │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ ├── scripts │ │ │ │ └── post-process.sh │ │ │ └── storage.yaml │ │ ├── lidarr │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ └── storage.yaml │ │ ├── radarr │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ └── storage.yaml │ │ ├── sonarr │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ └── storage.yaml │ │ ├── prowlarr │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ └── storage.yaml │ │ └── readarr │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ └── storage.yaml │ ├── infrastructure │ │ ├── netbox │ │ │ ├── secret-generator.yaml │ │ │ ├── graphite │ │ │ │ ├── kustomization.yaml │ │ │ │ ├── service.yaml │ │ │ │ └── ingress.yaml │ │ │ └── kustomization.yaml │ │ ├── keycloak │ │ │ ├── secret-generator.yaml │ │ │ ├── keycloak.yaml │ │ │ └── kustomization.yaml │ │ ├── cert-manager │ │ │ ├── cluster-issuer.yaml │ │ │ ├── secret-generator.yaml │ │ │ ├── kustomization.yaml │ │ │ ├── values.yaml │ │ │ ├── issuer-letsencrypt-prod.yaml │ │ │ └── issuer-letsencrypt-staging.yaml │ │ ├── external-dns │ │ │ ├── secret-generator.yaml │ │ │ └── kustomization.yaml │ │ ├── oauth2-proxy │ │ │ ├── secret-generator.yaml │ │ │ └── kustomization.yaml │ │ ├── kubeshark │ │ │ └── values.yaml │ │ ├── traefik │ │ │ ├── traefik-forward-auth │ │ │ │ ├── configs │ │ │ │ │ └── traefik-forward-auth.ini │ │ │ │ ├── service.yaml │ │ │ │ ├── middleware.yaml │ │ │ │ └── kustomization.yaml │ │ │ ├── tlsstore.yaml │ │ │ ├── middleware.yaml │ │ │ ├── ingress-route.yaml │ │ │ └── values.yaml │ │ ├── reloader │ │ │ └── values.yaml │ │ ├── metallb │ │ │ ├── layer2.yaml │ │ │ └── pools.yaml │ │ ├── cloudflared │ │ │ └── config.yaml │ │ ├── rook-ceph │ │ │ └── README.md │ │ ├── ingress-nginx │ │ │ ├── wildcard-certificate.yaml │ │ │ └── external-wildcard-certificate.yaml │ │ └── local-path-provisioner │ │ │ └── storage-classes.yaml │ └── monitoring │ │ └── kube-prometheus-stack │ │ └── storage.yaml ├── kube-system │ ├── intel-device-plugin │ │ ├── values.yaml │ │ └── kustomization.yaml │ └── node-feature-discovery │ │ ├── values.yaml │ │ ├── google-coral-device.yaml │ │ └── zooz-zwave-device.yaml ├── argocd │ └── kustomization.yaml ├── bootstrap │ ├── infrastructure │ │ ├── keycloak.yaml │ │ ├── local-path-provisioner.yaml │ │ ├── reflector.yaml │ │ ├── oauth2-proxy.yaml │ │ ├── traefik.yaml │ │ ├── cert-manager.yaml │ │ ├── external-dns.yaml │ │ ├── ingress-nginx.yaml │ │ ├── netbox.yaml │ │ └── reloader.yaml │ ├── argocd.yaml │ ├── argocd │ │ └── argocd.yaml │ ├── services │ │ ├── wger.yaml │ │ ├── homepage.yaml │ │ ├── linkace.yaml │ │ ├── linkding.yaml │ │ ├── wallabag.yaml │ │ ├── stirling-pdf.yaml │ │ ├── change-detection.yaml │ │ └── linkwarden.yaml │ ├── home │ │ ├── owntracks.yaml │ │ ├── grocy.yaml │ │ ├── mosquitto.yaml │ │ ├── homebox.yaml │ │ ├── nvr.yaml │ │ ├── zwave-js-ui.yaml │ │ ├── home-assistant.yaml │ │ └── mealie.yaml │ ├── media │ │ ├── navidrome.yaml │ │ ├── immich.yaml │ │ ├── calibre.yaml │ │ ├── photoprism.yaml │ │ └── audiobookshelf.yaml │ ├── db │ │ ├── redis.yaml │ │ ├── cloudnative-pg.yaml │ │ └── mysql.yaml │ ├── kube-system │ │ ├── node-feature-discovery.yaml │ │ └── intel-device-plugin.yaml │ └── downloads │ │ ├── bazarr.yaml │ │ ├── lidarr.yaml │ │ ├── radarr.yaml │ │ ├── sonarr.yaml │ │ ├── readarr.yaml │ │ ├── prowlarr.yaml │ │ └── qbittorrent.yaml └── cluster.yaml ├── docs └── Homelab.png ├── .gitconfig ├── .gitleaks.toml ├── scripts ├── kustomize ├── references.sh └── install_helm.sh ├── .envrc ├── .gitattributes ├── playbooks ├── storage.yml ├── network.yml ├── apps.yml ├── host.yml └── reset.yml ├── .hooks └── post-checkout ├── .sops.yaml ├── requirements.txt ├── homelab.yml ├── requirements.yml ├── LICENSE.md ├── .github ├── linters │ ├── .markdownlint.yaml │ ├── .yamllint │ └── .ansible-lint └── workflows │ └── linters.yaml └── TODO.md /.markdownlintignore: -------------------------------------------------------------------------------- 1 | LICENSE.md 2 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | .github/linters/.yamllint -------------------------------------------------------------------------------- /infrastructure/roles/ca/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | .github/linters/.markdownlint.yaml -------------------------------------------------------------------------------- /infrastructure/inventory/host_vars/parche/config.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/storage/vars/main.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cluster/apps/services/homepage/config/docker.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /cluster/apps/services/homepage/config/bookmarks.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/network/flannel/files/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/templates/pvc.yaml.j2: -------------------------------------------------------------------------------- 1 | {{ app | pvc}} 2 | -------------------------------------------------------------------------------- /infrastructure/inventory/inventory.k8s.yaml: -------------------------------------------------------------------------------- 1 | plugin: kubernetes.core.k8s 2 | -------------------------------------------------------------------------------- /infrastructure/roles/common/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | allow_duplicates: true 3 | -------------------------------------------------------------------------------- /cluster/apps/services/homepage/config/kubernetes.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | mode: cluster 3 | -------------------------------------------------------------------------------- /infrastructure/roles/server/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: common 4 | -------------------------------------------------------------------------------- /docs/Homelab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clearlybaffled/homelab/HEAD/docs/Homelab.png -------------------------------------------------------------------------------- /infrastructure/roles/apps/meta/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | collections: 3 | - "lvrfrc87.git_acp" 4 | -------------------------------------------------------------------------------- /infrastructure/roles/dns/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - robertdebock.dns 4 | -------------------------------------------------------------------------------- /infrastructure/roles/server/kvm/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: common 4 | -------------------------------------------------------------------------------- /infrastructure/roles/server/nvr/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: common 4 | -------------------------------------------------------------------------------- /infrastructure/roles/workstation/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: common 4 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [merge "ours"] 2 | driver = true 3 | [diff "sopsdiffer"] 4 | textconv = sops -d 5 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/runtime/files/mounts.conf: -------------------------------------------------------------------------------- 1 | /usr/share/rhel/secrets:/run/secrets 2 | -------------------------------------------------------------------------------- /infrastructure/roles/kubernetes/node/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: common 4 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/runtime/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: common 4 | -------------------------------------------------------------------------------- /infrastructure/roles/opnsense/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # dependencies: 3 | # - ansibleguy.opnsense 4 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/network/flannel/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: common 4 | -------------------------------------------------------------------------------- /infrastructure/roles/kubernetes/control_plane/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: common 4 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/templates/storage.yaml.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | {{ app | storage }} 3 | -------------------------------------------------------------------------------- /infrastructure/roles/ca/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | pki_home: /srv/pki 3 | 4 | root_ca_cn: Hermleigh Home Root CA 5 | -------------------------------------------------------------------------------- /infrastructure/roles/common/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: include os var file 3 | include_tasks: 'os_vars.yml' 4 | -------------------------------------------------------------------------------- /infrastructure/roles/download/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | allow_duplicates: true 3 | dependencies: 4 | - role: common 5 | -------------------------------------------------------------------------------- /.gitleaks.toml: -------------------------------------------------------------------------------- 1 | [allowlist] 2 | description = "global allow list" 3 | paths = [ 4 | '''(.*?)\.sops\.yaml$''' 5 | ] 6 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/templates/application.yaml.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | {{ app | application }} 3 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/templates/kustomization.yaml.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | {{ app | kustomization }} 3 | -------------------------------------------------------------------------------- /infrastructure/roles/workstation/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | hashicorp_repo_key_url: https://apt.releases.hashicorp.com/gpg 3 | -------------------------------------------------------------------------------- /cluster/kube-system/intel-device-plugin/values.yaml: -------------------------------------------------------------------------------- 1 | name: intel-gpu-plugin 2 | sharedDevNum: 3 3 | nodeFeatureRule: true 4 | -------------------------------------------------------------------------------- /infrastructure/inventory/group_vars/kubectl/05-container-storage.yml: -------------------------------------------------------------------------------- 1 | --- 2 | local_storage_root_dir: /srv/cluster/volumes 3 | -------------------------------------------------------------------------------- /scripts/kustomize: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | /usr/local/bin/kustomize "$1" --enable-helm --enable-alpha-plugins --enable-exec "$2" 4 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/runtime/vars/main.yml: -------------------------------------------------------------------------------- 1 | crio_graphroot: /containers/storage 2 | crio_runroot: /containers/storage 3 | -------------------------------------------------------------------------------- /infrastructure/roles/server/nvr/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: nvr | Install coral driver 2 | include_tasks: 3 | file: coral.yml 4 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | export SOPS_AGE_KEY_FILE=$HOME/.config/sops/age/keys.txt 2 | PATH_add ./scripts 3 | export VIRTUAL_ENV=.venv 4 | layout python3 5 | -------------------------------------------------------------------------------- /infrastructure/inventory/group_vars/workstation/packages.yaml: -------------------------------------------------------------------------------- 1 | debian: 2 | - postgresql 3 | - libpq-dev 4 | - libvirt-clients 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sops.* diff=sopsdiffer 2 | cluster/bootstrap/** merge=ours 3 | infrastructure/inventory/group_vars/all/git-repo.yml merge=ours 4 | -------------------------------------------------------------------------------- /infrastructure/roles/kubernetes/node/tasks/debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: debian | Enable fstrim 3 | systemd: 4 | service: fstrim.timer 5 | enabled: true 6 | -------------------------------------------------------------------------------- /infrastructure/inventory/host_vars/localhost/packages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | local_packages: 3 | - mysql-common 4 | - postgresql-client 5 | - shellcheck 6 | - shfmt 7 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/runtime/templates/crictl.yaml.j2: -------------------------------------------------------------------------------- 1 | runtime-endpoint: {{ cri_socket }} 2 | image-endpoint: {{ cri_socket }} 3 | timeout: 30 4 | debug: false 5 | -------------------------------------------------------------------------------- /infrastructure/terraform/ipa/provider.tf: -------------------------------------------------------------------------------- 1 | provider "libvirt" { 2 | uri = "qemu+ssh://ansible@parche/system?keyfile=$HOME/.ssh/ansible" 3 | } 4 | 5 | provider "sops" {} 6 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/network/cilium/templates/cilium/sa.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: cilium 6 | namespace: kube-system 7 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/registry/templates/registry-sa.yml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: registry 5 | namespace: {{ registry_namespace }} 6 | -------------------------------------------------------------------------------- /cluster/apps/db/mysql/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: mysql-secret-generator 5 | namespace: db 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/home/mealie/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: mealie-secret-generator 5 | namespace: home 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/media/immich/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: immich-secret-generator 5 | namespace: media 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/services/wger/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: wger-secret-generator 5 | namespace: services 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /infrastructure/inventory/group_vars/all/git-repo.yml: -------------------------------------------------------------------------------- 1 | --- 2 | git_repo_url: https://github.com/clearlybaffled/homelab 3 | git_branch: main 4 | git_repo_ssh_url: git@github.com:clearlybaffled/homelab.git 5 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/network/cilium/templates/cilium-operator/sa.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: cilium-operator 6 | namespace: kube-system 7 | -------------------------------------------------------------------------------- /cluster/apps/downloads/bazarr/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: bazarr-secret-generator 5 | namespace: downloads 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/downloads/lidarr/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: lidarr-secret-generator 5 | namespace: downloads 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/downloads/radarr/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: radarr-secret-generator 5 | namespace: downloads 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/downloads/sonarr/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: sonarr-secret-generator 5 | namespace: downloads 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/media/photoprism/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: photoprism-secret-generator 5 | namespace: media 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/services/linkace/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: linkace-secret-generator 5 | namespace: services 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/runtime/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Restart crio 3 | command: /bin/true 4 | changed_when: false 5 | notify: 6 | - Reload systemd 7 | - CRI-O | reload crio 8 | -------------------------------------------------------------------------------- /playbooks/storage.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Generate application manifest files 3 | become: false 4 | hosts: localhost 5 | connection: local 6 | gather_facts: true 7 | roles: 8 | - containers/storage 9 | -------------------------------------------------------------------------------- /cluster/apps/downloads/prowlarr/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: prowlarr-secret-generator 5 | namespace: downloads 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/downloads/readarr/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: readarr-secret-generator 5 | namespace: downloads 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/home/paperless-ngx/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: paperless-ngx-secret-generator 5 | namespace: home 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/services/linkding/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: linkding-secret-generator 5 | namespace: services 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/services/wallabag/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: wallabag-secret-generator 5 | namespace: services 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /infrastructure/roles/crypto_device/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | crypt_name: pki 3 | crypt_mount_path: "{{ ('/srv', crypt_name)| path_join }}" 4 | crypt_mapping_file: "{{ ('/dev','mapper', crypt_name) | path_join }}" 5 | -------------------------------------------------------------------------------- /playbooks/network.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test connection 3 | hosts: ios 4 | gather_facts: false 5 | roles: 6 | - role: network 7 | vars: 8 | ansible_become_password: "{{ enable_password }}" 9 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/netbox/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: netbox-secret-generator 5 | namespace: infrastructure 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/services/wger/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: services 7 | generators: 8 | - ./secret-generator.yaml 9 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/registry/templates/registry-ns.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: {{ registry_namespace }} 6 | labels: 7 | name: {{ registry_namespace }} 8 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/keycloak/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: keycloak-secret-generator 5 | namespace: infrastructure 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/netbox/graphite/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: infrastructure 4 | resources: 5 | - ./ingress.yaml 6 | - ./service.yaml 7 | -------------------------------------------------------------------------------- /cluster/apps/services/linkding/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: services 7 | generators: 8 | - ./secret-generator.yaml 9 | -------------------------------------------------------------------------------- /cluster/apps/services/wallabag/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: services 7 | generators: 8 | - ./secret-generator.yaml 9 | -------------------------------------------------------------------------------- /infrastructure/inventory/libvirt.yaml: -------------------------------------------------------------------------------- 1 | # Connect to qemu 2 | plugin: community.libvirt.libvirt 3 | uri: 'qemu+ssh://ansible@parche/system?keyfile=~/.ssh/ansible' 4 | groups: 5 | ipaserver: inventory_hostname.startswith('tang') 6 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/cert-manager/cluster-issuer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: ca-issuer 5 | namespace: cert-manager 6 | spec: 7 | ca: 8 | secretName: ca-key-pair 9 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/external-dns/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: external-dns-secret-generator 5 | namespace: infrastructure 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/oauth2-proxy/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: oauth2-proxy-secret-generator 5 | namespace: infrastructure 6 | files: 7 | - ./secret.sops.yaml 8 | -------------------------------------------------------------------------------- /infrastructure/roles/common/vars/ubuntu.yml: -------------------------------------------------------------------------------- 1 | --- 2 | system_packages: 3 | - ipvsadm 4 | - lm-sensors 5 | - nfs-common 6 | - nvme-cli 7 | 8 | k8s_required_packages: 9 | - python3-kubernetes 10 | - python3-yaml 11 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/external-dns/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: infrastructure 7 | generators: 8 | - ./secret-generator.yaml 9 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/kubeshark/values.yaml: -------------------------------------------------------------------------------- 1 | tap: 2 | ingress: 3 | enabled: true 4 | host: &host kubeshark.hermleigh.home 5 | tls: 6 | - hosts: 7 | - *host 8 | secretName: kubeshark-ingress-tls 9 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/oauth2-proxy/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: infrastructure 7 | generators: 8 | - ./secret-generator.yaml 9 | -------------------------------------------------------------------------------- /cluster/apps/services/homepage/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: homepage-secret-generator 5 | namespace: homepage 6 | files: 7 | - ./secret.sops.yaml 8 | - ./app-keys.sops.yaml 9 | -------------------------------------------------------------------------------- /cluster/apps/db/cloudnative-pg/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: cloudnative-pg-secret-generator 5 | namespace: db 6 | files: 7 | - ./superuser.sops.yaml 8 | - ./app-user.sops.yaml 9 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/cert-manager/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: cert-manager-secret-generator 5 | namespace: cert-manager 6 | files: 7 | - ./cloudflare-api-token.sops.yaml 8 | -------------------------------------------------------------------------------- /cluster/apps/services/linkwarden/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: linkwarden-secret-generator 5 | namespace: services 6 | files: 7 | - ./secret.sops.yaml 8 | - ./db_url.sops.yaml 9 | -------------------------------------------------------------------------------- /.hooks/post-checkout: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Need to run `git config --global core.hookspath .hooks` once per host before this works 3 | git config --local include.path ../.gitconfig 4 | # Reference: https://github.com/stefanhoelzl/share-git-hooks-and-config 5 | -------------------------------------------------------------------------------- /cluster/apps/db/mysql/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: db 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./storage.yaml 11 | -------------------------------------------------------------------------------- /cluster/apps/home/mealie/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: home 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./storage.yaml 11 | -------------------------------------------------------------------------------- /infrastructure/roles/ca/files/req.conf: -------------------------------------------------------------------------------- 1 | [req] 2 | default_bits = 4096 3 | encrypt_key = yes 4 | default_md = sha512 5 | utf8 = yes 6 | string_mask = utf8only 7 | prompt = no 8 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/network/cilium/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Cilium check 3 | import_tasks: check.yml 4 | 5 | - name: Cilium install 6 | include_tasks: install.yml 7 | 8 | - name: Cilium apply 9 | include_tasks: apply.yml 10 | -------------------------------------------------------------------------------- /cluster/apps/media/immich/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: media 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./storage.yaml 11 | -------------------------------------------------------------------------------- /cluster/apps/media/photoprism/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: media 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./storage.yaml 11 | -------------------------------------------------------------------------------- /infrastructure/inventory/host_vars/tirante/packages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | host_packages: 3 | - fuse 4 | - ntfs-3g 5 | - nmap 6 | - exuberant-ctags 7 | - nvme-cli 8 | - direnv 9 | - jc 10 | - zerotier-one 11 | 12 | pip_packages: 13 | - pre-commit 14 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/network/cilium/templates/cilium/secret.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | data: 4 | keys: {{ cilium_ipsec_key }} 5 | kind: Secret 6 | metadata: 7 | name: cilium-ipsec-keys 8 | namespace: kube-system 9 | type: Opaque 10 | -------------------------------------------------------------------------------- /cluster/apps/downloads/lidarr/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: downloads 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./storage.yaml 11 | -------------------------------------------------------------------------------- /cluster/apps/downloads/radarr/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: downloads 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./storage.yaml 11 | -------------------------------------------------------------------------------- /cluster/apps/downloads/readarr/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: downloads 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./storage.yaml 11 | -------------------------------------------------------------------------------- /cluster/apps/downloads/sonarr/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: downloads 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./storage.yaml 11 | -------------------------------------------------------------------------------- /infrastructure/inventory/group_vars/all/pki/00-all.yml: -------------------------------------------------------------------------------- 1 | --- 2 | pki_dir: "{{ (infra_dir, 'inventory','group_vars','all','pki') | path_join }}" 3 | 4 | dn: 5 | country: US 6 | organization_name: HERMLEIGH.HOME 7 | organization_unit_name: Hermleigh House Network 8 | -------------------------------------------------------------------------------- /cluster/apps/downloads/prowlarr/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: downloads 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./storage.yaml 11 | -------------------------------------------------------------------------------- /cluster/apps/services/linkwarden/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: services 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./storage.yaml 11 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/traefik/traefik-forward-auth/configs/traefik-forward-auth.ini: -------------------------------------------------------------------------------- 1 | cookie-name = "_traefik_auth" 2 | log-level = "error" 3 | cookie-domain = "hermleigh.home" 4 | auth-host = "auth-traefik.gato.hermleigh.home" 5 | whitelist = "clearlybaffled120@gmail.com" 6 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/keycloak.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-postgres.yml 4 | apply: 5 | vars: 6 | db_user_var: username 7 | db_pass_var: password 8 | db_names: 9 | - keycloak 10 | -------------------------------------------------------------------------------- /infrastructure/roles/workstation/tasks/terraform.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install Terraform 3 | package: 4 | name: terraform 5 | 6 | - name: Verify install 7 | command: terraform -help 8 | 9 | - name: Install autocomplete 10 | command: terraform -install-autocomplete 11 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/linkace.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-mysql.yml 4 | apply: 5 | vars: 6 | db_user_var: DB_USERNAME 7 | db_pass_var: DB_PASSWORD 8 | db_names: 9 | - linkacedb 10 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/nextcloud.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-mysql.yml 4 | apply: 5 | vars: 6 | db_user_var: db-username 7 | db_pass_var: db-password 8 | db_names: 9 | - nextcloud 10 | -------------------------------------------------------------------------------- /cluster/apps/home/paperless-ngx/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: home 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./hp-scan-to-cm.yaml 11 | - ./storage.yaml 12 | -------------------------------------------------------------------------------- /infrastructure/inventory/host_vars/parche/packages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | host_packages: 3 | - fuse 4 | - ntfs-3g 5 | - nmap 6 | - exfat-fuse 7 | - exuberant-ctags 8 | - hdparm 9 | - lm-sensors 10 | - nfs-common 11 | - nvme-cli 12 | - zerotier-one 13 | - firmware-realtek 14 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/linkding.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-postgres.yml 4 | apply: 5 | vars: 6 | db_user_var: LD_DB_USER 7 | db_pass_var: LD_DB_PASSWORD 8 | db_names: 9 | - linkding 10 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/mealie.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-postgres.yml 4 | apply: 5 | vars: 6 | db_user_var: POSTGRES_USER 7 | db_pass_var: POSTGRES_PASSWORD 8 | db_names: 9 | - mealie 10 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/wger.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-postgres.yml 4 | apply: 5 | vars: 6 | db_user_var: DJANGO_DB_USER 7 | db_pass_var: DJANGO_DB_PASSWORD 8 | db_names: 9 | - wger 10 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/network/cilium/tasks/reset.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Reset | check and remove devices if still present 3 | include_tasks: reset_iface.yml 4 | vars: 5 | iface: "{{ item }}" 6 | loop: 7 | - cilium_host 8 | - cilium_net 9 | - cilium_vxlan 10 | -------------------------------------------------------------------------------- /infrastructure/roles/kubernetes/client/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | kubeconfig_localhost: false 3 | kubeconfig_localhost_ansible_host: false 4 | kubectl_localhost: false 5 | artifacts_dir: "{{ inventory_dir }}/artifacts" 6 | 7 | kube_config_dir: "/etc/kubernetes" 8 | kube_apiserver_port: "6443" 9 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/netbox/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: infrastructure 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./graphite/ 11 | - ./storage.yaml 12 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/traefik/traefik-forward-auth/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: traefik-forward-auth 5 | spec: 6 | type: ClusterIP 7 | selector: 8 | app: traefik-forward-auth 9 | ports: 10 | - name: auth-http 11 | port: 4181 12 | -------------------------------------------------------------------------------- /infrastructure/_shared/networks.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | lan: "172.16.1.0/24" 3 | servers: "172.16.1.8/29" 4 | #trusted: "10.1.2.0/24" 5 | guest: "192.168.2.0/24" 6 | iot: "192.168.7.0/24" 7 | video: "10.1.4.0/24" 8 | wg_trusted: "10.0.11.0/24" 9 | services: "10.5.0.0/24" 10 | rescue: "192.168.15.0/24" 11 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/paperless-ngx.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-postgres.yml 4 | apply: 5 | vars: 6 | db_user_var: PAPERLESS_DBUSER 7 | db_pass_var: PAPERLESS_DBPASS 8 | db_names: 9 | - paperless 10 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/runtime/templates/http-proxy.conf.j2: -------------------------------------------------------------------------------- 1 | [Service] 2 | Environment={% if http_proxy is defined %}"HTTP_PROXY={{ http_proxy }}"{% endif %} {% if https_proxy is defined %}"HTTPS_PROXY={{ https_proxy }}"{% endif %} {% if no_proxy is defined %}"NO_PROXY={{ no_proxy }}"{% endif %} 3 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/templates/secret-generator.yaml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: {{ app.name }}-secret-generator 5 | namespace: {{ app.namespace }} 6 | files: 7 | {% for name in app.secrets.keys() %} 8 | - ./{{ name }}.sops.yaml 9 | {% endfor %} 10 | -------------------------------------------------------------------------------- /cluster/apps/home/mosquitto/mosquitto.conf: -------------------------------------------------------------------------------- 1 | per_listener_settings false 2 | listener 1883 3 | allow_anonymous false 4 | persistence true 5 | persistence_location /data 6 | autosave_interval 1800 7 | connection_messages false 8 | autosave_interval 60 9 | password_file /mosquitto/external_config/mosquitto_pwd 10 | -------------------------------------------------------------------------------- /infrastructure/inventory/host_vars/parche/node-labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | node_labels: 3 | - hermleigh.home/dvb: installed 4 | - hermleigh.home/jbod: "true" 5 | - intel.feature.node.kubernetes.io/gpu: "true" 6 | - zoos.feature.node.kubernetes.io/zwave: "true" 7 | - google.feature.node.kubernetes.io/coral: "true" 8 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/photoprism.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-mysql.yml 4 | apply: 5 | vars: 6 | db_user_var: PHOTOPRISM_DATABASE_USER 7 | db_pass_var: PHOTOPRISM_DATABASE_PASSWORD 8 | db_names: 9 | - photoprism 10 | -------------------------------------------------------------------------------- /.sops.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | keys_groups: &default_keys 3 | - age: 4 | - &age age1e764qpphm5nlzp04qf6zcq8f400390d9wmzramq84hqp60k6qyvqsvgg45 5 | 6 | creation_rules: 7 | - path_regex: .*\.sops\.yaml 8 | encrypted_regex: "^(data|stringData)$" 9 | key_groups: *default_keys 10 | - key_groups: *default_keys 11 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/wallabag.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-postgres.yml 4 | apply: 5 | vars: 6 | db_user_var: SYMFONY__ENV__DATABASE_USER 7 | db_pass_var: SYMFONY__ENV__DATABASE_PASSWORD 8 | db_names: 9 | - wallabag 10 | -------------------------------------------------------------------------------- /infrastructure/roles/kubernetes/node/tasks/redhat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: redhat | Node Configuration 3 | notify: Reboot 4 | block: 5 | - name: RedHat| Disable firewalld 6 | systemd: 7 | service: firewalld.service 8 | enabled: false 9 | masked: true 10 | state: stopped 11 | -------------------------------------------------------------------------------- /cluster/apps/home/mosquitto/secret-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: viaduct.ai/v1 2 | kind: ksops 3 | metadata: 4 | name: mosquitto-secret-generator 5 | namespace: home 6 | files: 7 | - ./secret.sops.yaml 8 | - ./frigate-secret.sops.yaml 9 | - ./home-assistant-secret.sops.yaml 10 | - ./owntracks-secret.sops.yaml 11 | -------------------------------------------------------------------------------- /cluster/apps/home/paperless-ngx/hp-scan-to-cm.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: hp-scan-to-env 5 | namespace: services 6 | data: 7 | IP: 172.16.1.180 8 | PATTERN: '"scan"_dd.mm.yyyy_hh:MM:ss' 9 | PGID: "997" 10 | PUID: "997" 11 | RESOLUTION: "300" 12 | TZ: America/New_York 13 | -------------------------------------------------------------------------------- /infrastructure/roles/kubernetes/kubelet/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Reboot 3 | reboot: 4 | msg: Rebooting nodes 5 | reboot_timeout: 3600 6 | 7 | - name: Node | restart kubelet # noqa no-changed-when 8 | command: /bin/true 9 | notify: 10 | - Reload systemd 11 | - Kubelet | restart kubelet 12 | -------------------------------------------------------------------------------- /infrastructure/terraform/ipa/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.5.7" 3 | required_providers { 4 | libvirt = { 5 | source = "dmacvicar/libvirt" 6 | version = "0.7.1" 7 | } 8 | sops = { 9 | source = "carlpett/sops" 10 | version = "1.0.0" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/README.md: -------------------------------------------------------------------------------- 1 | # Apps 2 | 3 | > The part that does the real heavy lifting. 4 | 5 | Once the cluster is up and ready, this role start launching the actual applications. 6 | I think it has become one of my crowning achievements in my jounrey towards peak ansible-ness. 7 | 8 | \[more coming when i feel like it\] 9 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/registry/templates/registry-secrets.yml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: registry-secret 5 | namespace: {{ registry_namespace }} 6 | type: Opaque 7 | data: 8 | {% if registry_htpasswd != "" %} 9 | htpasswd: {{ registry_htpasswd | b64encode }} 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /infrastructure/roles/server/kvm/templates/libvirt_dir_pool.xml.j2: -------------------------------------------------------------------------------- 1 | 2 | {{ pool_name }} 3 | 4 | {{ storage_dir }} 5 | 6 | 0755 7 | 0 8 | 0 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/traefik/tlsstore.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://github.com/datreeio/CRDs-catalog/raw/main/traefik.io/tlsstore_v1alpha1.json 2 | apiVersion: traefik.io/v1alpha1 3 | kind: TLSStore 4 | metadata: 5 | name: default 6 | spec: 7 | defaultCertificate: 8 | secretName: external-wildcard-certificate 9 | -------------------------------------------------------------------------------- /infrastructure/roles/ca/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ca_home: "{{ (pki_home, ca_name) | path_join }}" 3 | 4 | ca_private_keyfile: "{{ ( ca_home, 'private', ca_name+'.key') | path_join }}" 5 | signing_ca_home: "{{ (pki_home, signing_ca)|path_join }}" 6 | signing_ca_private_keyfile: "{{ ( signing_ca_home, 'private', signing_ca+'.key') | path_join }}" 7 | -------------------------------------------------------------------------------- /infrastructure/terraform/ipa/ipa_network_config: -------------------------------------------------------------------------------- 1 | version: 1 2 | config: 3 | - type: physical 4 | name: eth0 5 | subnets: 6 | - type: static 7 | address: 172.16.2.2/24 8 | gateway: 172.16.2.1 9 | dns_nameservers: 10 | - 172.16.1.1 11 | dns_search: 12 | - hermleigh.home 13 | -------------------------------------------------------------------------------- /infrastructure/terraform/kvm/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.5.7" 3 | required_providers { 4 | libvirt = { 5 | source = "dmacvicar/libvirt" 6 | version = "0.7.1" 7 | } 8 | } 9 | } 10 | 11 | provider "libvirt" { 12 | uri = "qemu+ssh://ansible@parche/system?keyfile=$HOME/.ssh/ansible" 13 | } 14 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/registry/templates/registry-cm.yml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: registry-config 5 | namespace: {{ registry_namespace }} 6 | {% if registry_config %} 7 | data: 8 | config.yml: |- 9 | {{ registry_config | to_yaml(indent=2, width=1337) | indent(width=4) }} 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/cert-manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: cert-manager 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./cluster-issuer.yaml 11 | - ./issuer-letsencrypt-prod.yaml 12 | - ./issuer-letsencrypt-staging.yaml 13 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/reloader/values.yaml: -------------------------------------------------------------------------------- 1 | reloader: 2 | reloadStrategy: annotations 3 | readOnlyRootFilesystem: true 4 | deployment: 5 | resources: 6 | limits: 7 | memory: "512Mi" 8 | requests: 9 | cpu: "10m" 10 | memory: "512Mi" 11 | podMonitor: 12 | enabled: true 13 | namespace: monitoring 14 | -------------------------------------------------------------------------------- /cluster/apps/services/linkace/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: services 7 | generators: 8 | - ./secret-generator.yaml 9 | configMapGenerator: 10 | - name: nginx-config 11 | files: 12 | - nginx.conf 13 | options: 14 | disableNameSuffixHash: true 15 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/network/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: 3 | - role: network/cilium 4 | when: kube_network_plugin == 'cilium' or cilium_deploy_additionally | default(false) | bool 5 | tags: 6 | - cilium 7 | 8 | - role: network/flannel 9 | when: kube_network_plugin == 'flannel' 10 | tags: 11 | - flannel 12 | -------------------------------------------------------------------------------- /cluster/apps/home/mosquitto/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: home 7 | generators: 8 | - ./secret-generator.yaml 9 | configMapGenerator: 10 | - name: mosquitto-configmap 11 | files: 12 | - ./mosquitto.conf 13 | options: 14 | disableNameSuffixHash: true 15 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/metallb/layer2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: metallb.io/v1beta1 2 | kind: L2Advertisement 3 | metadata: 4 | name: layer2 5 | namespace: metallb-system 6 | spec: 7 | ipAddressPools: 8 | - ingress-pool 9 | - general-pool 10 | interfaces: 11 | - br0 12 | nodeSelectors: 13 | - matchLabels: 14 | kubernetes.io/hostname: parche 15 | -------------------------------------------------------------------------------- /infrastructure/roles/dvb/files/environment.conf: -------------------------------------------------------------------------------- 1 | [Service] 2 | # Command line arguments to be passed by systemd to mythbackend on startup. 3 | # This variable must be defined or the service will not start. 4 | Environment="MYTHBACKEND_ARGS=--systemd-journal" 5 | 6 | # Environment variables for the mythbackend program itself 7 | Environment=MYTHCONFDIR=/srv/mythtv/.mythtv 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ansible>=9.0.0 2 | ansible-core>=2.16.0 3 | ansible-lint>=6.14.3 4 | ansible-pylibssh>=1.1.0 5 | kubernetes>=26.1.0 6 | jinja2>=3.1.2 7 | ansible-navigator 8 | netaddr 9 | pywinrm>=0.3.0 10 | cryptography>=1.3 11 | jsonpatch 12 | psycopg2-binary 13 | PyMySQL 14 | mysqlclient 15 | libvirt-python 16 | pre-commit 17 | jc 18 | checkov 19 | httpx 20 | -------------------------------------------------------------------------------- /cluster/apps/home/grocy/svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: grocy 5 | namespace: services 6 | labels: 7 | app.kubernetes.io/name: grocy 8 | spec: 9 | selector: 10 | app.kubernetes.io/name: grocy 11 | ports: 12 | - name: grocy-web 13 | port: 80 14 | targetPort: 8080 15 | protocol: TCP 16 | type: ClusterIP 17 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/traefik/middleware.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: traefik.containo.us/v1alpha1 2 | kind: Middleware 3 | metadata: 4 | name: forward-auth 5 | spec: 6 | forwardAuth: 7 | address: https://oauth.sso.hermleigh.home 8 | authResponseHeaders: 9 | - Authorization 10 | - Set-Cookie 11 | authResponseHeadersRegex: ^X- 12 | trustForwardHeader: true 13 | -------------------------------------------------------------------------------- /infrastructure/roles/common/vars/os.yml: -------------------------------------------------------------------------------- 1 | --- 2 | common_packages: 3 | - curl 4 | - git 5 | - ca-certificates 6 | - vim 7 | - git 8 | - wget 9 | - policycoreutils-python-utils 10 | - tree 11 | - jq 12 | - lshw 13 | - htop 14 | - hdparm 15 | - gnupg-agent 16 | - socat 17 | - dos2unix 18 | - smartmontools 19 | - rsync 20 | - unzip 21 | - zstd 22 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/network/cilium/templates/cilium/crb.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: cilium 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: ClusterRole 9 | name: cilium 10 | subjects: 11 | - kind: ServiceAccount 12 | name: cilium 13 | namespace: kube-system 14 | -------------------------------------------------------------------------------- /infrastructure/roles/server/kvm/templates/libvirt_rbd_pool.xml.j2: -------------------------------------------------------------------------------- 1 | 2 | {{ pool_name }} 3 | 4 | rbdpool 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/network/cilium/templates/000-cilium-portmap.conflist.j2: -------------------------------------------------------------------------------- 1 | { 2 | "cniVersion": "0.3.1", 3 | "name": "cilium-portmap", 4 | "plugins": [ 5 | { 6 | "type": "cilium-cni" 7 | }, 8 | { 9 | "type": "portmap", 10 | "capabilities": { "portMappings": true } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/netbox/graphite/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: graphite 5 | namespace: infrastructure 6 | spec: 7 | selector: 8 | app.kubernetes.io/component: netbox 9 | app.kubernetes.io/instance: netbox 10 | app.kubernetes.io/name: netbox 11 | ports: 12 | - port: 80 13 | targetPort: 80 14 | protocol: TCP 15 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/traefik/traefik-forward-auth/middleware.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: traefik.containo.us/v1alpha1 2 | kind: Middleware 3 | metadata: 4 | name: traefik-forward-auth 5 | spec: 6 | forwardAuth: 7 | address: http://traefik-forward-auth.traefik-system.svc.cluster.local:4181 8 | authResponseHeaders: 9 | - X-Forwarded-User 10 | trustForwardHeader: true 11 | -------------------------------------------------------------------------------- /infrastructure/roles/crypto_device/tasks/close.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Unmount {{ crypt_name }} volume 3 | ansible.posix.mount: 4 | path: "{{ crypt_mount_path }}" 5 | state: unmounted 6 | register: umount 7 | 8 | - name: Close dm-crypt 9 | command: 10 | cmd: "cryptsetup close {{ crypt_name }}" 11 | removes: "{{ crypt_mapping_file }}" 12 | when: umount is success 13 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/reset.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Shutdown all apps 3 | include_tasks: shutdown.yml 4 | loop: "{{ (apps|items|rejectattr('1.wave','defined') + (apps | items | selectattr('1.wave','in',['3','4']) | sort(attribute='1.wave', reverse=True)) ) }}" 5 | loop_control: 6 | label: "{{ item.0 }}" 7 | vars: 8 | app_name: "{{ item.0 }}" 9 | app: "{{ item.1 }}" 10 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/registry/templates/registry-cr.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: psp:registry 6 | namespace: {{ registry_namespace }} 7 | rules: 8 | - apiGroups: 9 | - policy 10 | resourceNames: 11 | - registry 12 | resources: 13 | - podsecuritypolicies 14 | verbs: 15 | - use 16 | -------------------------------------------------------------------------------- /infrastructure/roles/kubernetes/control_plane/tasks/reset.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check if cluster is still up 3 | kubernetes.core.k8s_cluster_info: 4 | ssl_ca_cert: "{{ kube_cluster_cacerts }}" 5 | register: cluster_status 6 | ignore_errors: true 7 | 8 | - name: kubeadm | Reset cluster 9 | command: "{{ bin_dir }}/kubeadm reset --force" 10 | # when: cluster_status is not failed 11 | -------------------------------------------------------------------------------- /cluster/apps/db/cloudnative-pg/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: db 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./cluster.yaml 11 | - ./service.yaml 12 | - https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/docs/src/samples/monitoring/prometheusrule.yaml 13 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/traefik/traefik-forward-auth/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | commonLabels: 4 | app: traefik-forward-auth 5 | 6 | resources: 7 | - service.yaml 8 | - deployment.yaml 9 | - middleware.yaml 10 | 11 | configMapGenerator: 12 | - name: configs 13 | files: 14 | - configs/traefik-forward-auth.ini 15 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/post-launch/ingress-nginx.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ingress-nginx | Wait for SSL Certificate Generation Jobs to complete 3 | kubernetes.core.k8s_info: 4 | namespace: infrastructure 5 | kind: Pod 6 | wait: true 7 | wait_timeout: 120 8 | label_selectors: 9 | - app.kubernetes.io/component=controller 10 | wait_condition: 11 | type: Ready 12 | -------------------------------------------------------------------------------- /infrastructure/terraform/ipa/variables.tf: -------------------------------------------------------------------------------- 1 | data "sops_file" "ansible_ssh_key" { 2 | source_file = "${path.module}/../../inventory/group_vars/all/ansible_user.sops.yml" 3 | } 4 | 5 | data "sops_file" "root_ca_crt" { 6 | source_file = "${path.module}/../../inventory/group_vars/all/pki/root-ca.sops.yml" 7 | } 8 | 9 | output "hosts_created" { 10 | value = [libvirt_domain.ipa-server.name] 11 | } 12 | -------------------------------------------------------------------------------- /infrastructure/roles/kubernetes/kubelet/files/kubelet.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=kubelet: The Kubernetes Node Agent 3 | Documentation=https://kubernetes.io/docs/home/ 4 | Wants=network-online.target 5 | After=network-online.target 6 | 7 | [Service] 8 | ExecStart=/usr/local/bin/kubelet 9 | Restart=always 10 | StartLimitInterval=0 11 | RestartSec=10 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/network/cilium/templates/cilium-operator/crb.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: cilium-operator 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: ClusterRole 9 | name: cilium-operator 10 | subjects: 11 | - kind: ServiceAccount 12 | name: cilium-operator 13 | namespace: kube-system 14 | -------------------------------------------------------------------------------- /cluster/apps/downloads/bazarr/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: downloads 7 | generators: 8 | - ./secret-generator.yaml 9 | configMapGenerator: 10 | - name: bazarr-scripts 11 | files: 12 | - ./scripts/post-process.sh 13 | generatorOptions: 14 | disableNameSuffixHash: true 15 | resources: 16 | - ./storage.yaml 17 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/immich.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-postgres.yml 4 | apply: 5 | vars: 6 | db_user_var: DB_USERNAME 7 | db_pass_var: DB_PASSWORD 8 | db_names: 9 | - "{{ secret.data.DB_DATABASE_NAME | b64decode }}" 10 | vars: 11 | secret: "{{ lookup('community.sops.sops', app.app_dir+'/secret.sops.yaml') | from_yaml }}" 12 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/registry/templates/registry-crb.yml.j2: -------------------------------------------------------------------------------- 1 | kind: RoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: psp:registry 5 | namespace: {{ registry_namespace }} 6 | subjects: 7 | - kind: ServiceAccount 8 | name: registry 9 | namespace: {{ registry_namespace }} 10 | roleRef: 11 | kind: ClusterRole 12 | name: psp:registry 13 | apiGroup: rbac.authorization.k8s.io 14 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/post-launch/cloudnative-pg.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Wait for PostgreSQL cluster to be healthy 3 | kubernetes.core.k8s_info: 4 | kind: Cluster 5 | api_version: postgresql.cnpg.io/v1 6 | namespace: db 7 | name: cnpg-cluster 8 | wait: true 9 | wait_sleep: 60 10 | wait_timeout: 360 11 | wait_condition: 12 | type: Ready 13 | reason: ClusterIsReady 14 | status: 'True' 15 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/bazarr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-postgres.yml 4 | apply: 5 | vars: 6 | db_user_var: POSTGRES_USERNAME 7 | db_pass_var: POSTGRES_PASSWORD 8 | db_names: 9 | - "{{ secret.data.POSTGRES_DATABASE | b64decode }}" 10 | vars: 11 | secret: "{{ lookup('community.sops.sops', app.app_dir+'/secret.sops.yaml') | from_yaml }}" 12 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/network/cilium/tasks/reset_iface.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Reset | check if network device {{ iface }} is present" 3 | stat: 4 | path: "/sys/class/net/{{ iface }}" 5 | get_attributes: no 6 | get_checksum: no 7 | get_mime: no 8 | register: device_remains 9 | 10 | - name: "Reset | remove network device {{ iface }}" 11 | command: "ip link del {{ iface }}" 12 | when: device_remains.stat.exists 13 | -------------------------------------------------------------------------------- /playbooks/apps.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Manage ArgoCD Application(s) 3 | hosts: localhost 4 | gather_facts: true 5 | roles: 6 | - apps 7 | vars: 8 | # see ./infrastructure/roles/apps/meta/argument_specs.yaml for definitions 9 | git_push_force: false 10 | git_push: false 11 | app_no_loop_pause: true 12 | app_tasks: true 13 | batch_apps: true 14 | # app_skip_list: 15 | app_deploy_list: 16 | - intel-device-plugin 17 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/cert-manager.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install Cert-manager certs 3 | kubernetes.core.k8s: 4 | api_version: v1 5 | kind: Secret 6 | name: ca-key-pair 7 | namespace: cert-manager 8 | server_side_apply: 9 | field_manager: ansible 10 | definition: 11 | type: kubernetes.io/tls 12 | data: 13 | tls.crt: "{{ kubernetes_ca.crt }}" 14 | tls.key: "{{ kubernetes_ca.key }}" 15 | -------------------------------------------------------------------------------- /infrastructure/roles/kubernetes/control_plane/templates/csr.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: certificates.k8s.io/v1 3 | kind: CertificateSigningRequest 4 | metadata: 5 | name: {{ name }} 6 | spec: 7 | groups: 8 | - system:authenticated 9 | - system:masters 10 | request: {{ csr.csr | b64encode | replace('\n','') }} 11 | signerName: kubernetes.io/kube-apiserver-client 12 | usages: 13 | - digital signature 14 | - key encipherment 15 | - client auth 16 | -------------------------------------------------------------------------------- /cluster/apps/home/grocy/cm.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: grocy-cm 5 | namespace: services 6 | labels: 7 | app.kubernetes.io/name: grocy 8 | data: 9 | GENERIC_TIMEZONE: "America/New_York" 10 | TZ: "America/New_York" 11 | PUID: "997" 12 | PGID: "997" 13 | GROCY_CULTURE: en 14 | MAX_UPLOAD: 50M 15 | PHP_MAX_FILE_UPLOAD: "200" 16 | PHP_MAX_POST: 100M 17 | PHP_MEMORY_LIMIT: 512M 18 | GROCY_MODE: production 19 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/runtime/templates/config.json.j2: -------------------------------------------------------------------------------- 1 | {% if crio_registry_auth is defined and crio_registry_auth|length %} 2 | { 3 | {% for reg in crio_registry_auth %} 4 | "auths": { 5 | "{{ reg.registry }}": { 6 | "auth": "{{ (reg.username + ':' + reg.password) | string | b64encode }}" 7 | } 8 | {% if not loop.last %} 9 | }, 10 | {% else %} 11 | } 12 | {% endif %} 13 | {% endfor %} 14 | } 15 | {% else %} 16 | {} 17 | {% endif %} 18 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/runtime/templates/unqualified.conf.j2: -------------------------------------------------------------------------------- 1 | {%- set _unqualified_registries = [] -%} 2 | {% for _registry in crio_registries if _registry.unqualified -%} 3 | {% if _registry.prefix is defined -%} 4 | {{ _unqualified_registries.append(_registry.prefix) }} 5 | {% else %} 6 | {{ _unqualified_registries.append(_registry.location) }} 7 | {%- endif %} 8 | {%- endfor %} 9 | 10 | unqualified-search-registries = {{ _unqualified_registries | string }} 11 | -------------------------------------------------------------------------------- /homelab.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Host initialization 3 | import_playbook: playbooks/host.yml 4 | tags: 5 | - host 6 | 7 | - name: Kubernetes Cluster initialization 8 | import_playbook: playbooks/cluster.yml 9 | tags: 10 | - cluster 11 | 12 | - name: Cluster Application bootstrap 13 | import_playbook: playbooks/apps.yml 14 | tags: 15 | - apps 16 | 17 | - name: IPA Server startup 18 | import_playbook: playbooks/freeipa.yml 19 | tags: 20 | - ipa 21 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/cert-manager/values.yaml: -------------------------------------------------------------------------------- 1 | installCRDs: false 2 | 3 | extraArgs: 4 | - '--default-issuer-kind=ClusterIssuer' 5 | - '--default-issuer-name=ca-issuer' 6 | - '--dns01-recursive-nameservers-only' 7 | - '--dns01-recursive-nameservers=8.8.8.8:53,1.1.1.1:53' 8 | 9 | prometheus: 10 | enabled: true 11 | servicemonitor: 12 | enabled: true 13 | prometheusInstance: kube-prometheus-stack 14 | labels: 15 | release: kube-prometheus-stack 16 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/registry/templates/registry-pvc.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: PersistentVolumeClaim 4 | metadata: 5 | name: registry-pvc 6 | namespace: {{ registry_namespace }} 7 | labels: 8 | addonmanager.kubernetes.io/mode: Reconcile 9 | spec: 10 | accessModes: 11 | - {{ registry_storage_access_mode }} 12 | storageClassName: {{ registry_storage_class }} 13 | resources: 14 | requests: 15 | storage: {{ registry_disk_size }} 16 | -------------------------------------------------------------------------------- /scripts/references.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 4 | cd "${scriptDir}/.." || exit 1 5 | base=$(pwd) 6 | 7 | if [ $# -ne 0 ]; then 8 | 9 | for repo in "$@"; do 10 | path=$(dirname $repo) 11 | git clone --single-branch https://github.com/$repo $base/references/$path 12 | done 13 | 14 | else 15 | 16 | for repo in $base/references/*; do 17 | git -C $repo pull --rebase 18 | done 19 | 20 | fi 21 | -------------------------------------------------------------------------------- /infrastructure/roles/workstation/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: execute os specific tasks 3 | include_tasks: "{{ ansible_os_family|lower }}.yml" 4 | 5 | - name: Install python packages 6 | pip: 7 | name: "{{ pip_packages }}" 8 | when: pip_packages is defined 9 | 10 | - name: Install Mozilla SOPS 11 | include_role: 12 | name: community.sops.install 13 | 14 | - name: Create local ansible user 15 | include_role: 16 | name: user 17 | tasks_from: system-user.yml 18 | -------------------------------------------------------------------------------- /requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | collections: 3 | - name: lvrfrc87.git_acp 4 | type: galaxy 5 | - name: freeipa.ansible_freeipa 6 | type: galaxy 7 | - ansible.posix 8 | - ansible.windows 9 | - community.crypto 10 | - community.general 11 | - community.libvirt 12 | - community.sops 13 | - community.postgresql 14 | - community.mysql 15 | - community.grafana 16 | - kubernetes.core 17 | - cisco.ios 18 | - name: ansibleguy.opnsense 19 | type: galaxy 20 | version: 1.2.9 21 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/cloudflared/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: cloudflared-configmap 5 | namespace: infrastructure 6 | data: 7 | config.yaml: | 8 | --- 9 | tunnel: 8b18a8e8-a189-45f0-a88d-5b38ee7086c4 10 | 11 | ingress: 12 | - hostname: hermleigh.cc 13 | service: &svc https://ingress-nginx-controller.infrastructure.svc 14 | - hostname: "*.hermleigh.cc" 15 | service: *svc 16 | - service: http_status:404 17 | -------------------------------------------------------------------------------- /playbooks/host.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Workstation Provisioning 3 | hosts: workstations 4 | gather_facts: true 5 | become: true 6 | tags: workstation 7 | roles: 8 | - workstation 9 | 10 | - name: Server Provisioning 11 | hosts: servers 12 | gather_facts: true 13 | any_errors_fatal: true 14 | become: true 15 | tags: server 16 | roles: 17 | - server 18 | - dvb 19 | 20 | - name: KVM setup 21 | hosts: hypervisor 22 | become: true 23 | tags: kvm 24 | roles: 25 | - server/kvm 26 | -------------------------------------------------------------------------------- /cluster/apps/home/grocy/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: home 7 | configMapGenerator: 8 | - name: grocy-config 9 | files: 10 | - ./config.php 11 | options: 12 | disableNameSuffixHash: true 13 | labels: 14 | app.kubernetes.io/name: grocy 15 | app.kubernetes.io/instance: grocy 16 | resources: 17 | - ./cm.yaml 18 | - ./deployment.yaml 19 | - ./ingress.yaml 20 | - ./storage.yaml 21 | - ./svc.yaml 22 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/metallb/pools.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: metallb.io/v1beta1 3 | kind: IPAddressPool 4 | metadata: 5 | namespace: metallb-system 6 | name: general-pool 7 | spec: 8 | addresses: 9 | - 172.16.1.32/27 10 | autoAssign: true 11 | avoidBuggyIPs: true 12 | --- 13 | apiVersion: metallb.io/v1beta1 14 | kind: IPAddressPool 15 | metadata: 16 | namespace: metallb-system 17 | name: ingress-pool 18 | spec: 19 | addresses: 20 | - 172.16.1.24/31 21 | autoAssign: true 22 | avoidBuggyIPs: true 23 | -------------------------------------------------------------------------------- /infrastructure/roles/user/templates/crb.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | annotations: 6 | rbac.authorization.kubernetes.io/autoupdate: "true" 7 | labels: 8 | kubernetes.io/bootstrapping: rbac-defaults 9 | name: {{ kube_user }} 10 | roleRef: 11 | apiGroup: rbac.authorization.k8s.io 12 | kind: ClusterRole 13 | name: cluster-admin 14 | subjects: 15 | - apiGroup: rbac.authorization.k8s.io 16 | kind: Group 17 | name: service:masters 18 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/lidarr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-postgres.yml 4 | apply: 5 | vars: 6 | db_user_var: LIDARR__POSTGRES_USER 7 | db_pass_var: LIDARR__POSTGRES_PASSWORD 8 | db_names: 9 | - "{{ secret.data.LIDARR__POSTGRES_MAIN_DB | b64decode }}" 10 | - "{{ secret.data.LIDARR__POSTGRES_LOG_DB | b64decode }}" 11 | vars: 12 | secret: "{{ lookup('community.sops.sops', app.app_dir+'/secret.sops.yaml') | from_yaml }}" 13 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/radarr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-postgres.yml 4 | apply: 5 | vars: 6 | db_user_var: RADARR__POSTGRES_USER 7 | db_pass_var: RADARR__POSTGRES_PASSWORD 8 | db_names: 9 | - "{{ secret.data.RADARR__POSTGRES_MAIN_DB | b64decode }}" 10 | - "{{ secret.data.RADARR__POSTGRES_LOG_DB | b64decode }}" 11 | vars: 12 | secret: "{{ lookup('community.sops.sops', app.app_dir+'/secret.sops.yaml') | from_yaml }}" 13 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/sonarr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-postgres.yml 4 | apply: 5 | vars: 6 | db_user_var: SONARR__POSTGRES_USER 7 | db_pass_var: SONARR__POSTGRES_PASSWORD 8 | db_names: 9 | - "{{ secret.data.SONARR__POSTGRES_MAIN_DB | b64decode }}" 10 | - "{{ secret.data.SONARR__POSTGRES_LOG_DB | b64decode }}" 11 | vars: 12 | secret: "{{ lookup('community.sops.sops', app.app_dir+'/secret.sops.yaml') | from_yaml }}" 13 | -------------------------------------------------------------------------------- /cluster/apps/db/cloudnative-pg/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile-upstream:master 2 | ARG CNPG_TAG 3 | 4 | FROM ghcr.io/cloudnative-pg/postgresql:$CNPG_TAG 5 | 6 | ARG CNPG_TAG 7 | ARG PGVECTORS_TAG 8 | ARG TARGETARCH 9 | 10 | # drop to root to install packages 11 | USER root 12 | 13 | # install pgvecto.rs 14 | ADD https://github.com/tensorchord/pgvecto.rs/releases/download/$PGVECTORS_TAG/vectors-pg${CNPG_TAG%.*}_${PGVECTORS_TAG#"v"}_$TARGETARCH.deb ./pgvectors.deb 15 | RUN apt install ./pgvectors.deb 16 | 17 | USER postgres 18 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/prowlarr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-postgres.yml 4 | apply: 5 | vars: 6 | db_user_var: PROWLARR__POSTGRES_USER 7 | db_pass_var: PROWLARR__POSTGRES_PASSWORD 8 | db_names: 9 | - "{{ secret.data.PROWLARR__POSTGRES_MAIN_DB | b64decode }}" 10 | - "{{ secret.data.PROWLARR__POSTGRES_LOG_DB | b64decode }}" 11 | vars: 12 | secret: "{{ lookup('community.sops.sops', app.app_dir+'/secret.sops.yaml') | from_yaml }}" 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /cluster/apps/db/cloudnative-pg/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: postgresql-ext 5 | namespace: db 6 | annotations: 7 | metallb.universe.tf/loadBalancerIPs: 172.16.1.33 8 | external-dns.alpha.kubernetes.io/hostname: postgres.hermleigh.home 9 | spec: 10 | selector: 11 | cnpg.io/cluster: cnpg-cluster 12 | role: primary 13 | ports: 14 | - name: postgres 15 | protocol: TCP 16 | port: 5432 17 | targetPort: 5432 18 | type: LoadBalancer 19 | externalTrafficPolicy: Cluster 20 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/readarr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-postgres.yml 4 | apply: 5 | vars: 6 | db_user_var: READARR__POSTGRES_USER 7 | db_pass_var: READARR__POSTGRES_PASSWORD 8 | db_names: 9 | - "{{ secret.data.READARR__POSTGRES_MAIN_DB | b64decode }}" 10 | - "{{ secret.data.READARR__POSTGRES_LOG_DB | b64decode }}" 11 | - readarr 12 | vars: 13 | secret: "{{ lookup('community.sops.sops', app.app_dir+'/secret.sops.yaml') | from_yaml }}" 14 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/runtime/templates/registry.conf.j2: -------------------------------------------------------------------------------- 1 | [[registry]] 2 | prefix = "{{ item.prefix | default(item.location) }}" 3 | insecure = {{ item.insecure | default('false') | string | lower }} 4 | blocked = {{ item.blocked | default('false') | string | lower }} 5 | location = "{{ item.location }}" 6 | {% if item.mirrors is defined %} 7 | {% for mirror in item.mirrors %} 8 | 9 | [[registry.mirror]] 10 | location = "{{ mirror.location }}" 11 | insecure = {{ mirror.insecure | default('false') | string | lower }} 12 | {% endfor %} 13 | {% endif %} 14 | -------------------------------------------------------------------------------- /infrastructure/terraform/ipa/ipa.tf: -------------------------------------------------------------------------------- 1 | resource "libvirt_domain" "ipa-server" { 2 | name = "tang" 3 | memory = "4096" 4 | vcpu = 2 5 | autostart = true 6 | qemu_agent = true 7 | 8 | 9 | network_interface { 10 | network_name = "virtnet" 11 | } 12 | 13 | disk { 14 | volume_id = libvirt_volume.ipa-root.id 15 | scsi = true 16 | } 17 | 18 | cloudinit = libvirt_cloudinit_disk.ipa-cloud-init.id 19 | 20 | console { 21 | type = "pty" 22 | target_type = "serial" 23 | target_port = "0" 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /infrastructure/terraform/kvm/resources.tf: -------------------------------------------------------------------------------- 1 | resource "libvirt_volume" "fedora38-qcow2" { 2 | name = "fedora38.qcow2" 3 | pool = "cluster" 4 | source = "https://download.fedoraproject.org/pub/fedora/linux/releases/38/Cloud/x86_64/images/Fedora-Cloud-Base-38-1.6.x86_64.qcow2" 5 | format = "qcow2" 6 | } 7 | 8 | resource "libvirt_network" "server_network" { 9 | name = "servers" 10 | mode = "bridge" 11 | bridge = "br0" 12 | } 13 | 14 | resource "libvirt_network" "admin_network" { 15 | name = "admin" 16 | mode = "bridge" 17 | bridge = "br1" 18 | } 19 | -------------------------------------------------------------------------------- /infrastructure/roles/dvb/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install DVB utilities 3 | apt: 4 | pkg: 5 | - v4l-utils 6 | - v4l-conf 7 | - dtv-scan-tables 8 | - dvb-apps 9 | - w-scan 10 | - jmtpfs 11 | - libmtp-runtime 12 | - mtp-tools 13 | - i2c-tools 14 | - xmltv 15 | update_cache: true 16 | 17 | 18 | - name: Create mythtv user 19 | user: 20 | name: mythtv 21 | uid: 990 22 | group: mythtv 23 | home: /srv/mythtv 24 | system: true 25 | groups: 26 | - video 27 | append: true 28 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/rook-ceph/README.md: -------------------------------------------------------------------------------- 1 | # ☠ Rook Ceph Cluster ☠ 2 | 3 | Here lies my old rook/ceph cluster. Hyperconverged, single node, single mon, single mgr, single OSD per disk, consumer grade disks. She just wasn't built to last. 4 | 5 | [The death knell](https://github.com/clearlybaffled/homelab/issues/26) 6 | 7 | Maybe, one day, when my lab grows up to have some real servers with real disk drives 8 | lots of cores, tons of RAM, multiple nodes, it'll make sense to bring this back. 9 | 10 | But for the meantime, localPath PV/PVCs sound so much more manageable now. 11 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/netbox/graphite/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: graphite 5 | namespace: infrastructure 6 | labels: 7 | name: graphite 8 | spec: 9 | rules: 10 | - host: &host graphite.hermleigh.cc 11 | http: 12 | paths: 13 | - pathType: Prefix 14 | path: "/" 15 | backend: 16 | service: 17 | name: graphite 18 | port: 19 | number: 80 20 | tls: 21 | - hosts: 22 | - *host 23 | secretName: external-wildcard-certificate 24 | -------------------------------------------------------------------------------- /cluster/kube-system/node-feature-discovery/values.yaml: -------------------------------------------------------------------------------- 1 | worker: 2 | annotations: 3 | configmap.reloader.stakater.com/reload: node-feature-discovery-worker-conf 4 | config: 5 | core: 6 | sources: 7 | - custom 8 | - pci 9 | - usb 10 | sources: 11 | usb: 12 | deviceClassWhitelist: 13 | - "02" 14 | - "03" 15 | - "0e" 16 | - "ef" 17 | - "fe" 18 | - "ff" 19 | deviceLabelFields: 20 | - class 21 | - vendor 22 | - device 23 | prometheus: 24 | enable: true 25 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/keycloak/keycloak.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: k8s.keycloak.org/v2alpha1 3 | kind: Keycloak 4 | metadata: 5 | name: keycloak 6 | namespace: infrastructure 7 | spec: 8 | instances: 1 9 | db: 10 | vendor: postgres 11 | host: cnpg-cluster-rw.db.svc 12 | usernameSecret: 13 | name: keycloak-db-secret 14 | key: username 15 | passwordSecret: 16 | name: keycloak-db-secret 17 | key: password 18 | http: 19 | tlsSecret: external-wildcard-certificate 20 | hostname: 21 | hostname: keycloak.hermleigh.cc 22 | ingress: 23 | enabled: false 24 | -------------------------------------------------------------------------------- /infrastructure/roles/server/tasks/partitions.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create Partitions 3 | community.general.lvg: 4 | vg: "{{ inventory_hostname }}-vg" 5 | 6 | - name: Create Logical volumes 7 | community.general.lvol: 8 | vg: "{{ inventory_hostname }}-vg" 9 | lv: "{{ item.name }}" 10 | size: "{{ item.size }}" 11 | active: true 12 | state: present 13 | loop: partitions 14 | 15 | - name: Create Filsystems 16 | community.general.filesystem: 17 | dev: "/dev/mapper/{{ inventory_hostname }}--vg-{{ item.name|regex_sub('-','--') }}" 18 | fstype: "{{ item.fstype }}" 19 | loop: partitions 20 | -------------------------------------------------------------------------------- /infrastructure/roles/user/templates/kubeconfig.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Config 4 | clusters: 5 | - name: {{ cluster_name }} 6 | cluster: 7 | certificate-authority-data: {{ kubernetes_ca.crt }} 8 | server: {{ kube_apiserver_endpoint }} 9 | users: 10 | - name: {{ kube_user }} 11 | user: 12 | client-certificate-data: {{ certificate }} 13 | client-key-data: {{ private_key.privatekey | string | b64encode | replace('\n','') }} 14 | contexts: 15 | - context: 16 | cluster: {{ cluster_name }} 17 | user: {{ kube_user }} 18 | name: {{ cluster_name }} 19 | current-context: {{ cluster_name }} 20 | -------------------------------------------------------------------------------- /infrastructure/roles/kubernetes/kubelet/templates/node-kubeconfig.yaml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Config 4 | clusters: 5 | - name: local 6 | cluster: 7 | certificate-authority: {{ kube_cert_dir }}/ca.pem 8 | server: {{ kube_apiserver_endpoint }} 9 | users: 10 | - name: kubelet 11 | user: 12 | client-certificate: {{ kube_cert_dir }}/node-{{ inventory_hostname }}.pem 13 | client-key: {{ kube_cert_dir }}/node-{{ inventory_hostname }}-key.pem 14 | contexts: 15 | - context: 16 | cluster: local 17 | user: kubelet 18 | name: kubelet-{{ cluster_name }} 19 | current-context: kubelet-{{ cluster_name }} 20 | -------------------------------------------------------------------------------- /cluster/apps/services/homepage/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: homepage 7 | generators: 8 | - ./secret-generator.yaml 9 | configMapGenerator: 10 | - name: homepage-config 11 | files: 12 | - ./config/bookmarks.yaml 13 | - ./config/docker.yaml 14 | - ./config/kubernetes.yaml 15 | - ./config/services.yaml 16 | - ./config/settings.yaml 17 | - ./config/widgets.yaml 18 | options: 19 | disableNameSuffixHash: true 20 | labels: 21 | app.kubernetes.io/name: homepage 22 | app.kubernetes.io/instance: homepage 23 | -------------------------------------------------------------------------------- /cluster/kube-system/intel-device-plugin/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: kube-system 7 | helmCharts: 8 | - name: intel-device-plugins-gpu 9 | repo: https://intel.github.io/helm-charts/ 10 | version: 0.29.0 11 | namespace: kube-system 12 | releaseName: intel-device-plugins-gpu 13 | valuesFile: values.yaml 14 | patches: 15 | - patch: "- op: remove\n path: /spec/resourceManager\n- op: remove\n path: /metadata/annotations" 16 | target: 17 | version: v1 18 | group: deviceplugin.intel.com 19 | kind: GpuDevicePlugin 20 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/keycloak/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: infrastructure 7 | generators: 8 | - ./secret-generator.yaml 9 | resources: 10 | - ./ingress.yaml 11 | - ./keycloak.yaml 12 | - https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/25.0.2/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml 13 | - https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/25.0.2/kubernetes/keycloaks.k8s.keycloak.org-v1.yml 14 | - https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/25.0.2/kubernetes/kubernetes.yml 15 | -------------------------------------------------------------------------------- /cluster/apps/db/mysql/values.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/bitnami/charts/main/bitnami/mysql/values.schema.json 2 | auth: 3 | existingSecret: mysql-secret 4 | metrics: 5 | enabled: true 6 | primary: 7 | persistence: 8 | existingClaim: mysql 9 | podSecurityContext: 10 | enabled: true 11 | fsGroup: 997 12 | containerSecurityContext: 13 | enabled: true 14 | runAsUser: 997 15 | runAsNonRoot: true 16 | service: 17 | annotations: 18 | metallb.universe.tf/loadBalancerIPs: 172.16.1.32 19 | external-dns.alpha.kubernetes.io/hostname: mysql.hermleigh.home 20 | type: LoadBalancer 21 | -------------------------------------------------------------------------------- /cluster/apps/db/redis/values.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/bitnami/charts/main/bitnami/redis/values.schema.json 2 | clusterDomain: seawolf 3 | 4 | architecture: standalone 5 | 6 | auth: 7 | enabled: false 8 | 9 | master: 10 | persistence: 11 | enabled: false 12 | service: 13 | type: LoadBalancer 14 | dnsConfig: 15 | options: 16 | - name: ndots 17 | value: "8" 18 | 19 | metrics: 20 | enabled: true 21 | prometheusRule: 22 | enabled: true 23 | namespace: monitoring 24 | serviceMonitor: 25 | enabled: true 26 | interval: 1m 27 | 28 | useExternalDNS: 29 | enabled: true 30 | -------------------------------------------------------------------------------- /.github/linters/.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | default: true 3 | 4 | # MD013/line-length - Line length 5 | MD013: 6 | # Number of characters 7 | line_length: 240 8 | # Number of characters for headings 9 | heading_line_length: 80 10 | # Number of characters for code blocks 11 | code_block_line_length: 80 12 | # Include code blocks 13 | code_blocks: true 14 | # Include tables 15 | tables: true 16 | # Include headings 17 | headings: true 18 | # Strict length checking 19 | strict: false 20 | # Stern length checking 21 | stern: false 22 | 23 | # Allow all inline HTML 24 | MD033: false 25 | 26 | # All images without alt-text 27 | MD045: false 28 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | Apps to deploy: 2 | - [ ] Vault 3 | - [x] [node-hp-scan-to](https://github.com/manuc66/node-hp-scan-to) to paperless-ngx pod 4 | - [ ] [wger](https://github.com/wger-project/wger) 5 | - [ ] [Stirling-PDF](https://github.com/Frooodle/Stirling-PDF) 6 | - [ ] Thanos? Elastic? 7 | - [ ] Snipe-IT and/or homebox? 8 | - [ ] changedetecion.io 9 | 10 | Other tasks: 11 | - [ ] preload helm repos on argocd-app-controller using ansible kubectl connection 12 | - [ ] Add [grafana-dashboards-kubernetes](https://github.com/dotdc/grafana-dashboards-kubernetes) to grafana setup 13 | - [ ] CoreDns standalone config (still doing this??) 14 | 15 | 16 | -------------------------------------------------------------------------------- /cluster/apps/downloads/bazarr/scripts/post-process.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | printf "Cleaning subtitles for '%s' ...\n" "$1" 4 | python3 /add-ons/subcleaner/subcleaner.py "$1" -s 5 | 6 | case $1 in 7 | *movies*) section="1" ;; 8 | *shows*) section="2" ;; 9 | esac 10 | 11 | if [[ -n "$section" ]]; then 12 | printf "Refreshing Plex section '%s' for '%s' ...\n" "$section" "$(dirname "$1")" 13 | /usr/bin/curl -I -X GET -G \ 14 | --data-urlencode "path=$(dirname "$1")" \ 15 | --data-urlencode "X-Plex-Token=$2" \ 16 | --no-progress-meter \ 17 | "http://plex.media.svc.cluster.local:32400/library/sections/$section/refresh" 18 | fi 19 | -------------------------------------------------------------------------------- /cluster/kube-system/node-feature-discovery/google-coral-device.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/nfd.k8s-sigs.io/nodefeaturerule_v1alpha1.json 3 | apiVersion: nfd.k8s-sigs.io/v1alpha1 4 | kind: NodeFeatureRule 5 | metadata: 6 | name: google-coral-device 7 | spec: 8 | rules: 9 | - # Google Coral USB Accelerator 10 | name: google.coral 11 | labels: 12 | google.feature.node.kubernetes.io/coral: "true" 13 | matchFeatures: 14 | - feature: pci.device 15 | matchExpressions: 16 | vendor: { op: In, value: ["1ac1"] } 17 | device: { op: In, value: ["089a"] } 18 | -------------------------------------------------------------------------------- /cluster/apps/services/homepage/config/settings.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | title: Dashboard 3 | theme: dark 4 | color: neutral 5 | useEqualHeights: true 6 | layout: # key by group name in services.yaml 7 | Apps & Services: 8 | style: row 9 | columns: 4 10 | tab: Apps 11 | icon: mdi-application 12 | Media: 13 | style: row 14 | columns: 4 15 | tab: Apps 16 | icon: mdi-movie-open 17 | Downloads: 18 | style: row 19 | columns: 4 20 | tab: Apps 21 | icon: mdi-download 22 | Infrastructure: 23 | style: row 24 | columns: 4 25 | tab: Infrastructure 26 | Monitoring: 27 | style: row 28 | columns: 4 29 | tab: Infrastructure 30 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/network/cilium/templates/hubble/sa.yml.j2: -------------------------------------------------------------------------------- 1 | {% if cilium_hubble_tls_generate %} 2 | --- 3 | # Source: cilium/templates/hubble-generate-certs-serviceaccount.yaml 4 | apiVersion: v1 5 | kind: ServiceAccount 6 | metadata: 7 | name: hubble-generate-certs 8 | namespace: kube-system 9 | {% endif %} 10 | --- 11 | # Source: cilium/templates/hubble-relay-serviceaccount.yaml 12 | apiVersion: v1 13 | kind: ServiceAccount 14 | metadata: 15 | name: hubble-relay 16 | namespace: kube-system 17 | --- 18 | # Source: cilium/templates/hubble-ui-serviceaccount.yaml 19 | apiVersion: v1 20 | kind: ServiceAccount 21 | metadata: 22 | name: hubble-ui 23 | namespace: kube-system 24 | -------------------------------------------------------------------------------- /cluster/argocd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | namespace: argocd 7 | helmCharts: 8 | - name: argo-cd 9 | repo: https://argoproj.github.io/argo-helm 10 | version: 8.0.0 11 | namespace: argocd 12 | releaseName: argocd 13 | valuesFile: values.yaml 14 | patches: 15 | - patch: "- op: add\n path: /spec/template/spec/containers/0/env/-\n value: { name: 16 | ARGOCD_SYNC_WAVE_DELAY, value: '30' }" 17 | target: 18 | version: v1 19 | group: apps 20 | kind: StatefulSet 21 | name: argocd-application-controller 22 | labelSelector: app.kubernetes.io/name=argocd-application-controller 23 | -------------------------------------------------------------------------------- /cluster/bootstrap/infrastructure/keycloak.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: keycloak 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | source: 14 | repoURL: https://github.com/clearlybaffled/homelab 15 | path: cluster/apps/infrastructure/keycloak 16 | targetRevision: main 17 | destination: 18 | name: in-cluster 19 | namespace: infrastructure 20 | syncPolicy: 21 | automated: 22 | prune: true 23 | selfHeal: true 24 | syncOptions: 25 | - CreateNamespace=true 26 | - ServerSideApply=true 27 | -------------------------------------------------------------------------------- /cluster/kube-system/node-feature-discovery/zooz-zwave-device.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/nfd.k8s-sigs.io/nodefeaturerule_v1alpha1.json 3 | apiVersion: nfd.k8s-sigs.io/v1alpha1 4 | kind: NodeFeatureRule 5 | metadata: 6 | name: zooz-zwave-device 7 | spec: 8 | rules: 9 | - # Zooz Z-Stick 800 10 | name: zooz.zwave 11 | labels: 12 | zooz.feature.node.kubernetes.io/zwave: "true" 13 | matchFeatures: 14 | - feature: usb.device 15 | matchExpressions: 16 | class: { op: In, value: ["02"] } 17 | vendor: { op: In, value: ["1a86"] } 18 | device: { op: In, value: ["55d4"] } 19 | -------------------------------------------------------------------------------- /infrastructure/roles/common/tasks/os_vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Include common os variables 3 | include_vars: os.yml 4 | 5 | - name: gather os specific variable 6 | include_vars: "{{ item }}" 7 | with_first_found: 8 | - files: 9 | - "{{ ansible_distribution|lower }}-{{ ansible_distribution_version|lower|replace('/', '_') }}.yml" 10 | - "{{ ansible_distribution|lower }}-{{ ansible_distribution_release }}.yml" 11 | - "{{ ansible_distribution|lower }}-{{ ansible_distribution_major_version|lower|replace('/', '_') }}.yml" 12 | - "{{ ansible_distribution|lower }}.yml" 13 | - "{{ ansible_os_family|lower }}.yml" 14 | - defaults.yml 15 | skip: true 16 | -------------------------------------------------------------------------------- /infrastructure/roles/ca/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Open crypto container 3 | include_role: 4 | name: crypto_device 5 | vars: 6 | cmd: open 7 | when: purpose is defined 8 | 9 | - name: Signed 10 | block: 11 | - include_tasks: # noqa name 12 | file: "{{ task }}.yml" 13 | vars: 14 | task: |- 15 | {%- if purpose is not defined -%} 16 | root-ca 17 | {%- elif purpose == 'sub-ca' -%} 18 | sub-ca 19 | {%- else -%} 20 | sign 21 | {%- endif -%} 22 | 23 | always: 24 | - name: Close crypto container 25 | include_role: 26 | name: crypto_device 27 | vars: 28 | cmd: close 29 | -------------------------------------------------------------------------------- /cluster/apps/db/cloudnative-pg/values.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/cloudnative-pg/charts/main/charts/cloudnative-pg/values.schema.json 2 | 3 | # -- Container Security Context 4 | containerSecurityContext: 5 | allowPrivilegeEscalation: false 6 | readOnlyRootFilesystem: true 7 | runAsUser: 997 8 | runAsGroup: 997 9 | capabilities: 10 | drop: 11 | - "ALL" 12 | 13 | # -- Security Context for the whole pod 14 | podSecurityContext: 15 | runAsNonRoot: true 16 | seccompProfile: 17 | type: RuntimeDefault 18 | fsGroup: 997 19 | 20 | monitoring: 21 | grafanaDashboard: 22 | create: true 23 | namespace: monitoring 24 | podMonitorEnabled: true 25 | -------------------------------------------------------------------------------- /cluster/bootstrap/argocd.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: argocd 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | annotations: 12 | argocd.argoproj.io/sync-wave: '0' 13 | spec: 14 | project: default 15 | source: 16 | repoURL: https://github.com/clearlybaffled/homelab 17 | path: cluster/argocd 18 | targetRevision: main 19 | destination: 20 | name: in-cluster 21 | namespace: argocd 22 | syncPolicy: 23 | automated: 24 | prune: true 25 | selfHeal: true 26 | syncOptions: 27 | - CreateNamespace=true 28 | - ServerSideApply=true 29 | -------------------------------------------------------------------------------- /cluster/bootstrap/argocd/argocd.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: argocd 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | annotations: 12 | argocd.argoproj.io/sync-wave: '0' 13 | spec: 14 | project: default 15 | source: 16 | repoURL: https://github.com/clearlybaffled/homelab 17 | path: cluster/argocd 18 | targetRevision: main 19 | destination: 20 | name: in-cluster 21 | namespace: argocd 22 | syncPolicy: 23 | automated: 24 | prune: true 25 | selfHeal: true 26 | syncOptions: 27 | - CreateNamespace=true 28 | - ServerSideApply=true 29 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/ingress-nginx/wildcard-certificate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Certificate 3 | metadata: 4 | name: wildcard 5 | namespace: infrastructure 6 | spec: 7 | secretName: wildcard-certificate 8 | issuerRef: 9 | kind: ClusterIssuer 10 | name: ca-issuer 11 | commonName: "Hermleigh House Network Service" 12 | subject: 13 | organizations: 14 | - "HERMLEIGH.HOME" 15 | organizationalUnits: 16 | - "Hermleigh House Network" 17 | countries: 18 | - "US" 19 | dnsNames: 20 | - "seawolf.hermleigh.home" 21 | - "seawolf" 22 | - "*.seawolf.hermleigh.home" 23 | - "*.seawolf" 24 | - "*.hermleigh.home" 25 | ipAddresses: 26 | - "172.16.1.24" 27 | -------------------------------------------------------------------------------- /cluster/bootstrap/infrastructure/local-path-provisioner.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: local-path-provisioner 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | source: 14 | repoURL: https://github.com/clearlybaffled/homelab 15 | path: cluster/apps/infrastructure/local-path-provisioner 16 | targetRevision: main 17 | destination: 18 | name: in-cluster 19 | namespace: local-path-provisioner 20 | syncPolicy: 21 | automated: 22 | prune: true 23 | selfHeal: true 24 | syncOptions: 25 | - CreateNamespace=true 26 | - ServerSideApply=true 27 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/local-path-provisioner/storage-classes.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: storage.k8s.io/v1 3 | kind: StorageClass 4 | metadata: 5 | annotations: 6 | storageclass.kubernetes.io/is-default-class: 'False' 7 | name: local-path-videos 8 | provisioner: rancher.io/local-path 9 | parameters: 10 | nodePath: /videos/volumes 11 | volumeBindingMode: WaitForFirstConsumer 12 | reclaimPolicy: Retain 13 | --- 14 | apiVersion: storage.k8s.io/v1 15 | kind: StorageClass 16 | metadata: 17 | annotations: 18 | storageclass.kubernetes.io/is-default-class: 'False' 19 | name: local-path-shared 20 | provisioner: rancher.io/local-path 21 | parameters: 22 | nodePath: /share/volumes 23 | volumeBindingMode: WaitForFirstConsumer 24 | reclaimPolicy: Retain 25 | -------------------------------------------------------------------------------- /.github/linters/.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | ignore: | 3 | .ansible/ 4 | .direnv/ 5 | .private/ 6 | .vscode/ 7 | *.sops.* 8 | 9 | extends: default 10 | 11 | rules: 12 | truthy: 13 | allowed-values: ["true", "false"] 14 | 15 | comments: 16 | min-spaces-from-content: 1 17 | 18 | comments-indentation: false 19 | 20 | line-length: disable 21 | 22 | braces: 23 | min-spaces-inside: 0 24 | max-spaces-inside: 1 25 | 26 | brackets: 27 | min-spaces-inside: 0 28 | max-spaces-inside: 0 29 | 30 | indentation: 31 | indent-sequences: whatever 32 | spaces: 2 33 | 34 | hyphens: 35 | max-spaces-after: 1 36 | 37 | document-start: disable 38 | 39 | octal-values: 40 | forbid-explicit-octal: true 41 | forbid-implicit-octal: true 42 | -------------------------------------------------------------------------------- /cluster/bootstrap/infrastructure/reflector.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: reflector 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | annotations: 12 | argocd.argoproj.io/sync-wave: '3' 13 | spec: 14 | project: default 15 | source: 16 | chart: reflector 17 | repoURL: https://emberstack.github.io/helm-charts 18 | targetRevision: 9.1.4 19 | helm: 20 | releaseName: reflector 21 | destination: 22 | name: in-cluster 23 | namespace: infrastructure 24 | syncPolicy: 25 | automated: 26 | prune: true 27 | selfHeal: true 28 | syncOptions: 29 | - CreateNamespace=true 30 | - ServerSideApply=true 31 | -------------------------------------------------------------------------------- /infrastructure/terraform/kvm/README.md: -------------------------------------------------------------------------------- 1 | # KVM 2 | 3 | ## Requirements 4 | 5 | | Name | Version | 6 | |------|---------| 7 | | [terraform](#requirement\_terraform) | >= 1.5.7 | 8 | | [libvirt](#requirement\_libvirt) | 0.7.1 | 9 | 10 | ## Providers 11 | 12 | | Name | Version | 13 | |------|---------| 14 | | [libvirt](#provider\_libvirt) | 0.7.1 | 15 | 16 | ## Modules 17 | 18 | No modules. 19 | 20 | ## Resources 21 | 22 | | Name | Type | 23 | |------|------| 24 | | [libvirt_volume.fedora38-qcow2](https://registry.terraform.io/providers/dmacvicar/libvirt/0.7.1/docs/resources/volume) | resource | 25 | 26 | ## Inputs 27 | 28 | No inputs. 29 | 30 | ## Outputs 31 | 32 | No outputs. 33 | -------------------------------------------------------------------------------- /infrastructure/roles/common/vars/debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | system_packages: 3 | - dbus 4 | - gnupg-agent 5 | - software-properties-common 6 | - lm-sensors 7 | - nvme-cli 8 | 9 | k8s_required_packages: 10 | - python3-yaml 11 | - python3-kubernetes 12 | - python3-apt 13 | - age 14 | - apt-transport-https 15 | - software-properties-common 16 | - conntrack 17 | - iptables 18 | - apparmor 19 | - libseccomp2 20 | 21 | kvm_packages: 22 | - "qemu-system{{ (host_architecture == 'amd64') | ternary('-x86', ( (host_architecture == 'arm64') | ternary('-arm', ''))) }}" 23 | - qemu-utils 24 | - libvirt-clients 25 | - libvirt-daemon-system 26 | - libvirt-daemon-system-systemd 27 | - libvirt-daemon-driver-storage-rbd 28 | - libvirt-dev 29 | - virtinst 30 | - bridge-utils 31 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/ingress-nginx/external-wildcard-certificate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Certificate 3 | metadata: 4 | name: external-wildcard 5 | namespace: infrastructure 6 | spec: 7 | secretName: external-wildcard-certificate 8 | issuerRef: 9 | kind: ClusterIssuer 10 | name: letsencrypt-production 11 | commonName: hermleigh.cc 12 | subject: 13 | organizations: 14 | - "HERMLEIGH.CC" 15 | organizationalUnits: 16 | - "Hermleigh House Network" 17 | countries: 18 | - "US" 19 | dnsNames: 20 | - "*.hermleigh.cc" 21 | - "hermleigh.cc" 22 | secretTemplate: 23 | annotations: 24 | reflector.v1.k8s.emberstack.com/reflection-allowed: "true" 25 | reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true" 26 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/runtime/tasks/reset.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # WHY?? 3 | # - name: CRI-O | Remove crictl 4 | # file: 5 | # name: "{{ item }}" 6 | # state: absent 7 | # loop: 8 | # - /etc/crictl.yaml 9 | # - "{{ bin_dir }}/crictl" 10 | # tags: 11 | # - reset_crio 12 | 13 | - name: CRI-O | Stop crio service 14 | service: 15 | name: crio 16 | daemon_reload: true 17 | enabled: false 18 | # masked: true 19 | state: stopped 20 | tags: 21 | - reset_crio 22 | 23 | # Also, why?? 24 | # - name: CRI-O | Remove CRI-O configuration files 25 | # file: 26 | # name: "{{ item }}" 27 | # state: absent 28 | # loop: 29 | # - /etc/crio 30 | # - /etc/containers 31 | # - /etc/systemd/system/crio.service.d 32 | # tags: 33 | # - reset_crio 34 | -------------------------------------------------------------------------------- /cluster/apps/services/homepage/config/widgets.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # - logo: 3 | - kubernetes: 4 | cluster: 5 | show: true 6 | cpu: true 7 | memory: true 8 | showLabel: true 9 | label: "seawolf" 10 | nodes: 11 | show: true 12 | cpu: true 13 | memory: true 14 | showLabel: true 15 | - search: 16 | provider: custom 17 | url: https://search.unlocked.link/search?q= 18 | target: _blank 19 | # focus: true # Optional, will set focus to the search bar on page load 20 | - openmeteo: 21 | label: Home # optional 22 | latitude: {{HOMEPAGE_VAR_LATITUDE}} 23 | longitude: {{HOMEPAGE_VAR_LONGITUDE}} 24 | timezone: America/New_York 25 | units: imperial # or metric 26 | cache: 5 # Time in minutes to cache API responses, to stay within limits 27 | -------------------------------------------------------------------------------- /infrastructure/inventory/group_vars/ipaserver/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Reference: https://github.com/freeipa/ansible-freeipa/blob/master/roles/ipaserver/README.md#variables 3 | 4 | # Base 5 | ipaserver_domain: hermleigh.home 6 | ipaserver_realm: HERMLEIGH.HOME 7 | ipaserver_hostname: tang.hermleigh.home 8 | 9 | # Server 10 | ipaserver_no_host_dns: true 11 | 12 | # SSL Certificate 13 | 14 | # Client 15 | 16 | ipaclient_no_ntp: true 17 | 18 | # Certificate System 19 | ipaserver_subject_base: "OU=Hermleigh House Network,O=HERMLEIGH.HOME,C=US" 20 | ipaserver_ca_subject: "CN=IPA Intermediate CA,OU=Hermleigh House Network,O=HERMLEIGH.HOME,C=US" 21 | ipaserver_ca_signing_algorithm: SHA512withRSA 22 | 23 | # DNS 24 | 25 | # AD Trust 26 | 27 | # Special 28 | 29 | ipa_csr_path: "/tmp/{{ groups.ipaserver[0] + '-ipa' }}" 30 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/cert-manager/issuer-letsencrypt-prod.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/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 | email: clearlybaffled120@gmail.com 11 | preferredChain: "" 12 | privateKeySecretRef: 13 | name: letsencrypt-production 14 | server: https://acme-v02.api.letsencrypt.org/directory 15 | solvers: 16 | - dns01: 17 | cloudflare: 18 | apiTokenSecretRef: 19 | name: cloudflare-api-token 20 | key: api-token 21 | selector: 22 | dnsZones: 23 | - hermleigh.cc 24 | -------------------------------------------------------------------------------- /cluster/apps/home/grocy/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: grocy 5 | namespace: services 6 | annotations: 7 | gethomepage.dev/enabled: "true" 8 | gethomepage.dev/group: "Apps & Services" 9 | gethomepage.dev/icon: grocy.png 10 | gethomepage.dev/description: "ERP beyond your Fridge" 11 | labels: 12 | app.kubernetes.io/name: grocy 13 | spec: 14 | ingressClassName: nginx 15 | rules: 16 | - host: &host grocy.hermleigh.cc 17 | http: 18 | paths: 19 | - path: / 20 | pathType: Prefix 21 | backend: 22 | service: 23 | name: grocy 24 | port: 25 | number: 8080 26 | tls: 27 | - hosts: 28 | - *host 29 | secretName: external-wildcard-certificate 30 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/cert-manager/issuer-letsencrypt-staging.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/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 | email: clearlybaffled120@gmail.com 11 | preferredChain: "" 12 | privateKeySecretRef: 13 | name: letsencrypt-staging 14 | server: https://acme-staging-v02.api.letsencrypt.org/directory 15 | solvers: 16 | - dns01: 17 | cloudflare: 18 | apiTokenSecretRef: 19 | name: cloudflare-api-token 20 | key: api-token 21 | selector: 22 | dnsZones: 23 | - hermleigh.cc 24 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/traefik/ingress-route.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: traefik.io/v1alpha1 2 | kind: IngressRoute 3 | metadata: 4 | name: traefik-dashboard 5 | namespace: traefik-system 6 | annotations: 7 | cert-manager.io/common-name: traefik.seawolf.hermleigh.home 8 | cert-manager.io/private-key-algorithm: ECDSA 9 | cert-manager.io/private-key-size: '384' 10 | cert-manager.io/subject-countries: US 11 | cert-manager.io/subject-organizationalunits: Kubernetes - Services 12 | cert-manager.io/subject-organizations: HERMLEIGH.HOME 13 | spec: 14 | entryPoints: 15 | - websecure 16 | routes: 17 | - match: Host(`traefik.seawolf.hermleigh.home`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`)) 18 | kind: Rule 19 | services: 20 | - name: api@internal 21 | kind: TraefikService 22 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/registry/templates/registry-ing.yml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: registry 5 | namespace: {{ registry_namespace }} 6 | {% if registry_ingress_annotations %} 7 | annotations: 8 | {{ registry_ingress_annotations | to_nice_yaml(indent=2, width=1337) | indent(width=4) }} 9 | {% endif %} 10 | spec: 11 | {% if registry_ingress_tls_secret %} 12 | tls: 13 | - hosts: 14 | - {{ registry_ingress_host }} 15 | secretName: {{ registry_ingress_tls_secret }} 16 | {% endif %} 17 | rules: 18 | - host: {{ registry_ingress_host }} 19 | http: 20 | paths: 21 | - path: / 22 | pathType: Prefix 23 | backend: 24 | service: 25 | name: registry 26 | port: 27 | number: {{ registry_port }} 28 | -------------------------------------------------------------------------------- /scripts/install_helm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HELM=/usr/local/bin/helm 4 | JQ=/usr/bin/jq 5 | VALUES=values.yaml 6 | KONFIG=kustomization.yaml 7 | YQ=/usr/local/bin/yq 8 | 9 | # $HELM install --create-namespace -f $VALUES $($YQ -o=json -I=0 '.helmCharts[0]' $KONFIG | $JQ -r --arg name $(basename $PWD) '"\($name) \(.name) --version \(.version) --repo \(.repo) -n \(if .namespace then .namespace else $name end) "') 10 | 11 | if [[ $# -ne 0 ]]; then 12 | pushd $1 &>/dev/null || return 13 | fi 14 | 15 | CMD="$HELM install --create-namespace -f $VALUES " 16 | CMD+=$($YQ -o=json -I=0 '.helmCharts[0]' $KONFIG | $JQ -r --arg name $(basename $PWD) '"\($name) \(.name) --version \(.version) --repo \(.repo) -n \(if .namespace then .namespace else $name end) "') 17 | 18 | echo "Command is: $CMD" 19 | 20 | exec ${CMD} 21 | 22 | popd &>/dev/null || true 23 | -------------------------------------------------------------------------------- /infrastructure/roles/ca/tasks/directory.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set up CA directory 3 | file: 4 | path: "{{(ca_home, item)|path_join }}" 5 | state: directory 6 | mode: "{{ (item == 'private') | ternary('0700','0755') }}" 7 | with_items: 8 | - certs 9 | - db 10 | - private 11 | 12 | - name: Create empty index file 13 | copy: 14 | dest: "{{ (ca_home, 'db','index')|path_join }}" 15 | mode: '0644' 16 | content: '' 17 | force: false 18 | 19 | - name: Set initial serial number 20 | copy: 21 | dest: "{{ (ca_home, 'db','serial') | path_join }}" 22 | mode: '0644' 23 | content: "{{ lookup('pipe', 'openssl rand -hex 16') }}" 24 | force: false 25 | 26 | - name: Set initial CRL number 27 | copy: 28 | dest: "{{ (ca_home, 'db', 'crlnumber') | path_join }}" 29 | mode: '0644' 30 | content: '1001' 31 | -------------------------------------------------------------------------------- /cluster/apps/db/cloudnative-pg/cluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: postgresql.cnpg.io/v1 2 | kind: Cluster 3 | metadata: 4 | name: cnpg-cluster 5 | namespace: db 6 | spec: 7 | instances: 1 8 | 9 | imageName: localhost:10010/postgresql-vectors:16.2-10 10 | 11 | superuserSecret: 12 | name: cloudnative-pg-superuser 13 | enableSuperuserAccess: true 14 | 15 | bootstrap: 16 | initdb: 17 | database: app 18 | owner: app 19 | secret: 20 | name: cloudnative-pg-app-user 21 | postInitSQL: 22 | - "CREATE EXTENSION cube;" 23 | - "CREATE EXTENSION earthdistance;" 24 | - "CREATE EXTENSION vectors;" 25 | 26 | postgresql: 27 | shared_preload_libraries: 28 | - "vectors.so" 29 | 30 | storage: 31 | size: 50Gi 32 | storageClass: local-path 33 | monitoring: 34 | enablePodMonitor: true 35 | -------------------------------------------------------------------------------- /cluster/cluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: cluster 5 | namespace: argocd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | project: default 10 | 11 | source: 12 | repoURL: https://github.com/clearlybaffled/homelab 13 | path: cluster/bootstrap 14 | targetRevision: main 15 | directory: 16 | recurse: true 17 | 18 | destination: 19 | name: in-cluster 20 | namespace: argocd 21 | 22 | syncPolicy: 23 | automated: 24 | prune: true 25 | selfHeal: true 26 | syncOptions: 27 | - CreateNamespace=true 28 | - ServerSideApply=true 29 | - RespectIgnoreDifferences=true 30 | ignoreDifferences: 31 | - group: argoproj.io 32 | kind: Application 33 | jsonPointers: 34 | - /spec/syncPolicy 35 | -------------------------------------------------------------------------------- /infrastructure/terraform/ipa/resources.tf: -------------------------------------------------------------------------------- 1 | resource "libvirt_cloudinit_disk" "ipa-cloud-init" { 2 | name = "ipa-cloud-init.iso" 3 | pool = "cluster" 4 | user_data = templatefile( 5 | "${path.module}/ipa_cloud_init.tftpl", 6 | { 7 | ssh_pubkey = data.sops_file.ansible_ssh_key.data["ansible_user_ssh_pubkey"] 8 | ssh_key = data.sops_file.ansible_ssh_key.data["ansible_user_ssh_key"] 9 | root_ca_crt = data.sops_file.root_ca_crt.data["root_ca.crt"] 10 | } 11 | ) 12 | network_config = file("${path.module}/ipa_network_config") 13 | } 14 | 15 | resource "libvirt_volume" "ipa-root" { 16 | name = "ipa-root.qcow2" 17 | pool = "cluster" 18 | format = "qcow2" 19 | size = "10737418240" 20 | base_volume_name = "fedora38.qcow2" 21 | base_volume_pool = "cluster" 22 | 23 | } 24 | -------------------------------------------------------------------------------- /infrastructure/roles/download/templates/kubeadm-images.yaml.j2: -------------------------------------------------------------------------------- 1 | apiVersion: kubeadm.k8s.io/v1beta3 2 | kind: InitConfiguration 3 | nodeRegistration: 4 | criSocket: {{ cri_socket }} 5 | --- 6 | apiVersion: kubeadm.k8s.io/v1beta3 7 | kind: ClusterConfiguration 8 | imageRepository: {{ kube_image_repo }} 9 | kubernetesVersion: {{ kube_version }} 10 | etcd: 11 | {% if etcd_deployment_type == "kubeadm" %} 12 | local: 13 | imageRepository: "{{ etcd_image_repo | regex_replace("/etcd$","") }}" 14 | imageTag: "{{ etcd_image_tag }}" 15 | {% else %} 16 | external: 17 | endpoints: 18 | {% for endpoint in etcd_access_addresses.split(',') %} 19 | - {{ endpoint }} 20 | {% endfor %} 21 | {% endif %} 22 | dns: 23 | type: CoreDNS 24 | imageRepository: {{ coredns_image_repo | regex_replace('/coredns(?!/coredns).*$','') }} 25 | imageTag: {{ coredns_image_tag }} 26 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/storage/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | storage: 3 | namespaces: 4 | - services 5 | - media 6 | - downloads 7 | volumes: 8 | - name: files 9 | path: /share/Files 10 | size: &share 5.5Ti 11 | - name: books 12 | path: /share/Books 13 | size: *share 14 | - name: pictures 15 | path: /share/Pictures 16 | size: *share 17 | - name: movies 18 | path: /videos/Movies 19 | size: &videos 7.5Ti 20 | - name: tv-shows 21 | path: "/videos/TV Shows" 22 | size: *videos 23 | - name: audio 24 | path: /share/Audio 25 | size: *share 26 | - name: downloads 27 | path: /share/Downloads 28 | size: *share 29 | - name: music 30 | path: /share/Music 31 | size: *share 32 | - name: data 33 | path: /share 34 | size: *share 35 | -------------------------------------------------------------------------------- /cluster/apps/infrastructure/traefik/values.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | providers: 3 | kubernetesCRD: 4 | allowCrossNamespace: true 5 | kubernetesIngress: 6 | publishedService: 7 | # To fix argocd stuck in progressing https://github.com/argoproj/argo-cd/issues/968#issuecomment-955976397 8 | enabled: true 9 | # Need to override path since otherwise the namespace is set as default 10 | pathOverride: traefik-system/traefik 11 | 12 | additionalArguments: 13 | - "--api" 14 | - "--api.insecure=true" 15 | 16 | logs: 17 | general: 18 | level: ERROR 19 | 20 | ingressRoute: 21 | dashboard: 22 | enabled: false 23 | 24 | persistence: 25 | enabled: false 26 | 27 | service: 28 | type: LoadBalancer 29 | annotations: 30 | metallb.universe.tf/address-pool: ingress-pool 31 | external-dns.alpha.kubernetes.io/hostname: traefik.seawolf.hermleigh.home 32 | externalTrafficPolicy: Local 33 | -------------------------------------------------------------------------------- /cluster/apps/services/wger/values.yaml: -------------------------------------------------------------------------------- 1 | app: 2 | django: 3 | existingDatabase: 4 | enabled: true 5 | host: cnpg-cluster-rw.db.svc 6 | existingSecret: 7 | name: wger-secret 8 | dbuserKey: DJANGO_DB_USER 9 | dbpsKey: DJANGO_DB_PASSWORD 10 | environment: 11 | - name: DJANGO_CACHE_LOCATION 12 | value: "redis://redis-master.db.svc:6379/1" 13 | 14 | ingress: 15 | enabled: true 16 | ingressClassName: nginx 17 | url: fit.hermleigh.cc 18 | tls: true 19 | annotations: 20 | gethomepage.dev/enabled: "true" 21 | gethomepage.dev/name: Wger 22 | gethomepage.dev/description: "Personal workout/food tracker" 23 | gethomepage.dev/icon: "wger.svg" 24 | gethomepage.dev/group: "Apps & Services" 25 | cert-manager.io/cluster-issuer: ca-issuer 26 | 27 | redis: 28 | storage: 29 | className: local-path 30 | 31 | postgres: 32 | enabled: false 33 | -------------------------------------------------------------------------------- /infrastructure/roles/download/tasks/prep_download.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: prep_download | Set a few facts 3 | set_fact: 4 | download_force_cache: "{{ true if download_run_once else download_force_cache }}" 5 | tags: 6 | - facts 7 | 8 | - name: prep_download | Register docker images info 9 | shell: "{{ image_info_command }}" # noqa command-instead-of-shell image_info_command contains pipe therefore requires shell 10 | no_log: "{{ not (unsafe_show_logs|bool) }}" 11 | register: docker_images 12 | failed_when: false 13 | changed_when: false 14 | check_mode: false 15 | when: download_container 16 | 17 | - name: prep_download | Create staging directory on remote node 18 | file: 19 | path: "{{ download_cache_dir }}/images" 20 | state: directory 21 | recurse: true 22 | mode: "0777" 23 | owner: "{{ ansible_ssh_user | default(ansible_user_id) }}" 24 | group: "{{ kube_group }}" 25 | -------------------------------------------------------------------------------- /cluster/bootstrap/services/wger.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: wger 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: wger 15 | repoURL: https://wger-project.github.io/helm-charts 16 | targetRevision: 0.2.4 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/services/wger/values.yaml 20 | releaseName: wger 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/services/wger 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: services 28 | syncPolicy: 29 | automated: 30 | prune: true 31 | selfHeal: true 32 | syncOptions: 33 | - CreateNamespace=true 34 | - ServerSideApply=true 35 | -------------------------------------------------------------------------------- /.github/linters/.ansible-lint: -------------------------------------------------------------------------------- 1 | --- 2 | parseable: true 3 | exclude_paths: 4 | - cluster 5 | - '*.yaml' 6 | 7 | skip_list: 8 | # see https://docs.ansible.com/ansible-lint/rules/default_rules.html for a list of all default rules 9 | 10 | # These rules are intentionally skipped: 11 | # 12 | # [E204]: "Lines should be no longer than 160 chars" 13 | - '204' 14 | 15 | # [E701]: "meta/main.yml should contain relevant info" 16 | - '701' 17 | 18 | # [role-name] "meta/main.yml" Role name role-name does not match ``^+$`` pattern 19 | - 'role-name' 20 | 21 | - 'experimental' 22 | # [var-naming] "defaults/main.yml" File defines variable 'apiVersion' that violates variable naming standards 23 | - 'var-naming' 24 | - 'var-spacing' 25 | 26 | - fqcn-builtins 27 | 28 | - name[casing] 29 | 30 | - yaml 31 | 32 | - no-changed-when 33 | 34 | - schema[meta] 35 | - 'risky-file-permissions' 36 | - name[template] 37 | -------------------------------------------------------------------------------- /cluster/bootstrap/home/owntracks.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: owntracks 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/home/owntracks/values.yaml 20 | releaseName: owntracks 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/home/owntracks 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: home 28 | syncPolicy: 29 | automated: 30 | prune: true 31 | selfHeal: true 32 | syncOptions: 33 | - CreateNamespace=true 34 | - ServerSideApply=true 35 | -------------------------------------------------------------------------------- /cluster/bootstrap/media/navidrome.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: navidrome 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/media/navidrome/values.yaml 20 | releaseName: navidrome 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/media/navidrome 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: media 28 | syncPolicy: 29 | automated: 30 | prune: true 31 | selfHeal: true 32 | syncOptions: 33 | - CreateNamespace=true 34 | - ServerSideApply=true 35 | -------------------------------------------------------------------------------- /cluster/bootstrap/services/homepage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: homepage 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: homepage 15 | repoURL: http://jameswynn.github.io/helm-charts 16 | targetRevision: 2.0.2 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/services/homepage/values.yaml 20 | releaseName: homepage 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/services/homepage 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: homepage 28 | syncPolicy: 29 | automated: 30 | prune: true 31 | selfHeal: true 32 | syncOptions: 33 | - CreateNamespace=true 34 | - ServerSideApply=true 35 | -------------------------------------------------------------------------------- /cluster/bootstrap/services/linkace.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: linkace 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/services/linkace/values.yaml 20 | releaseName: linkace 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/services/linkace 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: services 28 | syncPolicy: 29 | automated: 30 | prune: true 31 | selfHeal: true 32 | syncOptions: 33 | - CreateNamespace=true 34 | - ServerSideApply=true 35 | -------------------------------------------------------------------------------- /cluster/bootstrap/services/linkding.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: linkding 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/services/linkding/values.yaml 20 | releaseName: linkding 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/services/linkding 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: services 28 | syncPolicy: 29 | automated: 30 | prune: true 31 | selfHeal: true 32 | syncOptions: 33 | - CreateNamespace=true 34 | - ServerSideApply=true 35 | -------------------------------------------------------------------------------- /cluster/bootstrap/services/wallabag.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: wallabag 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/services/wallabag/values.yaml 20 | releaseName: wallabag 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/services/wallabag 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: services 28 | syncPolicy: 29 | automated: 30 | prune: true 31 | selfHeal: true 32 | syncOptions: 33 | - CreateNamespace=true 34 | - ServerSideApply=true 35 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/linkwarden.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: # noqa name 3 | file: ../init-postgres.yml 4 | apply: 5 | vars: 6 | db_user_var: DB_USER 7 | db_pass_var: DB_PASS 8 | db_names: 9 | - linkwarden 10 | 11 | - name: Create secret for db url 12 | community.sops.sops_encrypt: 13 | path: "{{ app.app_dir }}/db_url.sops.yaml" 14 | encrypted_regex: "^(data|stringData)$" 15 | content_yaml: 16 | apiVersion: v1 17 | kind: Secret 18 | type: Opaque 19 | metadata: 20 | name: linkwarden-db-url 21 | namespace: "{{ app.namespace }}" 22 | data: 23 | DATABASE_URL: "{{ ('postgres://' ~ secret.data.DB_USER | b64decode ~ ':' ~ secret.data.DB_PASS | b64decode ~ '@cnpg-cluster-rw.db:5432/linkwarden' ) | b64encode }}" 24 | vars: 25 | secret: "{{ lookup('community.sops.sops', app.app_dir+'/secret.sops.yaml') | from_yaml }}" 26 | -------------------------------------------------------------------------------- /cluster/bootstrap/home/grocy.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: grocy 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | annotations: 12 | argocd.argoproj.io/sync-wave: '4' 13 | spec: 14 | project: default 15 | source: 16 | repoURL: https://github.com/clearlybaffled/homelab 17 | path: cluster/apps/home/grocy 18 | targetRevision: main 19 | destination: 20 | name: in-cluster 21 | namespace: home 22 | ignoreDifferences: 23 | - kind: PersistentVolume 24 | jsonPointers: 25 | - /spec/claimRef/resourceVersion 26 | - /spec/claimRef/uid 27 | - /status/lastPhaseTransitionTime 28 | syncPolicy: 29 | automated: 30 | prune: true 31 | selfHeal: true 32 | syncOptions: 33 | - CreateNamespace=true 34 | - ServerSideApply=true 35 | - RespectIgnoreDifferences=true 36 | -------------------------------------------------------------------------------- /cluster/bootstrap/db/redis.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: redis 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | annotations: 12 | argocd.argoproj.io/sync-wave: '0' 13 | spec: 14 | project: default 15 | sources: 16 | - chart: redis 17 | repoURL: https://charts.bitnami.com/bitnami 18 | targetRevision: 18.19.2 19 | helm: 20 | valueFiles: 21 | - $repo/cluster/apps/db/redis/values.yaml 22 | releaseName: redis 23 | - repoURL: https://github.com/clearlybaffled/homelab 24 | path: cluster/apps/db/redis 25 | targetRevision: main 26 | ref: repo 27 | destination: 28 | name: in-cluster 29 | namespace: db 30 | syncPolicy: 31 | automated: 32 | prune: true 33 | selfHeal: true 34 | syncOptions: 35 | - CreateNamespace=true 36 | - ServerSideApply=true 37 | -------------------------------------------------------------------------------- /cluster/bootstrap/services/stirling-pdf.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: stirling-pdf 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/services/stirling-pdf/values.yaml 20 | releaseName: stirling-pdf 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/services/stirling-pdf 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: services 28 | syncPolicy: 29 | automated: 30 | prune: true 31 | selfHeal: true 32 | syncOptions: 33 | - CreateNamespace=true 34 | - ServerSideApply=true 35 | -------------------------------------------------------------------------------- /infrastructure/roles/kubernetes/control_plane/defaults/main/etcd.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Set etcd user/group 3 | etcd_owner: etcd 4 | 5 | # Note: This does not set up DNS entries. It simply adds the following DNS 6 | # entries to the certificate 7 | etcd_cert_alt_names: 8 | - "etcd.kube-system.svc.{{ dns_domain }}" 9 | - "etcd.kube-system.svc" 10 | - "etcd.kube-system" 11 | - "etcd" 12 | etcd_cert_alt_ips: [] 13 | 14 | etcd_heartbeat_interval: "250" 15 | etcd_election_timeout: "5000" 16 | 17 | # etcd_snapshot_count: "10000" 18 | 19 | etcd_metrics: "basic" 20 | 21 | ## A dictionary of extra environment variables to add to etcd.env, formatted like: 22 | ## etcd_extra_vars: 23 | ## var1: "value1" 24 | ## var2: "value2" 25 | ## Note this is different from the etcd role with ETCD_ prfexi, caps, and underscores 26 | etcd_extra_vars: {} 27 | 28 | # etcd_quota_backend_bytes: "2147483648" 29 | # etcd_max_request_bytes: "1572864" 30 | 31 | etcd_compaction_retention: "8" 32 | -------------------------------------------------------------------------------- /infrastructure/roles/kubernetes/node/tasks/accounts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create kube_cert_group 3 | group: 4 | name: "{{ kube_cert_group }}" 5 | system: true 6 | 7 | - name: Create kube group 8 | group: 9 | name: "{{ kube_group }}" 10 | system: true 11 | 12 | - name: Create user 13 | user: 14 | name: "{{ kube_owner }}" 15 | groups: 16 | - "{{ kube_cert_group }}" 17 | - sys 18 | shell: "/sbin/nologin" 19 | create_home: false 20 | system: true 21 | comment: "Kubernetes User" 22 | 23 | - name: Add ansible_user to kube groups 24 | user: 25 | name: "{{ ansible_user }}" 26 | groups: 27 | - "{{ kube_group }}" 28 | - "{{ kube_cert_group }}" 29 | append: true 30 | 31 | - name: Ensure ansible_user has a ~/.kube directory 32 | file: 33 | path: "{{ ansible_homedir }}/.kube" 34 | owner: "{{ ansible_user }}" 35 | group: "{{ ansible_group }}" 36 | state: directory 37 | mode: "0700" 38 | -------------------------------------------------------------------------------- /cluster/bootstrap/services/change-detection.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: change-detection 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/services/change-detection/values.yaml 20 | releaseName: change-detection 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/services/change-detection 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: services 28 | syncPolicy: 29 | automated: 30 | prune: true 31 | selfHeal: true 32 | syncOptions: 33 | - CreateNamespace=true 34 | - ServerSideApply=true 35 | -------------------------------------------------------------------------------- /cluster/bootstrap/infrastructure/oauth2-proxy.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: oauth2-proxy 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: oauth2-proxy 15 | repoURL: https://oauth2-proxy.github.io/manifests 16 | targetRevision: 7.12.13 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/infrastructure/oauth2-proxy/values.yaml 20 | releaseName: oauth2-proxy 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/infrastructure/oauth2-proxy 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: infrastructure 28 | syncPolicy: 29 | automated: 30 | prune: true 31 | selfHeal: true 32 | syncOptions: 33 | - CreateNamespace=true 34 | - ServerSideApply=true 35 | -------------------------------------------------------------------------------- /infrastructure/roles/kubernetes/kubelet/files/10-kubeadm.conf: -------------------------------------------------------------------------------- 1 | # Note: This dropin only works with kubeadm and kubelet v1.11+ 2 | [Service] 3 | Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf" 4 | Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml" 5 | # This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically 6 | EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env 7 | # This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use 8 | # the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file. 9 | EnvironmentFile=-/etc/default/kubelet 10 | ExecStart= 11 | ExecStart=/usr/local/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS 12 | -------------------------------------------------------------------------------- /cluster/bootstrap/db/cloudnative-pg.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: cloudnative-pg 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | annotations: 12 | argocd.argoproj.io/sync-wave: '3' 13 | spec: 14 | project: default 15 | sources: 16 | - chart: cloudnative-pg 17 | repoURL: https://cloudnative-pg.github.io/charts 18 | targetRevision: 0.20.2 19 | helm: 20 | valueFiles: 21 | - $repo/cluster/apps/db/cloudnative-pg/values.yaml 22 | releaseName: cloudnative-pg 23 | - repoURL: https://github.com/clearlybaffled/homelab 24 | path: cluster/apps/db/cloudnative-pg 25 | targetRevision: main 26 | ref: repo 27 | destination: 28 | name: in-cluster 29 | namespace: db 30 | syncPolicy: 31 | automated: 32 | prune: true 33 | selfHeal: true 34 | syncOptions: 35 | - CreateNamespace=true 36 | - ServerSideApply=true 37 | -------------------------------------------------------------------------------- /cluster/bootstrap/infrastructure/traefik.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: traefik 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | annotations: 12 | argocd.argoproj.io/sync-wave: '1' 13 | spec: 14 | project: default 15 | sources: 16 | - chart: traefik 17 | repoURL: https://traefik.github.io/charts 18 | targetRevision: 35.2.0 19 | helm: 20 | valueFiles: 21 | - $repo/cluster/apps/infrastructure/traefik/values.yaml 22 | releaseName: traefik 23 | - repoURL: https://github.com/clearlybaffled/homelab 24 | path: cluster/apps/infrastructure/traefik 25 | targetRevision: main 26 | ref: repo 27 | destination: 28 | name: in-cluster 29 | namespace: traefik-system 30 | syncPolicy: 31 | automated: 32 | prune: true 33 | selfHeal: true 34 | syncOptions: 35 | - CreateNamespace=true 36 | - ServerSideApply=true 37 | -------------------------------------------------------------------------------- /infrastructure/roles/crypto_device/tasks/open.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Open crypto partition 3 | # Reference: https://wiki.archlinux.org/title/Dm-crypt/Device_encryption#Encryption_options_for_plain_mode 4 | command: 5 | cmd: >- 6 | cryptsetup open 7 | --type plain 8 | --size={{ ((crypt.size | human_to_bytes(default_unit='M')) / crypt_device_blocksize|int)|int }} 9 | --offset={{ crypt.offset }} 10 | --key-file={{ crypt_key_file | default(crypt_device_file) }} 11 | --keyfile-offset={{ crypt.key_offset * crypt_device_blocksize|int }} 12 | --keyfile-size={{ ((crypt.key_size | human_to_bytes(default_unit='K')) / crypt_device_blocksize|int)|int }} 13 | {{ crypt_device_file }} 14 | {{ crypt_name }} 15 | creates: "{{ crypt_mapping_file }}" 16 | register: crypt_open 17 | 18 | - name: Mount crypto partition 19 | ansible.posix.mount: 20 | path: "{{ crypt_mount_path }}" 21 | state: ephemeral 22 | fstype: ext4 23 | src: "{{ crypt_mapping_file }}" 24 | -------------------------------------------------------------------------------- /infrastructure/roles/workstation/tasks/debian.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add HashiCorp apt repository key 3 | block: 4 | - name: Download Hashicorp repo GPG key 5 | get_url: 6 | url: "{{ hashicorp_repo_key_url }}" 7 | dest: /etc/apt/trusted.gpg.d/hashicorp.asc 8 | mode: '0644' 9 | force: true 10 | 11 | - name: HashiCorp | apt source 12 | apt_repository: 13 | repo: "deb [signed-by=/etc/apt/trusted.gpg.d/hashicorp.asc] https://apt.releases.hashicorp.com {{ ansible_lsb.codename }} main" 14 | state: present 15 | 16 | - name: Update apt cache 17 | apt: 18 | update_cache: true 19 | 20 | - name: Load Root CA into system ca trust store 21 | copy: 22 | dest: "/usr/local/share/ca-certificates/{{ domain_name }}-root.crt" 23 | content: "{{ root_ca.crt | b64decode }}" 24 | notify: 25 | - "update ca truststore" 26 | 27 | - name: Ensure a locale exists 28 | community.general.locale_gen: 29 | name: en_US.UTF-8 30 | state: present 31 | -------------------------------------------------------------------------------- /cluster/bootstrap/infrastructure/cert-manager.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: cert-manager 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | annotations: 12 | argocd.argoproj.io/sync-wave: '1' 13 | spec: 14 | project: default 15 | sources: 16 | - chart: cert-manager 17 | repoURL: https://charts.jetstack.io 18 | targetRevision: v1.17.2 19 | helm: 20 | valueFiles: 21 | - $repo/cluster/apps/infrastructure/cert-manager/values.yaml 22 | releaseName: cert-manager 23 | - repoURL: https://github.com/clearlybaffled/homelab 24 | path: cluster/apps/infrastructure/cert-manager 25 | targetRevision: main 26 | ref: repo 27 | destination: 28 | name: in-cluster 29 | namespace: cert-manager 30 | syncPolicy: 31 | automated: 32 | prune: true 33 | selfHeal: true 34 | syncOptions: 35 | - CreateNamespace=true 36 | - ServerSideApply=true 37 | -------------------------------------------------------------------------------- /cluster/bootstrap/kube-system/node-feature-discovery.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: node-feature-discovery 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: node-feature-discovery 15 | repoURL: https://kubernetes-sigs.github.io/node-feature-discovery/charts 16 | targetRevision: 0.15.4 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/kube-system/node-feature-discovery/values.yaml 20 | releaseName: node-feature-discovery 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/kube-system/node-feature-discovery 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: kube-system 28 | syncPolicy: 29 | automated: 30 | prune: true 31 | selfHeal: true 32 | syncOptions: 33 | - CreateNamespace=true 34 | - ServerSideApply=true 35 | -------------------------------------------------------------------------------- /cluster/bootstrap/infrastructure/external-dns.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: external-dns 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | annotations: 12 | argocd.argoproj.io/sync-wave: '1' 13 | spec: 14 | project: default 15 | sources: 16 | - chart: external-dns 17 | repoURL: https://kubernetes-sigs.github.io/external-dns/ 18 | targetRevision: 1.16.1 19 | helm: 20 | valueFiles: 21 | - $repo/cluster/apps/infrastructure/external-dns/values.yaml 22 | releaseName: external-dns 23 | - repoURL: https://github.com/clearlybaffled/homelab 24 | path: cluster/apps/infrastructure/external-dns 25 | targetRevision: main 26 | ref: repo 27 | destination: 28 | name: in-cluster 29 | namespace: infrastructure 30 | syncPolicy: 31 | automated: 32 | prune: true 33 | selfHeal: true 34 | syncOptions: 35 | - CreateNamespace=true 36 | - ServerSideApply=true 37 | -------------------------------------------------------------------------------- /cluster/bootstrap/infrastructure/ingress-nginx.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: ingress-nginx 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | annotations: 12 | argocd.argoproj.io/sync-wave: '1' 13 | spec: 14 | project: default 15 | sources: 16 | - chart: ingress-nginx 17 | repoURL: https://kubernetes.github.io/ingress-nginx 18 | targetRevision: 4.12.2 19 | helm: 20 | valueFiles: 21 | - $repo/cluster/apps/infrastructure/ingress-nginx/values.yaml 22 | releaseName: ingress-nginx 23 | - repoURL: https://github.com/clearlybaffled/homelab 24 | path: cluster/apps/infrastructure/ingress-nginx 25 | targetRevision: main 26 | ref: repo 27 | destination: 28 | name: in-cluster 29 | namespace: infrastructure 30 | syncPolicy: 31 | automated: 32 | prune: true 33 | selfHeal: true 34 | syncOptions: 35 | - CreateNamespace=true 36 | - ServerSideApply=true 37 | -------------------------------------------------------------------------------- /cluster/apps/home/grocy/README.md: -------------------------------------------------------------------------------- 1 | # ![](https://raw.githubusercontent.com/grocy/grocy/master/public/img/logo.svg) 2 | 3 | The first app I deployed to the cluster. I decided to try to build this one completely from scratch (i.e. without using [Bern's chart template](https://bjw-s.github.io/helm-charts/docs/app-template/introduction.html)) 4 | and used John Hamelinks's k8s-config's for [grocy](https://git.sr.ht/~johnhamelink/k8s-grocy/) and [barcode-buddy](https://git.sr.ht/~johnhamelink/k8s-barcodebuddy). 5 | Currently, I've just configured each manifest file to have 2 YAML documents, one for each grocy and barcode-buddy. 6 | My thoughts are, if I'm feeling enterprising enough, would be to reconfig it to have barcode-buddy run as a sidecar to grocy isntead of a separate pod, and maybe even share underlying volumes. 7 | Grocy was also built and deployed before the [rook-ceph](../../infrastructure/rook-ceph/) storage solution was in place, so it uses local volumes (see [#22](https://github.com/clearlybaffled/homelab/issues/22) and [#23](https://github.com/clearlybaffled/homelab/issues/23)). 8 | -------------------------------------------------------------------------------- /infrastructure/roles/dvb/tasks/build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create a work directory 3 | ansible.builtin.tempfile: 4 | state: directory 5 | register: workdir 6 | 7 | - name: Git clone mythtv repo 8 | git: 9 | repo: 'https://github.com/mythtv/mythtv.git' 10 | dest: "{{ workdir.path }}" 11 | version: 'fixes/33' 12 | single_branch: true 13 | register: git_clone 14 | 15 | - name: Configure 16 | command: 17 | argv: 18 | - ./configure 19 | - --disable-firewire 20 | - --disable-hdhomerun 21 | - --disable-vbox 22 | - --disable-ceton 23 | - --disable-satip 24 | - --enable-libxvid 25 | - --enable-libx264 26 | - --enable-libx265 27 | - --enable-libmp3lame 28 | chdir: "{{ git_clone.git_dir_now }}" 29 | 30 | - name: Make 31 | community.general.make: 32 | chdir: "{{ git_clone.git_dir_now }}" 33 | jobs: 5 34 | register: make 35 | 36 | - name: Make Install 37 | community.general.make: 38 | chdir: "{{ git_clone.git_dir_now }}" 39 | target: Install 40 | when: make is not failed 41 | -------------------------------------------------------------------------------- /infrastructure/roles/user/README.md: -------------------------------------------------------------------------------- 1 | 2 | # USER (infrastructure/roles/user) 3 | 4 | ## ENTRY POINT: kube-user 5 | 6 | ### OPTIONS (= is mandatory) 7 | 8 | ``` 9 | = groupname 10 | Name of the local system group 11 | type: str 12 | 13 | = kube-user 14 | Name of the kubernetes user to create 15 | type: str 16 | 17 | = username 18 | Name of the local system user to apply the config to 19 | type: str 20 | ``` 21 | 22 | ## ENTRY POINT: system-user 23 | 24 | ### OPTIONS (= is mandatory) 25 | 26 | ``` 27 | = ansible_group 28 | ansible usergroup 29 | type: str 30 | 31 | = ansible_homedir 32 | ansible home directory 33 | type: str 34 | 35 | = ansible_user 36 | ansible username 37 | type: str 38 | 39 | - ansible_user_ssh_key 40 | ssh private key for ansible user 41 | default: null 42 | type: str 43 | 44 | - ansible_user_ssh_pubkey 45 | ssh public key for ansible user 46 | default: null 47 | type: str 48 | ``` 49 | -------------------------------------------------------------------------------- /cluster/bootstrap/home/mosquitto.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: mosquitto 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/home/mosquitto/values.yaml 20 | releaseName: mosquitto 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/home/mosquitto 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: home 28 | ignoreDifferences: 29 | - group: apps 30 | kind: StatefulSet 31 | jsonPointers: 32 | - /spec/volumeClaimTemplates 33 | syncPolicy: 34 | automated: 35 | prune: true 36 | selfHeal: true 37 | syncOptions: 38 | - CreateNamespace=true 39 | - ServerSideApply=true 40 | - RespectIgnoreDifferences=true 41 | -------------------------------------------------------------------------------- /cluster/apps/db/mysql/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: mysql 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: mysql 17 | namespace: db 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153.0Gi 23 | local: 24 | path: /srv/cluster/volumes/db/mysql/mysql 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: mysql 38 | namespace: db 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153.0Gi 47 | storageClassName: local-storage 48 | volumeName: mysql 49 | -------------------------------------------------------------------------------- /cluster/bootstrap/media/immich.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: immich 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: immich 15 | repoURL: https://immich-app.github.io/immich-charts 16 | targetRevision: 0.7.0 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/media/immich/values.yaml 20 | releaseName: immich 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/media/immich 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: media 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/tasks/pre-start/homepage.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Build Secret with credentials needed for widgets 3 | vars: 4 | secrets: {} 5 | block: 6 | - name: Get all secrets 7 | set_fact: 8 | secrets: >- 9 | {{ 10 | secrets | combine({ 11 | "HOMEPAGE_VAR_" + item|upper + "_API_KEY": 12 | lookup('community.sops.sops', (apps[item].app_dir, 'secret.sops.yaml')|path_join) | from_yaml | json_query('data.'+item|upper+'__API_KEY') 13 | }) 14 | }} 15 | with_items: 16 | - radarr 17 | - sonarr 18 | - lidarr 19 | - readarr 20 | - prowlarr 21 | 22 | - name: Write out SOPS file 23 | community.sops.sops_encrypt: 24 | path: "{{ (app.app_dir, 'app-keys.sops.yaml') | path_join }}" 25 | encrypted_regex: "^(data|stringData)$" 26 | content_yaml: 27 | apiVersion: v1 28 | kind: Secret 29 | type: Opaque 30 | metadata: 31 | name: app-keys 32 | namespace: "{{ app.namespace }}" 33 | data: 34 | "{{ secrets }}" 35 | -------------------------------------------------------------------------------- /cluster/apps/home/grocy/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: grocy 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: grocy 17 | namespace: home 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153.0Gi 23 | local: 24 | path: /srv/cluster/volumes/home/grocy/grocy 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: grocy 38 | namespace: home 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153.0Gi 47 | storageClassName: local-storage 48 | volumeName: grocy 49 | -------------------------------------------------------------------------------- /cluster/bootstrap/home/homebox.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: homebox 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/home/homebox/values.yaml 20 | releaseName: homebox 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/home/homebox 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: home 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /cluster/bootstrap/home/nvr.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: nvr 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: frigate 15 | repoURL: https://blakeblackshear.github.io/blakeshome-charts/ 16 | targetRevision: 7.5.1 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/home/nvr/frigate.yaml 20 | releaseName: frigate 21 | - chart: app-template 22 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 23 | targetRevision: 3.7.3 24 | helm: 25 | valueFiles: 26 | - $repo/cluster/apps/home/nvr/wyze-bridge.yaml 27 | releaseName: wyze-bridge 28 | - repoURL: https://github.com/clearlybaffled/homelab 29 | path: cluster/apps/home/nvr 30 | targetRevision: main 31 | ref: repo 32 | destination: 33 | name: in-cluster 34 | namespace: home 35 | syncPolicy: 36 | automated: 37 | prune: true 38 | selfHeal: true 39 | syncOptions: 40 | - CreateNamespace=true 41 | - ServerSideApply=true 42 | -------------------------------------------------------------------------------- /cluster/bootstrap/media/calibre.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: calibre 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/media/calibre/values.yaml 20 | releaseName: calibre 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/media/calibre 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: media 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /infrastructure/roles/apps/meta/argument_specs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | argument_specs: 3 | main: 4 | options: 5 | app_deploy_list: 6 | type: list 7 | description: A list of applications to deploy instead of the entire list 8 | required: false 9 | default: [] 10 | app_skip_list: 11 | type: list 12 | description: A list of applications to not deploy out of the entire list 13 | required: false 14 | default: [] 15 | app_start_wave: 16 | type: int 17 | required: false 18 | description: Wave number to start deployment from 19 | app_start_at: 20 | type: str 21 | description: Name of the app to start deploying from 22 | required: false 23 | app_no_loop_pause: 24 | type: bool 25 | required: false 26 | default: false 27 | description: Don't wait for user input before proceeding to next app in deployment 28 | batch_apps: 29 | type: bool 30 | required: false 31 | default: false 32 | description: Wait until deployment is complete to create a git commit instead of once per app. 33 | -------------------------------------------------------------------------------- /cluster/apps/home/homebox/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: homebox 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: homebox 17 | namespace: home 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153Gi 23 | local: 24 | path: /srv/cluster/volumes/home/homebox/homebox 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: homebox 38 | namespace: home 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153Gi 47 | storageClassName: local-storage 48 | volumeName: homebox 49 | -------------------------------------------------------------------------------- /cluster/apps/services/linkding/linkding.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /cluster/bootstrap/infrastructure/netbox.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: netbox 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: netbox 15 | repoURL: https://charts.boo.tc 16 | targetRevision: 4.1.1 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/infrastructure/netbox/values.yaml 20 | releaseName: netbox 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/infrastructure/netbox 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: infrastructure 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /cluster/apps/media/photoprism/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: photoprism 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: photoprism 17 | namespace: media 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 9100.0Gi 23 | local: 24 | path: /disks/sdg/photoprism 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: photoprism 38 | namespace: media 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 9100.0Gi 47 | storageClassName: local-storage 48 | volumeName: photoprism 49 | -------------------------------------------------------------------------------- /cluster/bootstrap/downloads/bazarr.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: bazarr 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/downloads/bazarr/values.yaml 20 | releaseName: bazarr 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/downloads/bazarr 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: downloads 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /cluster/bootstrap/downloads/lidarr.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: lidarr 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/downloads/lidarr/values.yaml 20 | releaseName: lidarr 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/downloads/lidarr 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: downloads 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /cluster/bootstrap/downloads/radarr.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: radarr 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/downloads/radarr/values.yaml 20 | releaseName: radarr 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/downloads/radarr 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: downloads 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /cluster/bootstrap/downloads/sonarr.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: sonarr 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/downloads/sonarr/values.yaml 20 | releaseName: sonarr 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/downloads/sonarr 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: downloads 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /infrastructure/inventory/group_vars/all/00-all.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ## Directory where the binaries will be installed 3 | bin_dir: /usr/local/bin 4 | 5 | ## Directory convenience variables 6 | root_dir: "{{ ansible_config_file | dirname }}" 7 | cluster_dir: "{{ root_dir }}/cluster" 8 | infra_dir: "{{ root_dir }}/infrastructure" 9 | 10 | ## Ansible User setup 11 | ansible_user: ansible 12 | ansible_group: ansible 13 | ansible_groups: 14 | - sys 15 | - adm 16 | ansible_homedir: /var/local/ansible 17 | 18 | # (string) Timezone for the servers 19 | timezone: "America/New_York" 20 | 21 | _host_architecture_groups: 22 | x86_64: amd64 23 | aarch64: arm64 24 | armv7l: arm 25 | host_architecture: >- 26 | {%- if ansible_architecture in _host_architecture_groups -%} 27 | {{ _host_architecture_groups[ansible_architecture] }} 28 | {%- else -%} 29 | {{ ansible_architecture }} 30 | {%- endif -%} 31 | 32 | _host_os_groups: 33 | Linux: linux 34 | Darwin: darwin 35 | Win32NT: windows 36 | host_os: >- 37 | {%- if ansible_system in _host_os_groups -%} 38 | {{ _host_os_groups[ansible_system] }} 39 | {%- else -%} 40 | {{ ansible_system|lower }} 41 | {%- endif -%} 42 | -------------------------------------------------------------------------------- /cluster/bootstrap/downloads/readarr.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: readarr 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/downloads/readarr/values.yaml 20 | releaseName: readarr 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/downloads/readarr 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: downloads 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /cluster/bootstrap/home/zwave-js-ui.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: zwave-js-ui 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/home/zwave-js-ui/values.yaml 20 | releaseName: zwave-js-ui 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/home/zwave-js-ui 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: home 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /cluster/bootstrap/media/photoprism.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: photoprism 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/media/photoprism/values.yaml 20 | releaseName: photoprism 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/media/photoprism 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: media 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /cluster/bootstrap/downloads/prowlarr.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: prowlarr 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/downloads/prowlarr/values.yaml 20 | releaseName: prowlarr 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/downloads/prowlarr 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: downloads 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /.github/workflows/linters.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Linters 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | ansible-lint: 11 | name: Ansible Lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | - name: Run ansible-lint 19 | uses: ansible/ansible-lint@main 20 | kubeconform: 21 | name: Kubernetes Lint 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: login to Github Packages 25 | run: echo "${{ github.token }}" | docker login https://ghcr.io -u ${GITHUB_ACTOR} --password-stdin 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | with: 29 | fetch-depth: 0 30 | - name: Kubeconform 31 | uses: docker://ghcr.io/yannh/kubeconform:v0.6.3 32 | with: 33 | entrypoint: '/kubeconform' 34 | args: "-summary -output text -schema-location default -schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' -ignore-missing-schemas $(grep -lr '^kind: ' cluster)" 35 | -------------------------------------------------------------------------------- /cluster/bootstrap/services/linkwarden.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: linkwarden 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/services/linkwarden/values.yaml 20 | releaseName: linkwarden 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/services/linkwarden 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: services 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /cluster/apps/services/stirling-pdf/values.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/bjw-s/helm-charts/main/charts/library/common/values.schema.json 2 | controllers: 3 | main: 4 | containers: 5 | stirling-pdf: 6 | image: 7 | repository: docker.io/frooodle/s-pdf 8 | tag: 0.27.0 9 | resources: 10 | requests: 11 | cpu: 10m 12 | memory: 256Mi 13 | limits: 14 | memory: 256Mi 15 | service: 16 | main: 17 | controller: main 18 | ports: 19 | http: 20 | port: 8080 21 | ingress: 22 | main: 23 | enabled: true 24 | className: nginx 25 | annotations: 26 | gethomepage.dev/enabled: "true" 27 | gethomepage.dev/group: "Apps & Services" 28 | gethomepage.dev/name: Stirling PDF 29 | gethomepage.dev/icon: stirling-pdf.png 30 | hosts: 31 | - host: &host pdf.hermleigh.cc 32 | paths: 33 | - path: / 34 | service: 35 | identifier: main 36 | port: http 37 | tls: 38 | - hosts: 39 | - *host 40 | secretName: external-wildcard-certificate 41 | -------------------------------------------------------------------------------- /cluster/bootstrap/db/mysql.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: mysql 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | annotations: 12 | argocd.argoproj.io/sync-wave: '3' 13 | spec: 14 | project: default 15 | sources: 16 | - chart: mysql 17 | repoURL: https://charts.bitnami.com/bitnami 18 | targetRevision: 9.14.3 19 | helm: 20 | valueFiles: 21 | - $repo/cluster/apps/db/mysql/values.yaml 22 | releaseName: mysql 23 | - repoURL: https://github.com/clearlybaffled/homelab 24 | path: cluster/apps/db/mysql 25 | targetRevision: main 26 | ref: repo 27 | destination: 28 | name: in-cluster 29 | namespace: db 30 | ignoreDifferences: 31 | - kind: PersistentVolume 32 | jsonPointers: 33 | - /spec/claimRef/resourceVersion 34 | - /spec/claimRef/uid 35 | - /status/lastPhaseTransitionTime 36 | syncPolicy: 37 | automated: 38 | prune: true 39 | selfHeal: true 40 | syncOptions: 41 | - CreateNamespace=true 42 | - ServerSideApply=true 43 | - RespectIgnoreDifferences=true 44 | -------------------------------------------------------------------------------- /cluster/bootstrap/home/home-assistant.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: home-assistant 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/home/home-assistant/values.yaml 20 | releaseName: home-assistant 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/home/home-assistant 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: home 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /infrastructure/terraform/ipa/ipa_cloud_init.tftpl: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | timezone: America/New_York 3 | 4 | hostname: tang 5 | fqdn: tang.hermleigh.home 6 | prefer_fqdn_over_hostname: true 7 | 8 | ssh_pwauth: false 9 | 10 | runcmd: 11 | - setenforce 0 12 | 13 | 14 | users: 15 | - name: ansible 16 | gecos: Ansible User 17 | groups: users,admin,wheel 18 | sudo: ALL=(ALL) NOPASSWD:ALL 19 | shell: /bin/bash 20 | lock_passwd: false 21 | plain_text_passwd: ansible 22 | homedir: /var/lib/ansible 23 | ssh_authorized_keys: 24 | - "${base64decode(ssh_pubkey)}" 25 | 26 | write_files: 27 | - path: /var/local/ansible/.ssh/id_ed25519 28 | owner: ansible:ansible 29 | permissions: '0o600' 30 | encoding: base64 31 | append: false 32 | defer: true 33 | content: ${ssh_key} 34 | 35 | package_update: true 36 | package_upgrade: true 37 | package_reboot_if_required: true 38 | 39 | packages: 40 | - qemu-guest-agent 41 | 42 | ntp: 43 | enabled: true 44 | config: 45 | service_name: chronyd 46 | servers: 47 | - 172.16.1.1 48 | 49 | 50 | ${yamlencode({ 51 | "ca_certs": { 52 | "trusted": [ 53 | "${root_ca_crt}" 54 | ] 55 | } 56 | })} 57 | -------------------------------------------------------------------------------- /cluster/apps/home/paperless-ngx/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: paperless-library 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: paperless-library 17 | namespace: home 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 5.5Ti 23 | local: 24 | path: /share/Files/PaperlessNGX 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: paperless-library 38 | namespace: home 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 5.5Ti 47 | storageClassName: local-storage 48 | volumeName: paperless-library 49 | -------------------------------------------------------------------------------- /cluster/bootstrap/downloads/qbittorrent.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: qbittorrent 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/downloads/qbittorrent/values.yaml 20 | releaseName: qbittorrent 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/downloads/qbittorrent 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: downloads 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /cluster/bootstrap/media/audiobookshelf.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: audiobookshelf 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: app-template 15 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 16 | targetRevision: 3.7.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/media/audiobookshelf/values.yaml 20 | releaseName: audiobookshelf 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/media/audiobookshelf 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: media 28 | ignoreDifferences: 29 | - kind: PersistentVolume 30 | jsonPointers: 31 | - /spec/claimRef/resourceVersion 32 | - /spec/claimRef/uid 33 | - /status/lastPhaseTransitionTime 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /infrastructure/roles/user/meta/argument_specs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | argument_specs: 3 | kube-user: 4 | options: 5 | kube-user: 6 | type: str 7 | required: true 8 | description: Name of the kubernetes user to create 9 | username: 10 | type: str 11 | required: true 12 | description: Name of the local system user to apply the config to 13 | groupname: 14 | type: str 15 | required: true 16 | description: Name of the local system group 17 | system-user: 18 | options: 19 | ansible_user: 20 | type: str 21 | required: true 22 | description: ansible username 23 | ansible_group: 24 | type: str 25 | required: true 26 | description: ansible usergroup 27 | ansible_homedir: 28 | type: str 29 | required: true 30 | description: ansible home directory 31 | ansible_user_ssh_key: 32 | type: str 33 | required: false 34 | description: ssh private key for ansible user 35 | ansible_user_ssh_pubkey: 36 | type: str 37 | required: false 38 | description: ssh public key for ansible user 39 | -------------------------------------------------------------------------------- /cluster/apps/media/immich/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: immich-library 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: immich-library 17 | namespace: media 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 916G 23 | local: 24 | path: /containers/volumes/media/immich/immich-library 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: immich-library 38 | namespace: media 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 916G 47 | storageClassName: local-storage 48 | volumeName: immich-library 49 | -------------------------------------------------------------------------------- /cluster/bootstrap/kube-system/intel-device-plugin.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: intel-device-plugin 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: intel-device-plugins-operator 15 | repoURL: https://intel.github.io/helm-charts/ 16 | targetRevision: 0.29.0 17 | helm: 18 | releaseName: intel-device-plugins-operator 19 | - chart: app-template 20 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 21 | targetRevision: 3.7.3 22 | helm: 23 | valueFiles: 24 | - $repo/cluster/kube-system/intel-device-plugin/exporter.yaml 25 | releaseName: intel-gpu-exporter 26 | - repoURL: https://github.com/clearlybaffled/homelab 27 | path: cluster/kube-system/intel-device-plugin 28 | targetRevision: main 29 | ref: repo 30 | destination: 31 | name: in-cluster 32 | namespace: kube-system 33 | syncPolicy: 34 | automated: 35 | prune: true 36 | selfHeal: true 37 | syncOptions: 38 | - CreateNamespace=true 39 | - ServerSideApply=true 40 | -------------------------------------------------------------------------------- /cluster/apps/services/linkwarden/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: linkwarden 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: linkwarden 17 | namespace: services 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153.0Gi 23 | local: 24 | path: /srv/cluster/volumes/services/linkwarden/linkwarden 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: linkwarden 38 | namespace: services 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153.0Gi 47 | storageClassName: local-storage 48 | volumeName: linkwarden 49 | -------------------------------------------------------------------------------- /cluster/apps/home/mealie/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: mealie-api-data 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: mealie-api-data 17 | namespace: home 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153.0Gi 23 | local: 24 | path: /srv/cluster/volumes/home/mealie/mealie-api-data 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: mealie-api-data 38 | namespace: home 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153.0Gi 47 | storageClassName: local-storage 48 | volumeName: mealie-api-data 49 | -------------------------------------------------------------------------------- /cluster/apps/media/calibre/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: calibre-config 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: calibre-config 17 | namespace: media 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153.0Gi 23 | local: 24 | path: /srv/cluster/volumes/media/calibre/calibre-config 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: calibre-config 38 | namespace: media 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153.0Gi 47 | storageClassName: local-storage 48 | volumeName: calibre-config 49 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/registry/templates/registry-svc.yml.j2: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: registry 6 | namespace: {{ registry_namespace }} 7 | labels: 8 | k8s-app: registry 9 | addonmanager.kubernetes.io/mode: Reconcile 10 | kubernetes.io/name: "KubeRegistry" 11 | {% if registry_service_annotations %} 12 | annotations: 13 | {{ registry_service_annotations | to_nice_yaml(indent=2, width=1337) | indent(width=4) }} 14 | {% endif %} 15 | spec: 16 | selector: 17 | k8s-app: registry 18 | type: {{ registry_service_type }} 19 | {% if registry_service_type == "ClusterIP" and registry_service_cluster_ip != "" %} 20 | clusterIP: {{ registry_service_cluster_ip }} 21 | {% endif %} 22 | {% if registry_service_type == "LoadBalancer" and registry_service_loadbalancer_ip != "" %} 23 | loadBalancerIP: {{ registry_service_loadbalancer_ip }} 24 | {% endif %} 25 | ports: 26 | - name: registry 27 | port: {{ registry_port }} 28 | protocol: TCP 29 | targetPort: {{ registry_port }} 30 | {% if registry_service_type == "NodePort" and registry_service_nodeport != "" %} 31 | nodePort: {{ registry_service_nodeport }} 32 | {% endif %} 33 | -------------------------------------------------------------------------------- /playbooks/reset.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Reset K8S cluster 3 | hosts: kube_control_plane 4 | any_errors_fatal: true 5 | gather_facts: true 6 | become: true 7 | tags: 8 | - cluster 9 | tasks: 10 | - name: Run resets from relevant roles 11 | include_role: 12 | name: "{{ item }}" 13 | tasks_from: reset 14 | with_items: 15 | # - apps 16 | - kubernetes/control_plane 17 | - containers/runtime 18 | 19 | - name: Reset | include file with reset tasks specific to the network_plugin if exists 20 | include_role: 21 | name: "containers/network/{{ kube_network_plugin }}" 22 | tasks_from: reset 23 | when: 24 | - kube_network_plugin in ['flannel', 'cilium', 'kube-router', 'calico'] 25 | tags: 26 | - network 27 | 28 | - name: Tear down terraform stacks 29 | hosts: localhost 30 | tags: 31 | - terraform 32 | tasks: 33 | - name: Tear down terraform stack 34 | community.general.terraform: 35 | project_path: "{{ (infra_dir, 'terraform', item) | path_join }}" 36 | state: absent 37 | when: item is directory 38 | with_fileglob: 39 | - "{{ infra_dir }}/terraform/*" 40 | -------------------------------------------------------------------------------- /cluster/apps/home/zwave-js-ui/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: zwave-app-store 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: zwave-app-store 17 | namespace: home 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153Gi 23 | local: 24 | path: /srv/cluster/volumes/home/zwave-js-ui/zwave-app-store 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: zwave-app-store 38 | namespace: home 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153Gi 47 | storageClassName: local-storage 48 | volumeName: zwave-app-store 49 | -------------------------------------------------------------------------------- /cluster/bootstrap/home/mealie.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: mealie 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | annotations: 12 | argocd.argoproj.io/sync-wave: '4' 13 | spec: 14 | project: default 15 | sources: 16 | - chart: app-template 17 | repoURL: https://bjw-s-labs.github.io/helm-charts/ 18 | targetRevision: 3.7.3 19 | helm: 20 | valueFiles: 21 | - $repo/cluster/apps/home/mealie/values.yaml 22 | releaseName: mealie 23 | - repoURL: https://github.com/clearlybaffled/homelab 24 | path: cluster/apps/home/mealie 25 | targetRevision: main 26 | ref: repo 27 | destination: 28 | name: in-cluster 29 | namespace: home 30 | ignoreDifferences: 31 | - kind: PersistentVolume 32 | jsonPointers: 33 | - /spec/claimRef/resourceVersion 34 | - /spec/claimRef/uid 35 | - /status/lastPhaseTransitionTime 36 | syncPolicy: 37 | automated: 38 | prune: true 39 | selfHeal: true 40 | syncOptions: 41 | - CreateNamespace=true 42 | - ServerSideApply=true 43 | - RespectIgnoreDifferences=true 44 | -------------------------------------------------------------------------------- /cluster/apps/downloads/bazarr/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: bazarr-config 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: bazarr-config 17 | namespace: downloads 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153.0Gi 23 | local: 24 | path: /srv/cluster/volumes/downloads/bazarr/bazarr-config 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: bazarr-config 38 | namespace: downloads 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153.0Gi 47 | storageClassName: local-storage 48 | volumeName: bazarr-config 49 | -------------------------------------------------------------------------------- /cluster/apps/downloads/lidarr/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: lidarr-config 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: lidarr-config 17 | namespace: downloads 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153.0Gi 23 | local: 24 | path: /srv/cluster/volumes/downloads/lidarr/lidarr-config 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: lidarr-config 38 | namespace: downloads 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153.0Gi 47 | storageClassName: local-storage 48 | volumeName: lidarr-config 49 | -------------------------------------------------------------------------------- /cluster/apps/downloads/radarr/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: radarr-config 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: radarr-config 17 | namespace: downloads 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153.0Gi 23 | local: 24 | path: /srv/cluster/volumes/downloads/radarr/radarr-config 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: radarr-config 38 | namespace: downloads 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153.0Gi 47 | storageClassName: local-storage 48 | volumeName: radarr-config 49 | -------------------------------------------------------------------------------- /cluster/apps/downloads/sonarr/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: sonarr-config 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: sonarr-config 17 | namespace: downloads 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153.0Gi 23 | local: 24 | path: /srv/cluster/volumes/downloads/sonarr/sonarr-config 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: sonarr-config 38 | namespace: downloads 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153.0Gi 47 | storageClassName: local-storage 48 | volumeName: sonarr-config 49 | -------------------------------------------------------------------------------- /cluster/apps/home/home-assistant/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: home-assistant 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: home-assistant 17 | namespace: home 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153.0Gi 23 | local: 24 | path: /srv/cluster/volumes/home/home-assistant/home-assistant 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: home-assistant 38 | namespace: home 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153.0Gi 47 | storageClassName: local-storage 48 | volumeName: home-assistant 49 | -------------------------------------------------------------------------------- /cluster/apps/media/jellyfin/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: jellyseer-config 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: jellyseer-config 17 | namespace: media 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153.0Gi 23 | local: 24 | path: /srv/cluster/volumes/media/jellyfin/jellyseer-config 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: jellyseer-config 38 | namespace: media 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153.0Gi 47 | storageClassName: local-storage 48 | volumeName: jellyseer-config 49 | -------------------------------------------------------------------------------- /cluster/apps/monitoring/kube-prometheus-stack/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: grafana 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: grafana 17 | namespace: monitoring 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153.0Gi 23 | local: 24 | path: /srv/cluster/volumes/monitoring/kube-prometheus-stack/grafana 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: grafana 38 | namespace: monitoring 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153.0Gi 47 | storageClassName: local-storage 48 | volumeName: grafana 49 | -------------------------------------------------------------------------------- /cluster/bootstrap/infrastructure/reloader.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: argoproj.io/v1alpha1 5 | kind: Application 6 | metadata: 7 | name: reloader 8 | namespace: argocd 9 | finalizers: 10 | - resources-finalizer.argocd.argoproj.io 11 | spec: 12 | project: default 13 | sources: 14 | - chart: reloader 15 | repoURL: https://stakater.github.io/stakater-charts 16 | targetRevision: 2.1.3 17 | helm: 18 | valueFiles: 19 | - $repo/cluster/apps/infrastructure/reloader/values.yaml 20 | releaseName: reloader 21 | - repoURL: https://github.com/clearlybaffled/homelab 22 | path: cluster/apps/infrastructure/reloader 23 | targetRevision: main 24 | ref: repo 25 | destination: 26 | name: in-cluster 27 | namespace: infrastructure 28 | ignoreDifferences: 29 | - group: apps 30 | kind: Deployment 31 | name: reloader-reloader 32 | jqPathExpressions: 33 | - .spec.template.spec.containers[].env[].valueFrom.resourceFieldRef.divisor 34 | syncPolicy: 35 | automated: 36 | prune: true 37 | selfHeal: true 38 | syncOptions: 39 | - CreateNamespace=true 40 | - ServerSideApply=true 41 | - RespectIgnoreDifferences=true 42 | -------------------------------------------------------------------------------- /cluster/apps/downloads/readarr/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: readarr-config 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: readarr-config 17 | namespace: downloads 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153.0Gi 23 | local: 24 | path: /srv/cluster/volumes/downloads/readarr/readarr-config 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: readarr-config 38 | namespace: downloads 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153.0Gi 47 | storageClassName: local-storage 48 | volumeName: readarr-config 49 | -------------------------------------------------------------------------------- /infrastructure/roles/containers/network/flannel/tasks/reset.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Clean CNI config directory 3 | file: 4 | path: /etc/cni/net.d 5 | state: absent 6 | - name: Clear iptables 7 | block: 8 | - name: iptables | flush 9 | ansible.builtin.iptables: 10 | flush: true 11 | - name: iptables | flush tables 12 | iptables: 13 | flush: true 14 | table: "{{ item }}" 15 | with_items: 16 | - nat 17 | - mangle 18 | - name: iptables | delete chains 19 | command: 'iptables -X' 20 | 21 | - name: Remove cni0 interface 22 | shell: | 23 | ip link set cni0 down 24 | ip link delete cni0 type bridge 25 | register: rm_if 26 | failed_when: rm_if.rc != 0 and not "Cannot find device" in rm_if.stderr 27 | notify: 28 | - 'Kubelet | stop kubelet' 29 | - 'CRI-O | stop crio' 30 | 31 | - name: iptables flush all 32 | iptables: 33 | flush: true 34 | - name: iptables flush nat 35 | iptables: 36 | flush: true 37 | table: nat 38 | notify: 39 | - 'Kubelet | start kubelet' 40 | - 'CRI-O | start crio' 41 | 42 | - name: Remove flannel/subnet.env 43 | file: 44 | path: /run/flannel/subnet.env 45 | state: absent 46 | -------------------------------------------------------------------------------- /infrastructure/roles/kubernetes/control_plane/tasks/approve-cert.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: kubernetes | Ensure CSR is provided 3 | assert: 4 | that: 5 | - csr is defined and csr 6 | - name is defined and name != "" 7 | 8 | - name: kubernetes | Submit CSR 9 | delegate_to: "{{ groups['kube_control_plane'][0] }}" 10 | connection: ssh 11 | become: true 12 | kubernetes.core.k8s: 13 | kubeconfig: "{{ kubeconfig_file }}" 14 | template: csr.yml.j2 15 | ca_cert: "{{ kube_cluster_cacerts }}" 16 | 17 | - name: kubernetes | Approve CSR 18 | delegate_to: "{{ groups['kube_control_plane'][0] }}" 19 | connection: ssh 20 | become: true 21 | command: "{{ kubectl }} certificate approve {{ name }}" 22 | register: approve_csr 23 | failed_when: approve_csr.rc != 0 24 | 25 | - name: kubernetes | Get Signed certificate 26 | delegate_to: "{{ groups['kube_control_plane'][0] }}" 27 | connection: ssh 28 | become: true 29 | command: "{{ kubectl }} get csr {{ name }} -o jsonpath='{.status.certificate}'" 30 | register: crt 31 | until: crt.stdout is ne "" 32 | retries: 10 33 | delay: 5 34 | 35 | - name: kubernetes | Return signed certificate 36 | set_fact: 37 | certificate: "{{ crt.stdout }}" 38 | -------------------------------------------------------------------------------- /cluster/apps/downloads/prowlarr/storage.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Ansible managed 3 | # 4 | apiVersion: v1 5 | kind: PersistentVolume 6 | metadata: 7 | name: prowlarr-config 8 | finalizers: 9 | - kubernetes.io/pv-protection 10 | spec: 11 | accessModes: 12 | - ReadWriteOnce 13 | claimRef: 14 | apiVersion: v1 15 | kind: PersistentVolumeClaim 16 | name: prowlarr-config 17 | namespace: downloads 18 | persistentVolumeReclaimPolicy: Retain 19 | volumeMode: Filesystem 20 | storageClassName: local-storage 21 | capacity: 22 | storage: 153.0Gi 23 | local: 24 | path: /srv/cluster/volumes/downloads/prowlarr/prowlarr-config 25 | nodeAffinity: 26 | required: 27 | nodeSelectorTerms: 28 | - matchExpressions: 29 | - key: kubernetes.io/hostname 30 | operator: In 31 | values: 32 | - parche 33 | --- 34 | apiVersion: v1 35 | kind: PersistentVolumeClaim 36 | metadata: 37 | name: prowlarr-config 38 | namespace: downloads 39 | finalizers: 40 | - kubernetes.io/pvc-protection 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 153.0Gi 47 | storageClassName: local-storage 48 | volumeName: prowlarr-config 49 | --------------------------------------------------------------------------------