Initial monorepo scaffold

Turborepo + pnpm monorepo for k3s homelab cluster on Intel NUCs.

- Apps: Next.js web frontend, Express API (TypeScript, Dockerfiles, k8s manifests)
- Packages: shared UI, ESLint config, TypeScript config, Drizzle DB schemas
- Infra/Ansible: bare-metal provisioning with roles for common, k3s-server, k3s-agent, hardening
- Infra/Kubernetes: ArgoCD GitOps (app-of-apps + ApplicationSets), platform components
  (cert-manager, Traefik, CloudNativePG, Valkey, Longhorn, Sealed Secrets), namespaces
- Observability: kube-prometheus-stack, Loki, Promtail as ArgoCD Applications
- CI/CD: GitHub Actions for PR builds, preview deploys, production deploys
- DX: Taskfile, utility scripts, copier templates, Ubiquiti network docs
This commit is contained in:
Julia McGhee
2026-03-19 22:24:56 +00:00
commit 96e3f32f28
118 changed files with 2681 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
# Prerequisites: cert-manager must be installed via Helm first.
# Install: helm install cert-manager jetstack/cert-manager --namespace cert-manager --set crds.enabled=true --version v1.16.3
# This file configures the Let's Encrypt issuers after cert-manager is running.
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: admin@homelab.local
privateKeySecretRef:
name: letsencrypt-staging-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@homelab.local
privateKeySecretRef:
name: letsencrypt-production-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- clusterissuer-letsencrypt.yaml

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager

View File

@@ -0,0 +1,45 @@
# Prerequisites: CloudNativePG operator must be installed first.
# Install: helm install cnpg cloudnative-pg/cloudnative-pg --namespace cnpg-system --create-namespace
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: homelab-pg
namespace: platform
spec:
instances: 2
primaryUpdateStrategy: unsupervised
storage:
storageClass: longhorn
size: 10Gi
postgresql:
parameters:
max_connections: "100"
shared_buffers: 256MB
effective_cache_size: 512MB
work_mem: 4MB
bootstrap:
initdb:
database: homelab
owner: homelab
secret:
name: homelab-pg-credentials
backup:
barmanObjectStore:
destinationPath: s3://homelab-pg-backups/
endpointURL: http://minio.platform.svc:9000
s3Credentials:
accessKeyId:
name: pg-backup-s3-credentials
key: ACCESS_KEY_ID
secretAccessKey:
name: pg-backup-s3-credentials
key: SECRET_ACCESS_KEY
retentionPolicy: "30d"
monitoring:
enablePodMonitor: true

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- cluster.yaml

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- storageclass.yaml

View File

@@ -0,0 +1,6 @@
# Prerequisites: Longhorn must be installed via Helm first.
# Install: helm install longhorn longhorn/longhorn --namespace longhorn-system --create-namespace --version 1.7.2
apiVersion: v1
kind: Namespace
metadata:
name: longhorn-system

View File

@@ -0,0 +1,14 @@
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: Immediate
parameters:
numberOfReplicas: "2"
staleReplicaTimeout: "2880"
dataLocality: best-effort

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml

View File

@@ -0,0 +1,9 @@
# Prerequisites: Sealed Secrets must be installed via Helm first.
# Install: helm install sealed-secrets sealed-secrets/sealed-secrets --namespace kube-system --version 2.16.2
# The controller runs in kube-system; this is just the config namespace.
apiVersion: v1
kind: Namespace
metadata:
name: sealed-secrets
labels:
managed-by: argocd

View File

@@ -0,0 +1,26 @@
# HelmChartConfig customizes the k3s-bundled Traefik deployment
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
ports:
web:
redirectTo:
port: websecure
websecure:
tls:
enabled: true
providers:
kubernetesCRD:
allowCrossNamespace: true
logs:
access:
enabled: true
metrics:
prometheus:
entryPoint: metrics
additionalArguments:
- "--entrypoints.websecure.http.tls.certresolver=letsencrypt"

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmchartconfig.yaml
- middleware-default-headers.yaml

View File

@@ -0,0 +1,16 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: default-headers
namespace: platform
spec:
headers:
browserXssFilter: true
contentTypeNosniff: true
frameDeny: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
customFrameOptionsValue: SAMEORIGIN
customRequestHeaders:
X-Forwarded-Proto: https

View File

@@ -0,0 +1,71 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: valkey
namespace: platform
labels:
app: valkey
spec:
replicas: 1
selector:
matchLabels:
app: valkey
template:
metadata:
labels:
app: valkey
spec:
containers:
- name: valkey
image: valkey/valkey:8.0-alpine
ports:
- containerPort: 6379
args:
- "--requirepass"
- "$(VALKEY_PASSWORD)"
- "--maxmemory"
- "256mb"
- "--maxmemory-policy"
- "allkeys-lru"
env:
- name: VALKEY_PASSWORD
valueFrom:
secretKeyRef:
name: valkey-credentials
key: password
resources:
requests:
memory: 128Mi
cpu: 100m
limits:
memory: 512Mi
readinessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 15
periodSeconds: 20
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: valkey-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: valkey-data
namespace: platform
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 2Gi

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: valkey
namespace: platform
labels:
app: valkey
spec:
type: ClusterIP
ports:
- port: 6379
targetPort: 6379
protocol: TCP
selector:
app: valkey