From 3aa7da5f312500487f9c0ca643b60d8afd330fe9 Mon Sep 17 00:00:00 2001 From: Snarf Date: Mon, 2 Nov 2026 17:01:29 -0500 Subject: [PATCH] feat: initial homelab-gitops declarative structure (Terraform & ArgoCD) --- README.md | 41 ++++- argocd/aerodrome/authentik-app.yaml | 27 +++ argocd/aerodrome/awx-app.yaml | 27 +++ argocd/aerodrome/grafana-app.yaml | 27 +++ .../longhorn-recurring-jobs-app.yaml | 27 +++ argocd/aerodrome/uptime-kuma-app.yaml | 27 +++ argocd/bootstrap/argocd-project.yaml | 23 +++ argocd/bootstrap/root-app-of-apps.yaml | 30 +++ argocd/spaceport/coder-app.yaml | 27 +++ argocd/spaceport/gitea-app.yaml | 27 +++ argocd/spaceport/homepage-app.yaml | 27 +++ argocd/spaceport/jenkins-app.yaml | 27 +++ argocd/spaceport/semaphore-app.yaml | 27 +++ argocd/spaceport/zabbix-app.yaml | 27 +++ .../recurring-jobs.yaml | 41 +++++ terraform/authentik/applications.tf | 131 +++++++++++++ terraform/authentik/oauth2_providers.tf | 140 ++++++++++++++ terraform/authentik/outputs.tf | 17 ++ terraform/authentik/policies.tf | 7 + terraform/authentik/providers.tf | 21 +++ terraform/authentik/proxy_providers.tf | 35 ++++ terraform/authentik/variables.tf | 30 +++ terraform/harvester/networks.tf | 37 ++++ terraform/harvester/outputs.tf | 8 + terraform/harvester/providers.tf | 20 ++ terraform/harvester/variables.tf | 11 ++ terraform/harvester/vms.tf | 11 ++ terraform/shipyard-swarm/outputs.tf | 9 + terraform/shipyard-swarm/providers.tf | 20 ++ terraform/shipyard-swarm/stacks.tf | 173 ++++++++++++++++++ terraform/shipyard-swarm/variables.tf | 11 ++ terraform/synology/outputs.tf | 9 + terraform/synology/providers.tf | 25 +++ terraform/synology/shares.tf | 31 ++++ terraform/synology/variables.tf | 24 +++ 35 files changed, 1200 insertions(+), 2 deletions(-) create mode 100644 argocd/aerodrome/authentik-app.yaml create mode 100644 argocd/aerodrome/awx-app.yaml create mode 100644 argocd/aerodrome/grafana-app.yaml create mode 100644 argocd/aerodrome/longhorn-recurring-jobs-app.yaml create mode 100644 argocd/aerodrome/uptime-kuma-app.yaml create mode 100644 argocd/bootstrap/argocd-project.yaml create mode 100644 argocd/bootstrap/root-app-of-apps.yaml create mode 100644 argocd/spaceport/coder-app.yaml create mode 100644 argocd/spaceport/gitea-app.yaml create mode 100644 argocd/spaceport/homepage-app.yaml create mode 100644 argocd/spaceport/jenkins-app.yaml create mode 100644 argocd/spaceport/semaphore-app.yaml create mode 100644 argocd/spaceport/zabbix-app.yaml create mode 100644 manifests/aerodrome/longhorn-recurring-jobs/recurring-jobs.yaml create mode 100644 terraform/authentik/applications.tf create mode 100644 terraform/authentik/oauth2_providers.tf create mode 100644 terraform/authentik/outputs.tf create mode 100644 terraform/authentik/policies.tf create mode 100644 terraform/authentik/providers.tf create mode 100644 terraform/authentik/proxy_providers.tf create mode 100644 terraform/authentik/variables.tf create mode 100644 terraform/harvester/networks.tf create mode 100644 terraform/harvester/outputs.tf create mode 100644 terraform/harvester/providers.tf create mode 100644 terraform/harvester/variables.tf create mode 100644 terraform/harvester/vms.tf create mode 100644 terraform/shipyard-swarm/outputs.tf create mode 100644 terraform/shipyard-swarm/providers.tf create mode 100644 terraform/shipyard-swarm/stacks.tf create mode 100644 terraform/shipyard-swarm/variables.tf create mode 100644 terraform/synology/outputs.tf create mode 100644 terraform/synology/providers.tf create mode 100644 terraform/synology/shares.tf create mode 100644 terraform/synology/variables.tf diff --git a/README.md b/README.md index 2ee6db0..f995cf5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,40 @@ -# homelab-gitops +# Snarfnet Homelab GitOps & Infrastructure as Code -Snarfnet Homelab GitOps, Terraform, and ArgoCD Declarations \ No newline at end of file +This repository contains declarative configurations for the Snarfnet homelab infrastructure across Kubernetes clusters (`spaceport`, `aerodrome`), Docker Swarm (`shipyard`), Synology NAS storage arrays, and Harvester HCI. + +## Repository Layout + +```text +homelab-gitops/ +├── terraform/ +│ ├── authentik/ # Authentik Providers, Applications, Proxy ForwardAuth, and Security Policies +│ ├── harvester/ # Declarative VLAN networks, VM templates, and SSH keys +│ ├── shipyard-swarm/ # Docker Swarm stacks (NPM, Gotify, Homebox, Peanut) +│ └── synology/ # Synology shared folders (kubernetes, docker, backups, media) and NFS rules +├── argocd/ +│ ├── bootstrap/ # Root App-of-Apps and Project definitions +│ ├── spaceport/ # Applications hosted on spaceport (Gitea, Zabbix, Jenkins, Semaphore, Coder, Homepage) +│ └── aerodrome/ # Applications hosted on aerodrome (Authentik, AWX, Grafana, Uptime Kuma, Longhorn) +└── manifests/ # Raw Kubernetes YAML manifests managed by ArgoCD + ├── spaceport/ + └── aerodrome/ +``` + +## Terraform State Storage + +Terraform state is stored in Gitea's built-in HTTP State Backend: +- Backend URL: `https://git.snarfnet.net/api/packages/snarf/terraform/state/` +- HTTP Authentication: Basic auth via Gitea account (`snarf`) + +## ArgoCD GitOps Engine + +- **ArgoCD Server:** `https://argo.snarfnet.net` (hosted on `spaceport` in namespace `infrastructure`) +- **Target Clusters:** + - In-cluster (`https://kubernetes.default.svc`): Spaceport (`sninfr0lnxctl01–04`, `sninfr0lnxwrk01–06`) + - Remote cluster (`https://140.44.4.11:6443`): Aerodrome (`sninfr2lnxctl01–03`, `sninfr2lnxwrk01–07`) +- **Sync Policy:** + - `automated: { prune: false, selfHeal: true }` + - `syncOptions: [ServerSideApply=true, CreateNamespace=true]` +- **Notifications Pipeline:** + - Webhook integration to Gotify (`http://140.44.4.70:8090/message?token=A8aWxGiToPsAuToMaTiO`) + - Automated failover bridge to Discord channel `#infra-alerts` diff --git a/argocd/aerodrome/authentik-app.yaml b/argocd/aerodrome/authentik-app.yaml new file mode 100644 index 0000000..56ef9d2 --- /dev/null +++ b/argocd/aerodrome/authentik-app.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: aerodrome-authentik + namespace: infrastructure + labels: + app.kubernetes.io/name: aerodrome-authentik + cluster: aerodrome + annotations: + notifications.argoproj.io/subscribe.on-sync-failed.gotify: "" + notifications.argoproj.io/subscribe.on-health-degraded.gotify: "" +spec: + project: homelab + source: + repoURL: "https://git.snarfnet.net/Snarf/homelab-gitops.git" + targetRevision: HEAD + path: manifests/aerodrome/authentik + destination: + server: "https://140.44.4.11:6443" + namespace: infra + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/argocd/aerodrome/awx-app.yaml b/argocd/aerodrome/awx-app.yaml new file mode 100644 index 0000000..7bf98b5 --- /dev/null +++ b/argocd/aerodrome/awx-app.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: aerodrome-awx + namespace: infrastructure + labels: + app.kubernetes.io/name: aerodrome-awx + cluster: aerodrome + annotations: + notifications.argoproj.io/subscribe.on-sync-failed.gotify: "" + notifications.argoproj.io/subscribe.on-health-degraded.gotify: "" +spec: + project: homelab + source: + repoURL: "https://git.snarfnet.net/Snarf/homelab-gitops.git" + targetRevision: HEAD + path: manifests/aerodrome/awx + destination: + server: "https://140.44.4.11:6443" + namespace: awx + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/argocd/aerodrome/grafana-app.yaml b/argocd/aerodrome/grafana-app.yaml new file mode 100644 index 0000000..86e0d20 --- /dev/null +++ b/argocd/aerodrome/grafana-app.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: aerodrome-grafana + namespace: infrastructure + labels: + app.kubernetes.io/name: aerodrome-grafana + cluster: aerodrome + annotations: + notifications.argoproj.io/subscribe.on-sync-failed.gotify: "" + notifications.argoproj.io/subscribe.on-health-degraded.gotify: "" +spec: + project: homelab + source: + repoURL: "https://git.snarfnet.net/Snarf/homelab-gitops.git" + targetRevision: HEAD + path: manifests/aerodrome/grafana + destination: + server: "https://140.44.4.11:6443" + namespace: monitoring + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/argocd/aerodrome/longhorn-recurring-jobs-app.yaml b/argocd/aerodrome/longhorn-recurring-jobs-app.yaml new file mode 100644 index 0000000..a02ace6 --- /dev/null +++ b/argocd/aerodrome/longhorn-recurring-jobs-app.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: aerodrome-longhorn-recurring-jobs + namespace: infrastructure + labels: + app.kubernetes.io/name: aerodrome-longhorn-recurring-jobs + cluster: aerodrome + annotations: + notifications.argoproj.io/subscribe.on-sync-failed.gotify: "" + notifications.argoproj.io/subscribe.on-health-degraded.gotify: "" +spec: + project: homelab + source: + repoURL: "https://git.snarfnet.net/Snarf/homelab-gitops.git" + targetRevision: HEAD + path: manifests/aerodrome/longhorn-recurring-jobs + destination: + server: "https://140.44.4.11:6443" + namespace: longhorn-system + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/argocd/aerodrome/uptime-kuma-app.yaml b/argocd/aerodrome/uptime-kuma-app.yaml new file mode 100644 index 0000000..736c2a4 --- /dev/null +++ b/argocd/aerodrome/uptime-kuma-app.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: aerodrome-uptime-kuma + namespace: infrastructure + labels: + app.kubernetes.io/name: aerodrome-uptime-kuma + cluster: aerodrome + annotations: + notifications.argoproj.io/subscribe.on-sync-failed.gotify: "" + notifications.argoproj.io/subscribe.on-health-degraded.gotify: "" +spec: + project: homelab + source: + repoURL: "https://git.snarfnet.net/Snarf/homelab-gitops.git" + targetRevision: HEAD + path: manifests/aerodrome/uptime-kuma + destination: + server: "https://140.44.4.11:6443" + namespace: monitoring + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/argocd/bootstrap/argocd-project.yaml b/argocd/bootstrap/argocd-project.yaml new file mode 100644 index 0000000..ed9608e --- /dev/null +++ b/argocd/bootstrap/argocd-project.yaml @@ -0,0 +1,23 @@ +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: homelab + namespace: infrastructure + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + description: "Snarfnet Homelab Core Services & Workloads" + sourceRepos: + - "https://git.snarfnet.net/Snarf/homelab-gitops.git" + - "http://gitea-http.infrastructure.svc.cluster.local:3000/Snarf/homelab-gitops.git" + destinations: + - namespace: "*" + server: "https://kubernetes.default.svc" + - namespace: "*" + server: "https://140.44.4.11:6443" + clusterResourceWhitelist: + - group: "*" + kind: "*" + namespaceResourceWhitelist: + - group: "*" + kind: "*" diff --git a/argocd/bootstrap/root-app-of-apps.yaml b/argocd/bootstrap/root-app-of-apps.yaml new file mode 100644 index 0000000..f8ff26b --- /dev/null +++ b/argocd/bootstrap/root-app-of-apps.yaml @@ -0,0 +1,30 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: root-app-of-apps + namespace: infrastructure + labels: + app.kubernetes.io/name: root-app-of-apps + annotations: + notifications.argoproj.io/subscribe.on-sync-failed.gotify: "" + notifications.argoproj.io/subscribe.on-health-degraded.gotify: "" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: homelab + source: + repoURL: "https://git.snarfnet.net/Snarf/homelab-gitops.git" + targetRevision: HEAD + path: argocd/bootstrap + directory: + recurse: true + destination: + server: "https://kubernetes.default.svc" + namespace: infrastructure + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/argocd/spaceport/coder-app.yaml b/argocd/spaceport/coder-app.yaml new file mode 100644 index 0000000..3baae39 --- /dev/null +++ b/argocd/spaceport/coder-app.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: spaceport-coder + namespace: infrastructure + labels: + app.kubernetes.io/name: spaceport-coder + cluster: spaceport + annotations: + notifications.argoproj.io/subscribe.on-sync-failed.gotify: "" + notifications.argoproj.io/subscribe.on-health-degraded.gotify: "" +spec: + project: homelab + source: + repoURL: "https://git.snarfnet.net/Snarf/homelab-gitops.git" + targetRevision: HEAD + path: manifests/spaceport/coder + destination: + server: "https://kubernetes.default.svc" + namespace: infrastructure + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/argocd/spaceport/gitea-app.yaml b/argocd/spaceport/gitea-app.yaml new file mode 100644 index 0000000..546f7f7 --- /dev/null +++ b/argocd/spaceport/gitea-app.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: spaceport-gitea + namespace: infrastructure + labels: + app.kubernetes.io/name: spaceport-gitea + cluster: spaceport + annotations: + notifications.argoproj.io/subscribe.on-sync-failed.gotify: "" + notifications.argoproj.io/subscribe.on-health-degraded.gotify: "" +spec: + project: homelab + source: + repoURL: "https://git.snarfnet.net/Snarf/homelab-gitops.git" + targetRevision: HEAD + path: manifests/spaceport/gitea + destination: + server: "https://kubernetes.default.svc" + namespace: infrastructure + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/argocd/spaceport/homepage-app.yaml b/argocd/spaceport/homepage-app.yaml new file mode 100644 index 0000000..f6711d5 --- /dev/null +++ b/argocd/spaceport/homepage-app.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: spaceport-homepage + namespace: infrastructure + labels: + app.kubernetes.io/name: spaceport-homepage + cluster: spaceport + annotations: + notifications.argoproj.io/subscribe.on-sync-failed.gotify: "" + notifications.argoproj.io/subscribe.on-health-degraded.gotify: "" +spec: + project: homelab + source: + repoURL: "https://git.snarfnet.net/Snarf/homelab-gitops.git" + targetRevision: HEAD + path: manifests/spaceport/homepage + destination: + server: "https://kubernetes.default.svc" + namespace: infrastructure + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/argocd/spaceport/jenkins-app.yaml b/argocd/spaceport/jenkins-app.yaml new file mode 100644 index 0000000..e205642 --- /dev/null +++ b/argocd/spaceport/jenkins-app.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: spaceport-jenkins + namespace: infrastructure + labels: + app.kubernetes.io/name: spaceport-jenkins + cluster: spaceport + annotations: + notifications.argoproj.io/subscribe.on-sync-failed.gotify: "" + notifications.argoproj.io/subscribe.on-health-degraded.gotify: "" +spec: + project: homelab + source: + repoURL: "https://git.snarfnet.net/Snarf/homelab-gitops.git" + targetRevision: HEAD + path: manifests/spaceport/jenkins + destination: + server: "https://kubernetes.default.svc" + namespace: infrastructure + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/argocd/spaceport/semaphore-app.yaml b/argocd/spaceport/semaphore-app.yaml new file mode 100644 index 0000000..47638dc --- /dev/null +++ b/argocd/spaceport/semaphore-app.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: spaceport-semaphore + namespace: infrastructure + labels: + app.kubernetes.io/name: spaceport-semaphore + cluster: spaceport + annotations: + notifications.argoproj.io/subscribe.on-sync-failed.gotify: "" + notifications.argoproj.io/subscribe.on-health-degraded.gotify: "" +spec: + project: homelab + source: + repoURL: "https://git.snarfnet.net/Snarf/homelab-gitops.git" + targetRevision: HEAD + path: manifests/spaceport/semaphore + destination: + server: "https://kubernetes.default.svc" + namespace: infrastructure + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/argocd/spaceport/zabbix-app.yaml b/argocd/spaceport/zabbix-app.yaml new file mode 100644 index 0000000..cf636d3 --- /dev/null +++ b/argocd/spaceport/zabbix-app.yaml @@ -0,0 +1,27 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: spaceport-zabbix + namespace: infrastructure + labels: + app.kubernetes.io/name: spaceport-zabbix + cluster: spaceport + annotations: + notifications.argoproj.io/subscribe.on-sync-failed.gotify: "" + notifications.argoproj.io/subscribe.on-health-degraded.gotify: "" +spec: + project: homelab + source: + repoURL: "https://git.snarfnet.net/Snarf/homelab-gitops.git" + targetRevision: HEAD + path: manifests/spaceport/zabbix + destination: + server: "https://kubernetes.default.svc" + namespace: zabbix + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/manifests/aerodrome/longhorn-recurring-jobs/recurring-jobs.yaml b/manifests/aerodrome/longhorn-recurring-jobs/recurring-jobs.yaml new file mode 100644 index 0000000..64d0c92 --- /dev/null +++ b/manifests/aerodrome/longhorn-recurring-jobs/recurring-jobs.yaml @@ -0,0 +1,41 @@ +apiVersion: longhorn.io/v1beta2 +kind: RecurringJob +metadata: + name: snapshot-4h + namespace: longhorn-system +spec: + name: snapshot-4h + task: snapshot + cron: "0 */4 * * *" + retain: 6 + concurrency: 2 + groups: + - default +--- +apiVersion: longhorn.io/v1beta2 +kind: RecurringJob +metadata: + name: snapshot-daily + namespace: longhorn-system +spec: + name: snapshot-daily + task: snapshot + cron: "0 2 * * *" + retain: 7 + concurrency: 2 + groups: + - default +--- +apiVersion: longhorn.io/v1beta2 +kind: RecurringJob +metadata: + name: backup-daily-nas + namespace: longhorn-system +spec: + name: backup-daily-nas + task: backup + cron: "0 3 * * *" + retain: 14 + concurrency: 2 + groups: + - default diff --git a/terraform/authentik/applications.tf b/terraform/authentik/applications.tf new file mode 100644 index 0000000..9d81a0b --- /dev/null +++ b/terraform/authentik/applications.tf @@ -0,0 +1,131 @@ +# 1. Argo CD +resource "authentik_application" "argocd" { + name = "Argo CD" + slug = "argo-cd" + protocol_provider = authentik_provider_oauth2.argocd.id + meta_icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/argo.png" + meta_launch_url = "https://argo.snarfnet.net" + group = "Infrastructure" + open_in_new_tab = true +} + +# 2. Gitea +resource "authentik_application" "gitea" { + name = "Gitea" + slug = "gitea" + protocol_provider = authentik_provider_oauth2.gitea.id + meta_icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/gitea.png" + meta_launch_url = "https://git.snarfnet.net" + group = "Development" + open_in_new_tab = true +} + +# 3. AWX +resource "authentik_application" "awx" { + name = "AWX Automation" + slug = "awx" + protocol_provider = authentik_provider_oauth2.awx.id + meta_icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/ansible.png" + meta_launch_url = "https://awx.snarfnet.net" + group = "Infrastructure" + open_in_new_tab = true +} + +# 4. Grafana +resource "authentik_application" "grafana" { + name = "Grafana" + slug = "grafana" + protocol_provider = authentik_provider_oauth2.grafana.id + meta_icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/grafana.png" + meta_launch_url = "https://grafana.snarfnet.net" + group = "Observability" + open_in_new_tab = true +} + +# 5. Zabbix +resource "authentik_application" "zabbix" { + name = "Zabbix" + slug = "zabbix" + protocol_provider = authentik_provider_oauth2.zabbix.id + meta_icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/zabbix.png" + meta_launch_url = "https://zabbix.snarfnet.net" + group = "Observability" + open_in_new_tab = true +} + +# 6. Coder +resource "authentik_application" "coder" { + name = "Coder" + slug = "coder" + protocol_provider = authentik_provider_oauth2.coder.id + meta_icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/coder.png" + meta_launch_url = "https://coder.snarfnet.net" + group = "Development" + open_in_new_tab = true +} + +# 7. Semaphore +resource "authentik_application" "semaphore" { + name = "Semaphore UI" + slug = "semaphore" + protocol_provider = authentik_provider_oauth2.semaphore.id + meta_icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/ansible.png" + meta_launch_url = "https://semaphore.snarfnet.net" + group = "Infrastructure" + open_in_new_tab = true +} + +# 8. Synology DSM +resource "authentik_application" "synology" { + name = "Synology DSM" + slug = "synology" + protocol_provider = authentik_provider_oauth2.synology.id + meta_icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/synology.png" + meta_launch_url = "https://nas.snarfnet.net" + group = "Storage" + open_in_new_tab = true +} + +# 9. Gotify +resource "authentik_application" "gotify" { + name = "Gotify" + slug = "gotify" + protocol_provider = authentik_provider_proxy.gotify.id + meta_icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/gotify.png" + meta_launch_url = "https://gotify.snarfnet.net" + group = "Observability" + open_in_new_tab = true +} + +# 10. Peanut +resource "authentik_application" "peanut" { + name = "Peanut UPS" + slug = "peanut" + protocol_provider = authentik_provider_proxy.peanut.id + meta_icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/ups.png" + meta_launch_url = "https://peanut.snarfnet.net" + group = "Power & Environment" + open_in_new_tab = true +} + +# 11. Homebox +resource "authentik_application" "homebox" { + name = "Homebox" + slug = "homebox" + protocol_provider = authentik_provider_proxy.homebox.id + meta_icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/homebox.png" + meta_launch_url = "https://homebox.snarfnet.net" + group = "Management" + open_in_new_tab = true +} + +# 12. NPM +resource "authentik_application" "npm" { + name = "Nginx Proxy Manager" + slug = "nginx-proxy-manager" + protocol_provider = authentik_provider_proxy.npm.id + meta_icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/nginx-proxy-manager.png" + meta_launch_url = "https://nginx.snarfnet.net" + group = "Infrastructure" + open_in_new_tab = true +} diff --git a/terraform/authentik/oauth2_providers.tf b/terraform/authentik/oauth2_providers.tf new file mode 100644 index 0000000..436b93c --- /dev/null +++ b/terraform/authentik/oauth2_providers.tf @@ -0,0 +1,140 @@ +# Scopes +data "authentik_property_mapping_provider_scope" "openid" { + scope_name = "openid" +} + +data "authentik_property_mapping_provider_scope" "email" { + scope_name = "email" +} + +data "authentik_property_mapping_provider_scope" "profile" { + scope_name = "profile" +} + +# 1. Argo CD +resource "authentik_provider_oauth2" "argocd" { + name = "Argo CD" + client_id = "argocd-client-id" + client_secret = "SnarfNetArgoCDPermanentSecret2026" + authorization_flow = var.default_authorization_flow_uuid + invalidation_flow = var.default_invalidation_flow_uuid + signing_key = var.default_signing_key_uuid + redirect_uris = ["https://argo.snarfnet.net/auth/callback"] + property_mappings = [ + data.authentik_property_mapping_provider_scope.openid.id, + data.authentik_property_mapping_provider_scope.email.id, + data.authentik_property_mapping_provider_scope.profile.id, + ] +} + +# 2. Gitea +resource "authentik_provider_oauth2" "gitea" { + name = "Gitea" + client_id = "gitea-client-id" + client_secret = "SnarfNetGiteaPermanentSecret2026" + authorization_flow = var.default_authorization_flow_uuid + invalidation_flow = var.default_invalidation_flow_uuid + signing_key = var.default_signing_key_uuid + redirect_uris = ["https://git.snarfnet.net/user/oauth2/authentik/callback"] + property_mappings = [ + data.authentik_property_mapping_provider_scope.openid.id, + data.authentik_property_mapping_provider_scope.email.id, + data.authentik_property_mapping_provider_scope.profile.id, + ] +} + +# 3. AWX +resource "authentik_provider_oauth2" "awx" { + name = "AWX Automation" + client_id = "awx-client-id" + client_secret = "SnarfNetAWXPermanentSecret2026" + authorization_flow = var.default_authorization_flow_uuid + invalidation_flow = var.default_invalidation_flow_uuid + signing_key = var.default_signing_key_uuid + redirect_uris = ["https://awx.snarfnet.net/sso/complete/authentik/"] + property_mappings = [ + data.authentik_property_mapping_provider_scope.openid.id, + data.authentik_property_mapping_provider_scope.email.id, + data.authentik_property_mapping_provider_scope.profile.id, + ] +} + +# 4. Grafana +resource "authentik_provider_oauth2" "grafana" { + name = "Grafana Monitoring" + client_id = "grafana-client-id" + client_secret = "SnarfNetGrafanaPermanentSecret2026" + authorization_flow = var.default_authorization_flow_uuid + invalidation_flow = var.default_invalidation_flow_uuid + signing_key = var.default_signing_key_uuid + redirect_uris = ["https://grafana.snarfnet.net/login/generic_oauth"] + property_mappings = [ + data.authentik_provider_mapping_provider_scope.openid.id, + data.authentik_property_mapping_provider_scope.email.id, + data.authentik_property_mapping_provider_scope.profile.id, + ] +} + +# 5. Zabbix +resource "authentik_provider_oauth2" "zabbix" { + name = "Zabbix Monitoring" + client_id = "zabbix-client-id" + client_secret = "SnarfNetZabbixPermanentSecret2026" + authorization_flow = var.default_authorization_flow_uuid + invalidation_flow = var.default_invalidation_flow_uuid + signing_key = var.default_signing_key_uuid + redirect_uris = ["https://zabbix.snarfnet.net/index_sso.php"] + property_mappings = [ + data.authentik_property_mapping_provider_scope.openid.id, + data.authentik_property_mapping_provider_scope.email.id, + data.authentik_property_mapping_provider_scope.profile.id, + ] +} + +# 6. Coder Workspaces +resource "authentik_provider_oauth2" "coder" { + name = "Coder Workspaces" + client_id = "coder-client-id" + client_secret = "SnarfNetCoderPermanentSecret2026" + authorization_flow = var.default_authorization_flow_uuid + invalidation_flow = var.default_invalidation_flow_uuid + signing_key = var.default_signing_key_uuid + redirect_uris = ["https://coder.snarfnet.net/api/v2/users/oidc/callback"] + property_mappings = [ + data.authentik_property_mapping_provider_scope.openid.id, + data.authentik_property_mapping_provider_scope.email.id, + data.authentik_property_mapping_provider_scope.profile.id, + ] +} + +# 7. Semaphore UI +resource "authentik_provider_oauth2" "semaphore" { + name = "Semaphore UI" + client_id = "semaphore-client-id" + client_secret = "SnarfNetSemaphorePermanentSecret2026" + authorization_flow = var.default_authorization_flow_uuid + invalidation_flow = var.default_invalidation_flow_uuid + signing_key = var.default_signing_key_uuid + redirect_uris = ["https://semaphore.snarfnet.net/api/auth/oidc/authentik/redirect"] + property_mappings = [ + data.authentik_property_mapping_provider_scope.openid.id, + data.authentik_property_mapping_provider_scope.email.id, + data.authentik_property_mapping_provider_scope.profile.id, + ] +} + +# 8. Synology DSM +resource "authentik_provider_oauth2" "synology" { + name = "Synology DSM" + client_id = "synology-client-id" + client_secret = "SnarfNetSynologyPermanentSecret2026" + authorization_flow = var.default_authorization_flow_uuid + invalidation_flow = var.default_invalidation_flow_uuid + signing_key = var.default_signing_key_uuid + redirect_uris = ["https://nas.snarfnet.net/entry.cgi", "https://snarfnas.snarfnet.net/entry.cgi"] + property_mappings = [ + data.authentik_property_mapping_provider_scope.openid.id, + data.authentik_property_mapping_provider_scope.email.id, + data.authentik_property_mapping_provider_scope.profile.id, + ] +} diff --git a/terraform/authentik/outputs.tf b/terraform/authentik/outputs.tf new file mode 100644 index 0000000..6d989c8 --- /dev/null +++ b/terraform/authentik/outputs.tf @@ -0,0 +1,17 @@ +output "authentik_applications" { + description = "Configured Authentik application slugs" + value = [ + authentik_application.argocd.slug, + authentik_application.gitea.slug, + authentik_application.awx.slug, + authentik_application.grafana.slug, + authentik_application.zabbix.slug, + authentik_application.coder.slug, + authentik_application.semaphore.slug, + authentik_application.synology.slug, + authentik_application.gotify.slug, + authentik_application.peanut.slug, + authentik_application.homebox.slug, + authentik_application.npm.slug, + ] +} diff --git a/terraform/authentik/policies.tf b/terraform/authentik/policies.tf new file mode 100644 index 0000000..899ebcb --- /dev/null +++ b/terraform/authentik/policies.tf @@ -0,0 +1,7 @@ +# Conditional MFA & Security Policies +resource "authentik_policy_password" "default" { + name = "homelab-password-policy" + length_min = 12 + error_message = "Password must be at least 12 characters long." + check_have_i_been_pwned = false +} diff --git a/terraform/authentik/providers.tf b/terraform/authentik/providers.tf new file mode 100644 index 0000000..fd755e3 --- /dev/null +++ b/terraform/authentik/providers.tf @@ -0,0 +1,21 @@ +terraform { + required_version = ">= 1.5.0" + required_providers { + authentik = { + source = "goauthentik/authentik" + version = "~> 2024.10" + } + } + + backend "http" { + address = "https://git.snarfnet.net/api/packages/snarf/terraform/state/authentik" + lock_address = "https://git.snarfnet.net/api/packages/snarf/terraform/state/authentik/lock" + unlock_address = "https://git.snarfnet.net/api/packages/snarf/terraform/state/authentik/lock" + username = "snarf" + } +} + +provider "authentik" { + url = var.authentik_url + token = var.authentik_token +} diff --git a/terraform/authentik/proxy_providers.tf b/terraform/authentik/proxy_providers.tf new file mode 100644 index 0000000..e498e07 --- /dev/null +++ b/terraform/authentik/proxy_providers.tf @@ -0,0 +1,35 @@ +# 1. Gotify Web UI ForwardAuth +resource "authentik_provider_proxy" "gotify" { + name = "Gotify ForwardAuth" + authorization_flow = var.default_authorization_flow_uuid + invalidation_flow = var.default_invalidation_flow_uuid + external_host = "https://gotify.snarfnet.net" + mode = "forward_single" +} + +# 2. Peanut UPS Dashboard ForwardAuth +resource "authentik_provider_proxy" "peanut" { + name = "Peanut ForwardAuth" + authorization_flow = var.default_authorization_flow_uuid + invalidation_flow = var.default_invalidation_flow_uuid + external_host = "https://peanut.snarfnet.net" + mode = "forward_single" +} + +# 3. Homebox Inventory ForwardAuth +resource "authentik_provider_proxy" "homebox" { + name = "Homebox ForwardAuth" + authorization_flow = var.default_authorization_flow_uuid + invalidation_flow = var.default_invalidation_flow_uuid + external_host = "https://homebox.snarfnet.net" + mode = "forward_single" +} + +# 4. Nginx Proxy Manager Admin ForwardAuth +resource "authentik_provider_proxy" "npm" { + name = "NPM Admin ForwardAuth" + authorization_flow = var.default_authorization_flow_uuid + invalidation_flow = var.default_invalidation_flow_uuid + external_host = "https://nginx.snarfnet.net" + mode = "forward_single" +} diff --git a/terraform/authentik/variables.tf b/terraform/authentik/variables.tf new file mode 100644 index 0000000..2a051cc --- /dev/null +++ b/terraform/authentik/variables.tf @@ -0,0 +1,30 @@ +variable "authentik_url" { + type = string + description = "Base URL for the Authentik instance" + default = "https://authentik.snarfnet.net" +} + +variable "authentik_token" { + type = string + description = "API Token for Authentik automation" + default = "SnarfNetPermanentAutomationToken2026" + sensitive = true +} + +variable "default_authorization_flow_uuid" { + type = string + description = "Default OAuth authorization flow UUID" + default = "38a86ef3-f43a-4756-8f54-ca008a8eef6e" +} + +variable "default_invalidation_flow_uuid" { + type = string + description = "Default OAuth invalidation flow UUID" + default = "7a8145b8-2476-4636-9901-825f826df805" +} + +variable "default_signing_key_uuid" { + type = string + description = "Default RSA signing key certificate UUID" + default = "6d48ba88-4b51-428f-903c-93334988a941" +} diff --git a/terraform/harvester/networks.tf b/terraform/harvester/networks.tf new file mode 100644 index 0000000..beee1b5 --- /dev/null +++ b/terraform/harvester/networks.tf @@ -0,0 +1,37 @@ +# Declarative Harvester VLAN Networks +resource "harvester_network" "vlan20_infra" { + name = "vlan20-infra" + namespace = "default" + vlan_id = 20 + route { + mode = "auto" + gateway = "140.44.4.1" + } +} + +resource "harvester_network" "vlan40_storage" { + name = "vlan40-storage" + namespace = "default" + vlan_id = 40 + route { + mode = "auto" + gateway = "14.10.10.1" + } +} + +resource "harvester_network" "vlan50_smarthome" { + name = "vlan50-smarthome" + namespace = "default" + vlan_id = 50 + route { + mode = "auto" + gateway = "14.4.4.1" + } +} + +# SSH Key Pair for Virtual Machines +resource "harvester_ssh_key" "snarfnet_master" { + name = "snarfnet-master-key" + namespace = "default" + public_key = var.master_ssh_public_key +} diff --git a/terraform/harvester/outputs.tf b/terraform/harvester/outputs.tf new file mode 100644 index 0000000..f22bf17 --- /dev/null +++ b/terraform/harvester/outputs.tf @@ -0,0 +1,8 @@ +output "harvester_vlan_networks" { + description = "Configured Harvester VLAN networks" + value = [ + harvester_network.vlan20_infra.name, + harvester_network.vlan40_storage.name, + harvester_network.vlan50_smarthome.name, + ] +} diff --git a/terraform/harvester/providers.tf b/terraform/harvester/providers.tf new file mode 100644 index 0000000..3d21d9c --- /dev/null +++ b/terraform/harvester/providers.tf @@ -0,0 +1,20 @@ +terraform { + required_version = ">= 1.5.0" + required_providers { + harvester = { + source = "harvester/harvester" + version = "~> 0.6" + } + } + + backend "http" { + address = "https://git.snarfnet.net/api/packages/snarf/terraform/state/harvester" + lock_address = "https://git.snarfnet.net/api/packages/snarf/terraform/state/harvester/lock" + unlock_address = "https://git.snarfnet.net/api/packages/snarf/terraform/state/harvester/lock" + username = "snarf" + } +} + +provider "harvester" { + kubeconfig = var.harvester_kubeconfig +} diff --git a/terraform/harvester/variables.tf b/terraform/harvester/variables.tf new file mode 100644 index 0000000..9a9d262 --- /dev/null +++ b/terraform/harvester/variables.tf @@ -0,0 +1,11 @@ +variable "harvester_kubeconfig" { + type = string + description = "Path to Harvester cluster kubeconfig file" + default = "~/.kube/harvester.yaml" +} + +variable "master_ssh_public_key" { + type = string + description = "Snarfnet Master SSH Public Key" + default = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPqgXJd84m12yNq1gqjAOm6n8sK1l9K3eK0Y7Y+V8V7/ snarfnet-master" +} diff --git a/terraform/harvester/vms.tf b/terraform/harvester/vms.tf new file mode 100644 index 0000000..e127c46 --- /dev/null +++ b/terraform/harvester/vms.tf @@ -0,0 +1,11 @@ +# Harvester Virtual Machine Templates / Definitions +# Note: Baremetal Harvester HCI nodes remain outside destructive automation scope. +# VM resources defined declaratively for state tracking. + +resource "harvester_image" "ubuntu_focal" { + name = "ubuntu-20.04-server-cloudimg-arm64" + namespace = "default" + display_name = "Ubuntu 20.04 ARM64 Cloud Image" + source_type = "download" + url = "https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-server-cloudimg-arm64.img" +} diff --git a/terraform/shipyard-swarm/outputs.tf b/terraform/shipyard-swarm/outputs.tf new file mode 100644 index 0000000..8ba229e --- /dev/null +++ b/terraform/shipyard-swarm/outputs.tf @@ -0,0 +1,9 @@ +output "swarm_services" { + description = "Configured Docker Swarm services" + value = [ + docker_service.gotify.name, + docker_service.peanut.name, + docker_service.homebox.name, + docker_service.nginx_proxy_manager.name, + ] +} diff --git a/terraform/shipyard-swarm/providers.tf b/terraform/shipyard-swarm/providers.tf new file mode 100644 index 0000000..1aee6bc --- /dev/null +++ b/terraform/shipyard-swarm/providers.tf @@ -0,0 +1,20 @@ +terraform { + required_version = ">= 1.5.0" + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~> 3.0" + } + } + + backend "http" { + address = "https://git.snarfnet.net/api/packages/snarf/terraform/state/shipyard-swarm" + lock_address = "https://git.snarfnet.net/api/packages/snarf/terraform/state/shipyard-swarm/lock" + unlock_address = "https://git.snarfnet.net/api/packages/snarf/terraform/state/shipyard-swarm/lock" + username = "snarf" + } +} + +provider "docker" { + host = var.docker_host +} diff --git a/terraform/shipyard-swarm/stacks.tf b/terraform/shipyard-swarm/stacks.tf new file mode 100644 index 0000000..cb51878 --- /dev/null +++ b/terraform/shipyard-swarm/stacks.tf @@ -0,0 +1,173 @@ +# Swarm Overlay Network +resource "docker_network" "homelab_public" { + name = "homelab_public" + driver = "overlay" + attachable = true +} + +# 1. Gotify Notification Service +resource "docker_service" "gotify" { + name = "gotify" + + task_spec { + container_spec { + image = "gotify/server:latest" + env = { + GOTIFY_SERVER_PORT = "80" + TZ = "America/New_York" + } + mounts { + target = "/app/data" + source = "${var.swarm_storage_base}/gotify" + type = "bind" + } + } + restart_policy { + condition = "any" + } + } + + mode { + replicated { + replicas = 1 + } + } + + endpoint_spec { + ports { + target_port = 80 + published_port = 8090 + publish_mode = "ingress" + } + } +} + +# 2. Peanut UPS Service +resource "docker_service" "peanut" { + name = "peanut" + + task_spec { + container_spec { + image = "brandawg93/peanut:latest" + env = { + TZ = "America/New_York" + } + mounts { + target = "/config" + source = "${var.swarm_storage_base}/peanut" + type = "bind" + } + } + restart_policy { + condition = "any" + } + } + + mode { + replicated { + replicas = 1 + } + } + + endpoint_spec { + ports { + target_port = 8080 + published_port = 8050 + publish_mode = "ingress" + } + } +} + +# 3. Homebox Inventory Service +resource "docker_service" "homebox" { + name = "homebox" + + task_spec { + container_spec { + image = "ghcr.io/sysadminsmedia/homebox:latest" + env = { + HBOX_MODE = "production" + HBOX_STORAGE_DATA = "/data" + TZ = "America/New_York" + } + mounts { + target = "/data" + source = "${var.swarm_storage_base}/homebox" + type = "bind" + } + } + restart_policy { + condition = "any" + } + } + + mode { + replicated { + replicas = 1 + } + } + + endpoint_spec { + ports { + target_port = 7745 + published_port = 7745 + publish_mode = "ingress" + } + } +} + +# 4. Nginx Proxy Manager Service (Node-pinned to snshpd4lnxswm04) +resource "docker_service" "nginx_proxy_manager" { + name = "nginx-proxy-manager" + + task_spec { + container_spec { + image = "jc21/nginx-proxy-manager:latest" + env = { + TZ = "America/New_York" + PUID = "1000" + PGID = "1000" + } + mounts { + target = "/data" + source = "${var.swarm_storage_base}/nginx-proxy-manager_nginx-data/_data" + type = "bind" + } + mounts { + target = "/etc/letsencrypt" + source = "${var.swarm_storage_base}/nginx-proxy-manager_nginx-letsencrypt/_data" + type = "bind" + } + } + placement { + constraints = ["node.hostname == snshpd4lnxswm04"] + } + restart_policy { + condition = "any" + } + } + + mode { + replicated { + replicas = 1 + } + } + + endpoint_spec { + ports { + target_port = 80 + published_port = 80 + publish_mode = "host" + } + ports { + target_port = 443 + published_port = 443 + publish_mode = "host" + } + ports { + target_port = 81 + published_port = 81 + publish_mode = "host" + } + } +} diff --git a/terraform/shipyard-swarm/variables.tf b/terraform/shipyard-swarm/variables.tf new file mode 100644 index 0000000..372c7d4 --- /dev/null +++ b/terraform/shipyard-swarm/variables.tf @@ -0,0 +1,11 @@ +variable "docker_host" { + type = string + description = "Docker daemon SSH / TCP connection string" + default = "ssh://snarf@140.44.4.71:22" +} + +variable "swarm_storage_base" { + type = string + description = "Path to NFS persistent storage mount" + default = "/mnt/swarm_shared/legacy_volumes" +} diff --git a/terraform/synology/outputs.tf b/terraform/synology/outputs.tf new file mode 100644 index 0000000..902c70a --- /dev/null +++ b/terraform/synology/outputs.tf @@ -0,0 +1,9 @@ +output "configured_shares" { + description = "Configured Synology NFS shares" + value = [ + synology_filestation_share.kubernetes.name, + synology_filestation_share.docker.name, + synology_filestation_share.backups.name, + synology_filestation_share.media.name, + ] +} diff --git a/terraform/synology/providers.tf b/terraform/synology/providers.tf new file mode 100644 index 0000000..99933c5 --- /dev/null +++ b/terraform/synology/providers.tf @@ -0,0 +1,25 @@ +terraform { + required_version = ">= 1.5.0" + required_providers { + synology = { + source = "synology-community/synology" + version = "~> 0.2" + } + } + + backend "http" { + address = "https://git.snarfnet.net/api/packages/snarf/terraform/state/synology" + lock_address = "https://git.snarfnet.net/api/packages/snarf/terraform/state/synology/lock" + unlock_address = "https://git.snarfnet.net/api/packages/snarf/terraform/state/synology/lock" + username = "snarf" + } +} + +provider "synology" { + host = var.synology_host + port = var.synology_port + username = var.synology_user + password = var.synology_password + https = true + insecure = true +} diff --git a/terraform/synology/shares.tf b/terraform/synology/shares.tf new file mode 100644 index 0000000..8338725 --- /dev/null +++ b/terraform/synology/shares.tf @@ -0,0 +1,31 @@ +# 1. Kubernetes Longhorn Backup Target Shared Folder +resource "synology_filestation_share" "kubernetes" { + name = "kubernetes" + vol_path = "/volume1" + description = "Kubernetes Longhorn Backup Target and Persistent RWX storage" + enable_recycle_bin = false +} + +# 2. Docker Swarm Persistent Volumes Shared Folder +resource "synology_filestation_share" "docker" { + name = "docker" + vol_path = "/volume1" + description = "Docker Swarm shared persistent storage" + enable_recycle_bin = false +} + +# 3. Homelab Backups Shared Folder +resource "synology_filestation_share" "backups" { + name = "backups" + vol_path = "/volume1" + description = "Automated SQLite and system config backup bundles" + enable_recycle_bin = true +} + +# 4. Media Shared Folder +resource "synology_filestation_share" "media" { + name = "media" + vol_path = "/volume1" + description = "Media and storage pool" + enable_recycle_bin = false +} diff --git a/terraform/synology/variables.tf b/terraform/synology/variables.tf new file mode 100644 index 0000000..369a495 --- /dev/null +++ b/terraform/synology/variables.tf @@ -0,0 +1,24 @@ +variable "synology_host" { + type = string + description = "Synology DSM IP address" + default = "14.10.10.71" +} + +variable "synology_port" { + type = number + description = "Synology DSM HTTPS port" + default = 5001 +} + +variable "synology_user" { + type = string + description = "Synology DSM management user" + default = "admin" +} + +variable "synology_password" { + type = string + description = "Synology DSM management password" + default = "" + sensitive = true +}