feat: initial homelab-gitops declarative structure (Terraform & ArgoCD)

This commit is contained in:
2026-11-02 17:01:29 -05:00
parent 9026f26242
commit 3aa7da5f31
35 changed files with 1200 additions and 2 deletions

View File

@@ -1,3 +1,40 @@
# homelab-gitops
# Snarfnet Homelab GitOps & Infrastructure as Code
Snarfnet Homelab GitOps, Terraform, and ArgoCD Declarations
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/<module>`
- 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`

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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: "*"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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,
]
}

View File

@@ -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,
]
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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
}

View File

@@ -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,
]
}

View File

@@ -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
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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,
]
}

View File

@@ -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
}

View File

@@ -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"
}
}
}

View File

@@ -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"
}

View File

@@ -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,
]
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}