diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..3e57848 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,31 @@ +# .sops.yaml +# SOPS configuration for encrypting Kubernetes secrets +# Generate age key: age-keygen -o key.txt +# Export: export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt +# Encrypt: sops -e -i secret.yaml +# Decrypt: sops -d secret.yaml +# +# Reference: https://github.com/getsops/sops + +creation_rules: + # Infrastructure secrets (networking, storage, monitoring) + - path_regex: infrastructure/.*/.*secret.*\.yaml$ + encrypted_regex: ^(data|stringData)$ + age: >- + age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + + # Application secrets + - path_regex: apps/.*/.*secret.*\.yaml$ + encrypted_regex: ^(data|stringData)$ + age: >- + age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + + # Cluster-specific secrets + - path_regex: clusters/.*/.*secret.*\.yaml$ + encrypted_regex: ^(data|stringData)$ + age: >- + age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +# NOTE: Replace the age public key above with your actual key +# The encrypted_regex ensures only data/stringData fields are encrypted, +# leaving metadata readable for GitOps tooling diff --git a/README.md b/README.md index c67f828..cfea3cb 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,20 @@ # Homelab GitOps ![Kubernetes](https://img.shields.io/badge/k3s-1.28+-326CE5?style=flat&logo=kubernetes&logoColor=white) -![ArgoCD](https://img.shields.io/badge/GitOps-Ready-EF7B4D?style=flat&logo=argo&logoColor=white) +![ArgoCD](https://img.shields.io/badge/ArgoCD-2.10+-EF7B4D?style=flat&logo=argo&logoColor=white) +![SOPS](https://img.shields.io/badge/SOPS-age-green?style=flat) ![License](https://img.shields.io/badge/License-MIT-blue) -GitOps repository for homelab Kubernetes infrastructure. Everything as code. +GitOps repository for homelab Kubernetes infrastructure. Everything as code, auto-synced by ArgoCD. + +## Quick Start + +```bash +# Bootstrap cluster (after ArgoCD installed) +kubectl apply -k clusters/defiant/ +``` + +See [docs/BOOTSTRAP.md](docs/BOOTSTRAP.md) for full setup guide. ## Infrastructure @@ -17,17 +27,33 @@ GitOps repository for homelab Kubernetes infrastructure. Everything as code. ## Structure ``` -├── apps/ # Application deployments -│ ├── base/ # Base manifests -│ └── overlays/ # Environment overrides -├── infrastructure/ # Cluster infrastructure -│ ├── networking/ # Ingress, certs, DNS -│ ├── storage/ # NFS, PVCs -│ └── monitoring/ # Prometheus, Grafana -└── clusters/ - └── defiant/ # k3s cluster config +├── apps/ # Application deployments +│ ├── base/ # Base manifests (Kustomize) +│ └── overlays/ # Environment overrides +│ ├── prod/ # → Auto-discovered by ApplicationSet +│ └── dev/ +├── infrastructure/ # Cluster infrastructure +│ ├── cert-manager/ # ✅ TLS with Let's Encrypt +│ ├── networking/ # Istio gateway, NetworkPolicies +│ ├── storage/ # NFS StorageClass +│ └── monitoring/ # Prometheus, Grafana, Loki +├── clusters/ +│ └── defiant/ # Cluster bootstrap +│ ├── kustomization.yaml +│ ├── root-applicationset.yaml # Git Directory Generator +│ └── projects.yaml # ArgoCD AppProjects +└── docs/ + └── BOOTSTRAP.md # Setup guide ``` +## GitOps Pattern + +Uses **ArgoCD ApplicationSets** with Git Directory Generator: + +- `infrastructure/*` → Auto-creates ArgoCD Applications +- `apps/overlays/prod/*` → Auto-creates prod Applications +- Add a directory, push, ArgoCD syncs automatically + ## Defiant (k3s) Workloads - 🏥 MediSynth - FHIR healthcare platform @@ -43,9 +69,17 @@ GitOps repository for homelab Kubernetes infrastructure. Everything as code. - 🏠 Home Assistant - 📊 Homepage, Uptime Kuma -## Secrets +## Secrets Management -Encrypted with SOPS + age. Never committed in plain text. +Encrypted with **SOPS + age**. Configuration in `.sops.yaml`. + +```bash +# Encrypt a secret +sops -e -i infrastructure/cert-manager/secret.yaml + +# Decrypt for editing +sops infrastructure/cert-manager/secret.yaml +``` ## License diff --git a/apps/base/.gitkeep b/apps/base/.gitkeep new file mode 100644 index 0000000..9946e33 --- /dev/null +++ b/apps/base/.gitkeep @@ -0,0 +1,2 @@ +# Base manifests for applications +# Use Kustomize bases here, overlays in apps/overlays/{env}/ diff --git a/apps/overlays/dev/.gitkeep b/apps/overlays/dev/.gitkeep new file mode 100644 index 0000000..6c6758a --- /dev/null +++ b/apps/overlays/dev/.gitkeep @@ -0,0 +1,2 @@ +# Development overlay directory +# Add dev-specific patches and configurations here diff --git a/apps/overlays/prod/.gitkeep b/apps/overlays/prod/.gitkeep new file mode 100644 index 0000000..b637926 --- /dev/null +++ b/apps/overlays/prod/.gitkeep @@ -0,0 +1,2 @@ +# Production overlay directory +# Each subdirectory here becomes an ArgoCD Application via ApplicationSet diff --git a/clusters/defiant/argocd-namespace.yaml b/clusters/defiant/argocd-namespace.yaml new file mode 100644 index 0000000..7e45ede --- /dev/null +++ b/clusters/defiant/argocd-namespace.yaml @@ -0,0 +1,12 @@ +# clusters/defiant/argocd-namespace.yaml +# ArgoCD namespace with required labels +apiVersion: v1 +kind: Namespace +metadata: + name: argocd + labels: + app.kubernetes.io/name: argocd + app.kubernetes.io/part-of: argocd + # Pod Security Standards - privileged for ArgoCD repo-server + pod-security.kubernetes.io/enforce: baseline + pod-security.kubernetes.io/warn: restricted diff --git a/clusters/defiant/kustomization.yaml b/clusters/defiant/kustomization.yaml new file mode 100644 index 0000000..332c501 --- /dev/null +++ b/clusters/defiant/kustomization.yaml @@ -0,0 +1,12 @@ +# clusters/defiant/kustomization.yaml +# Root Kustomization for defiant k3s cluster +# Applied by ArgoCD or manually via: kubectl apply -k clusters/defiant/ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: argocd + +resources: + - argocd-namespace.yaml + - root-applicationset.yaml + - projects.yaml diff --git a/clusters/defiant/projects.yaml b/clusters/defiant/projects.yaml new file mode 100644 index 0000000..077c519 --- /dev/null +++ b/clusters/defiant/projects.yaml @@ -0,0 +1,49 @@ +# clusters/defiant/projects.yaml +# ArgoCD AppProjects for access control and grouping +--- +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: infrastructure + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + description: Core cluster infrastructure (networking, storage, monitoring) + sourceRepos: + - 'https://github.com/ghndrx/homelab-gitops.git' + - 'https://charts.jetstack.io' + - 'https://prometheus-community.github.io/helm-charts' + - 'https://grafana.github.io/helm-charts' + destinations: + - namespace: '*' + server: https://kubernetes.default.svc + clusterResourceWhitelist: + - group: '*' + kind: '*' + namespaceResourceWhitelist: + - group: '*' + kind: '*' +--- +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: apps + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + description: User-facing applications + sourceRepos: + - 'https://github.com/ghndrx/homelab-gitops.git' + destinations: + - namespace: 'prod-*' + server: https://kubernetes.default.svc + - namespace: 'dev-*' + server: https://kubernetes.default.svc + clusterResourceWhitelist: + - group: '' + kind: Namespace + namespaceResourceWhitelist: + - group: '*' + kind: '*' diff --git a/clusters/defiant/root-applicationset.yaml b/clusters/defiant/root-applicationset.yaml new file mode 100644 index 0000000..eeaad5f --- /dev/null +++ b/clusters/defiant/root-applicationset.yaml @@ -0,0 +1,96 @@ +# clusters/defiant/root-applicationset.yaml +# Root ApplicationSet using Git Directory Generator +# Automatically creates ArgoCD Applications for each component in infrastructure/ +# Reference: https://argo-cd.readthedocs.io/en/latest/operator-manual/applicationset/Generators-Git/ +--- +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: infrastructure + namespace: argocd +spec: + goTemplate: true + goTemplateOptions: ["missingkey=error"] + generators: + - git: + repoURL: https://github.com/ghndrx/homelab-gitops.git + revision: HEAD + directories: + - path: infrastructure/* + template: + metadata: + name: '{{ .path.basename }}' + namespace: argocd + labels: + app.kubernetes.io/part-of: homelab-infrastructure + finalizers: + - resources-finalizer.argocd.argoproj.io + spec: + project: infrastructure + source: + repoURL: https://github.com/ghndrx/homelab-gitops.git + targetRevision: HEAD + path: '{{ .path.path }}' + destination: + server: https://kubernetes.default.svc + namespace: '{{ .path.basename }}' + syncPolicy: + automated: + prune: true + selfHeal: true + allowEmpty: false + syncOptions: + - CreateNamespace=true + - PrunePropagationPolicy=foreground + - PruneLast=true + retry: + limit: 5 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m +--- +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: apps + namespace: argocd +spec: + goTemplate: true + goTemplateOptions: ["missingkey=error"] + generators: + - git: + repoURL: https://github.com/ghndrx/homelab-gitops.git + revision: HEAD + directories: + - path: apps/overlays/prod/* + template: + metadata: + name: 'prod-{{ .path.basename }}' + namespace: argocd + labels: + app.kubernetes.io/part-of: homelab-apps + environment: prod + finalizers: + - resources-finalizer.argocd.argoproj.io + spec: + project: apps + source: + repoURL: https://github.com/ghndrx/homelab-gitops.git + targetRevision: HEAD + path: '{{ .path.path }}' + destination: + server: https://kubernetes.default.svc + namespace: 'prod-{{ .path.basename }}' + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + retry: + limit: 3 + backoff: + duration: 5s + factor: 2 + maxDuration: 1m diff --git a/docs/BOOTSTRAP.md b/docs/BOOTSTRAP.md new file mode 100644 index 0000000..e299c13 --- /dev/null +++ b/docs/BOOTSTRAP.md @@ -0,0 +1,102 @@ +# Cluster Bootstrap Guide + +This guide walks through bootstrapping a new k3s cluster with ArgoCD GitOps. + +## Prerequisites + +- k3s cluster running +- `kubectl` configured with cluster access +- `age` installed for SOPS encryption +- GitHub repo access configured + +## 1. Install ArgoCD + +```bash +# Create namespace +kubectl create namespace argocd + +# Install ArgoCD +kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml + +# Wait for pods +kubectl wait --for=condition=available deployment/argocd-server -n argocd --timeout=300s +``` + +## 2. Configure SOPS + +```bash +# Generate age key (one-time) +age-keygen -o ~/.config/sops/age/keys.txt + +# Get public key +cat ~/.config/sops/age/keys.txt | grep "public key" + +# Update .sops.yaml with your public key +# Create k8s secret for ArgoCD to decrypt +kubectl create secret generic sops-age \ + --namespace=argocd \ + --from-file=key.txt=~/.config/sops/age/keys.txt +``` + +## 3. Bootstrap the Cluster + +```bash +# Apply root kustomization +kubectl apply -k clusters/defiant/ + +# This creates: +# - ArgoCD namespace with PSS labels +# - AppProjects (infrastructure, apps) +# - Root ApplicationSets that auto-discover components +``` + +## 4. Access ArgoCD UI + +```bash +# Get initial admin password +kubectl -n argocd get secret argocd-initial-admin-secret \ + -o jsonpath="{.data.password}" | base64 -d + +# Port forward +kubectl port-forward svc/argocd-server -n argocd 8080:443 + +# Open https://localhost:8080 +# Username: admin +``` + +## 5. Verify Infrastructure + +After bootstrap, ArgoCD will automatically sync: + +- **cert-manager** - TLS certificate management with Let's Encrypt +- **networking** - Istio gateway (when configured) +- **storage** - NFS StorageClass (when configured) +- **monitoring** - Prometheus/Grafana (when configured) + +## Adding New Infrastructure + +1. Create directory under `infrastructure//` +2. Add `kustomization.yaml` (required) +3. Add manifests or helmCharts +4. Commit and push +5. ArgoCD auto-discovers via Git Directory Generator + +## Adding Applications + +1. Create base in `apps/base//` +2. Create overlay in `apps/overlays/prod//` +3. Commit and push +4. ArgoCD creates Application automatically + +## Troubleshooting + +```bash +# Check ApplicationSet status +kubectl get applicationsets -n argocd + +# Check Application sync status +kubectl get applications -n argocd + +# View ArgoCD logs +kubectl logs -n argocd deployment/argocd-applicationset-controller +``` diff --git a/infrastructure/cert-manager/clusterissuers.yaml b/infrastructure/cert-manager/clusterissuers.yaml new file mode 100644 index 0000000..4ea5b30 --- /dev/null +++ b/infrastructure/cert-manager/clusterissuers.yaml @@ -0,0 +1,54 @@ +# infrastructure/cert-manager/clusterissuers.yaml +# Let's Encrypt ClusterIssuers for TLS certificates +# Usage: Add annotation to Ingress: +# cert-manager.io/cluster-issuer: letsencrypt-prod +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + # Staging endpoint for testing (higher rate limits, fake certs) + server: https://acme-staging-v02.api.letsencrypt.org/directory + email: admin@example.com # TODO: Update with your email + privateKeySecretRef: + name: letsencrypt-staging-account-key + solvers: + # HTTP-01 challenge via Ingress + - http01: + ingress: + ingressClassName: istio +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod +spec: + acme: + # Production endpoint (rate limited, real certs) + server: https://acme-v02.api.letsencrypt.org/directory + email: admin@example.com # TODO: Update with your email + privateKeySecretRef: + name: letsencrypt-prod-account-key + solvers: + # HTTP-01 challenge via Ingress + - http01: + ingress: + ingressClassName: istio +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned +spec: + selfSigned: {} +--- +# Internal CA for service-to-service mTLS +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: internal-ca +spec: + ca: + secretName: internal-ca-key-pair diff --git a/infrastructure/cert-manager/kustomization.yaml b/infrastructure/cert-manager/kustomization.yaml new file mode 100644 index 0000000..a79a2ce --- /dev/null +++ b/infrastructure/cert-manager/kustomization.yaml @@ -0,0 +1,48 @@ +# infrastructure/cert-manager/kustomization.yaml +# Cert-Manager with Let's Encrypt ClusterIssuers +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: cert-manager + +resources: + - namespace.yaml + - clusterissuers.yaml + +helmCharts: + - name: cert-manager + repo: https://charts.jetstack.io + version: v1.14.4 + releaseName: cert-manager + namespace: cert-manager + valuesInline: + installCRDs: true + replicaCount: 1 + # Pod Security Standards compliance + podSecurityPolicy: + enabled: false + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + webhook: + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + cainjector: + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + # Prometheus ServiceMonitor + prometheus: + enabled: true + servicemonitor: + enabled: true diff --git a/infrastructure/cert-manager/namespace.yaml b/infrastructure/cert-manager/namespace.yaml new file mode 100644 index 0000000..b21f9c1 --- /dev/null +++ b/infrastructure/cert-manager/namespace.yaml @@ -0,0 +1,11 @@ +# infrastructure/cert-manager/namespace.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: cert-manager + labels: + app.kubernetes.io/name: cert-manager + app.kubernetes.io/component: certificate-management + # Pod Security Standards + pod-security.kubernetes.io/enforce: restricted + pod-security.kubernetes.io/warn: restricted diff --git a/infrastructure/monitoring/kustomization.yaml b/infrastructure/monitoring/kustomization.yaml new file mode 100644 index 0000000..02a8bd0 --- /dev/null +++ b/infrastructure/monitoring/kustomization.yaml @@ -0,0 +1,13 @@ +# infrastructure/monitoring/kustomization.yaml +# Monitoring: Prometheus, Grafana, Alertmanager +# TODO: Add kube-prometheus-stack helm chart +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: monitoring + +resources: [] +# Future additions via helmCharts: +# - kube-prometheus-stack +# - loki +# - grafana dashboards diff --git a/infrastructure/networking/kustomization.yaml b/infrastructure/networking/kustomization.yaml new file mode 100644 index 0000000..340ac2c --- /dev/null +++ b/infrastructure/networking/kustomization.yaml @@ -0,0 +1,12 @@ +# infrastructure/networking/kustomization.yaml +# Networking stack: Istio ingress, DNS, Network Policies +# TODO: Add Istio Gateway, VirtualService templates +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: istio-system + +resources: [] +# Future additions: +# - gateway.yaml +# - default-network-policies.yaml diff --git a/infrastructure/storage/kustomization.yaml b/infrastructure/storage/kustomization.yaml new file mode 100644 index 0000000..a0cfe5b --- /dev/null +++ b/infrastructure/storage/kustomization.yaml @@ -0,0 +1,10 @@ +# infrastructure/storage/kustomization.yaml +# Storage: NFS provisioner, PVC templates +# TODO: Add NFS StorageClass pointing to TrueNAS +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: [] +# Future additions: +# - nfs-storageclass.yaml +# - default-pvc-templates.yaml