feat(bootstrap): Phase-0 GitHub-seed bootstrap — root-app-github (SSH seed), deploy-key Secret template, cutover URL, bootstrap.sh runner (cilium→longhorn→cnpg→forgejo-db→argocd→cutover)
This commit is contained in:
Executable
+123
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Phase-0 bootstrap — bring a bare Talos cluster to a self-hosted GitOps control
|
||||
# plane, breaking the ArgoCD <-> Forgejo circle via a GitHub seed + cutover.
|
||||
# See docs/adr/0001-gitops-bootstrap-and-cd.md (Part A) and docs/plans/0001-EXECUTION.md.
|
||||
#
|
||||
# Order (all manual, once): Cilium -> Longhorn -> CNPG operator -> forgejo-db
|
||||
# (wait Ready) -> Forgejo -> ArgoCD (seeded from GitHub) -> cutover to Forgejo.
|
||||
# Everything ELSE is deployed by ArgoCD from the seed repo, in sync-wave order.
|
||||
#
|
||||
# Prereqs:
|
||||
# - Talos cluster up; kubectl context points at it
|
||||
# - helm 3, kubectl
|
||||
# - SOPS age key at $SOPS_KEY (for the ArgoCD SOPS CMP plugin)
|
||||
# - GitHub read-only deploy key private half at $DEPLOY_KEY (public half added
|
||||
# to the GitHub repo's Deploy keys)
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BOOT="$SCRIPT_DIR/k8s/bootstrap"
|
||||
SOPS_KEY="${SOPS_KEY:-$HOME/.sops/homelab-age.key}"
|
||||
DEPLOY_KEY="${DEPLOY_KEY:-$HOME/.ssh/argocd_seed}"
|
||||
GITHUB_SSH="[email protected]:Riotpiaole/riotpiao.homelab.com.git"
|
||||
|
||||
log() { echo "[$(date +%H:%M:%S)] $*"; }
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
phase(){ echo; echo "━━━ $* ━━━"; echo; }
|
||||
|
||||
preflight() {
|
||||
log "preflight…"
|
||||
kubectl cluster-info >/dev/null || die "kubectl not configured / cluster unreachable"
|
||||
command -v helm >/dev/null || die "helm 3 not found"
|
||||
[[ -f "$SOPS_KEY" ]] || die "SOPS age key missing at $SOPS_KEY"
|
||||
[[ -f "$DEPLOY_KEY" ]] || die "GitHub deploy key missing at $DEPLOY_KEY (see phase4-argocd/seed-repo-secret.example.yaml)"
|
||||
log "✅ preflight ok"
|
||||
}
|
||||
|
||||
p1_cilium() {
|
||||
phase "PHASE 1a: CNI (Cilium)"
|
||||
if kubectl -n kube-system get ds cilium >/dev/null 2>&1; then log "cilium present, skip"; return; fi
|
||||
helm repo add cilium https://helm.cilium.io >/dev/null; helm repo update >/dev/null
|
||||
helm install cilium cilium/cilium -n kube-system \
|
||||
--set ipam.mode=kubernetes --set kubeProxyReplacement=true --wait --timeout 10m
|
||||
log "✅ cilium installed"
|
||||
}
|
||||
|
||||
p1_longhorn() {
|
||||
phase "PHASE 1b: STORAGE (Longhorn)"
|
||||
if helm -n longhorn-system list 2>/dev/null | grep -q longhorn; then log "longhorn present, skip"; return; fi
|
||||
helm repo add longhorn https://charts.longhorn.io >/dev/null; helm repo update >/dev/null
|
||||
helm install longhorn longhorn/longhorn -n longhorn-system --create-namespace \
|
||||
--values "$BOOT/phase1-storage/longhorn-values.yaml" --wait --timeout 10m
|
||||
kubectl -n longhorn-system wait --for=condition=available --timeout=300s deploy/longhorn-manager
|
||||
kubectl apply -f "$BOOT/phase1-storage/storageclasses.yaml"
|
||||
log "✅ longhorn installed"
|
||||
}
|
||||
|
||||
p2_cnpg() {
|
||||
phase "PHASE 2: CNPG OPERATOR"
|
||||
if helm -n cnpg-system list 2>/dev/null | grep -q cnpg; then log "cnpg present, skip"; return; fi
|
||||
helm repo add cnpg https://cloudnative-pg.github.io/charts >/dev/null; helm repo update >/dev/null
|
||||
helm install cnpg cnpg/cloudnative-pg -n cnpg-system --create-namespace \
|
||||
--values "$BOOT/phase2-cnpg/cnpg-values.yaml" --wait --timeout 5m
|
||||
kubectl get crd clusters.postgresql.cnpg.io >/dev/null || die "CNPG CRD not registered"
|
||||
log "✅ cnpg operator installed"
|
||||
}
|
||||
|
||||
p3_forgejo() {
|
||||
phase "PHASE 3: forgejo-db + Forgejo (ns cicd)"
|
||||
kubectl create ns cicd --dry-run=client -o yaml | kubectl apply -f -
|
||||
kubectl apply -f "$BOOT/phase3-forgejo/forgejo-db.yaml"
|
||||
log "waiting for forgejo-db Ready (3-5 min)…"
|
||||
kubectl wait --for=condition=Ready --timeout=600s cluster/forgejo-db -n cicd
|
||||
kubectl -n cicd get secret forgejo-db-app >/dev/null || die "CNPG did not create forgejo-db-app secret"
|
||||
if helm -n cicd list 2>/dev/null | grep -q forgejo; then log "forgejo present, skip"; return; fi
|
||||
helm repo add forgejo https://code.forgejo.org/forgejo-helm >/dev/null 2>&1 || \
|
||||
helm repo add forgejo https://dl.gitea.io/charts/ >/dev/null
|
||||
helm repo update >/dev/null
|
||||
helm install forgejo forgejo/forgejo -n cicd \
|
||||
--values "$BOOT/phase3-forgejo/forgejo-values.yaml" --wait --timeout 10m
|
||||
log "✅ forgejo up — now push this repo to Forgejo and configure the GitHub pull-mirror"
|
||||
}
|
||||
|
||||
p4_argocd() {
|
||||
phase "PHASE 4: ArgoCD (seeded from GitHub)"
|
||||
kubectl create ns argocd --dry-run=client -o yaml | kubectl apply -f -
|
||||
# SOPS age key for the repo-server CMP plugin
|
||||
kubectl -n argocd create secret generic sops-age \
|
||||
--from-file=keys.txt="$SOPS_KEY" --dry-run=client -o yaml | kubectl apply -f -
|
||||
# GitHub deploy-key repo credential (read-only)
|
||||
kubectl -n argocd create secret generic seed-github-repo \
|
||||
--from-literal=type=git --from-literal=url="$GITHUB_SSH" \
|
||||
--from-file=sshPrivateKey="$DEPLOY_KEY" --dry-run=client -o yaml | kubectl apply -f -
|
||||
kubectl -n argocd label secret seed-github-repo argocd.argoproj.io/secret-type=repository --overwrite
|
||||
if ! helm -n argocd list 2>/dev/null | grep -q argocd; then
|
||||
helm repo add argo https://argoproj.github.io/argo-helm >/dev/null; helm repo update >/dev/null
|
||||
helm install argocd argo/argo-cd -n argocd \
|
||||
--values "$BOOT/phase4-argocd/argocd-values.yaml" --wait --timeout 10m
|
||||
fi
|
||||
kubectl -n argocd wait --for=condition=available --timeout=300s deploy/argocd-server
|
||||
kubectl apply -f "$BOOT/phase4-argocd/root-app-github.yaml"
|
||||
log "✅ ArgoCD syncing from GitHub seed. Watch: kubectl get applications -n argocd"
|
||||
}
|
||||
|
||||
p5_cutover() {
|
||||
phase "PHASE 5: CUTOVER GitHub -> Forgejo"
|
||||
read -rp "Forgejo healthy AND mirroring GitHub? (y/N) " r; [[ $r =~ ^[Yy]$ ]] || die "push+mirror to Forgejo first"
|
||||
kubectl apply -f "$BOOT/phase5-cutover/root-app-forgejo.yaml"
|
||||
log "✅ root app now sourced from Forgejo. GitHub mirror = DR seed. Circle dead."
|
||||
}
|
||||
|
||||
case "${1:-all}" in
|
||||
all) preflight; p1_cilium; p1_longhorn; p2_cnpg; p3_forgejo; p4_argocd
|
||||
log "Phases 1-4 done. Push repo to Forgejo + set up pull-mirror, then: $0 cutover" ;;
|
||||
cilium) preflight; p1_cilium ;;
|
||||
storage) preflight; p1_longhorn ;;
|
||||
cnpg) preflight; p2_cnpg ;;
|
||||
forgejo) preflight; p3_forgejo ;;
|
||||
argocd) preflight; p4_argocd ;;
|
||||
cutover) preflight; p5_cutover ;;
|
||||
*) echo "usage: $0 {all|cilium|storage|cnpg|forgejo|argocd|cutover}"; exit 1 ;;
|
||||
esac
|
||||
@@ -1,8 +1,10 @@
|
||||
# ArgoCD Root App-of-Apps — GitHub Mirror Source
|
||||
# This is the initial configuration that breaks the circular dependency
|
||||
# Points at GitHub mirror, not Forgejo (Forgejo isn't ready yet)
|
||||
#
|
||||
# After Forgejo is healthy and repo is pushed, use phase5-cutover/root-app-forgejo.yaml
|
||||
# ArgoCD Root App-of-Apps — GitHub seed source (circle-breaker).
|
||||
# Applied ONCE at Phase 0 (manual), BEFORE Forgejo serves the repo. Points at the
|
||||
# GitHub seed so ArgoCD can deploy everything after the control plane. After
|
||||
# Forgejo is healthy + mirroring GitHub, cut over with phase5-cutover/root-app-forgejo.yaml.
|
||||
#
|
||||
# repoURL is the SSH form — must match the `url` in the seed-repo deploy-key Secret
|
||||
# (see seed-repo-secret.example.yaml). Apply that Secret before this.
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
@@ -16,11 +18,12 @@ metadata:
|
||||
namespace: argocd
|
||||
spec:
|
||||
description: Homelab infrastructure and applications
|
||||
# Single-tenant homelab: all sources are trusted (GitHub seed, Forgejo, and the
|
||||
# public Helm chart repos the app tree pulls from). Wildcard avoids silently
|
||||
# blocking a chart host (operator.min.io, hashicorp, strimzi.io, go.temporal.io,
|
||||
# dl.gitea.io, *.github.io, charts.*, …).
|
||||
sourceRepos:
|
||||
- 'https://github.com/YOUR-ORG/homelab.git' # ← REPLACE with your GitHub mirror
|
||||
- 'https://forgejo.riotpiao.com/YOUR-ORG/homelab.git'
|
||||
- 'https://*.github.io/*' # Helm charts from GitHub Pages
|
||||
- 'https://charts.*' # Public Helm repos
|
||||
- '*'
|
||||
destinations:
|
||||
- namespace: '*'
|
||||
server: 'https://kubernetes.default.svc'
|
||||
@@ -40,16 +43,13 @@ metadata:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: homelab
|
||||
|
||||
source:
|
||||
repoURL: https://github.com/YOUR-ORG/homelab.git # ← REPLACE with your GitHub mirror
|
||||
repoURL: git@github.com:Riotpiaole/riotpiao.homelab.com.git # GitHub seed (SSH)
|
||||
targetRevision: main
|
||||
path: k8s/argocd/apps
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: argocd
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# ArgoCD repo credential for the PRIVATE GitHub seed — deploy key (read-only).
|
||||
# Apply at Phase 0 BEFORE root-app-github.yaml. This is a TEMPLATE: never commit
|
||||
# the real private key.
|
||||
#
|
||||
# ssh-keygen -t ed25519 -C "argocd@homelab" -f argocd_seed -N ""
|
||||
# # add argocd_seed.pub → GitHub repo → Settings → Deploy keys (Read-only, no write)
|
||||
# kubectl create ns argocd --dry-run=client -o yaml | kubectl apply -f -
|
||||
# kubectl -n argocd create secret generic seed-github-repo \
|
||||
# --from-literal=type=git \
|
||||
# [email protected]:Riotpiaole/riotpiao.homelab.com.git \
|
||||
# --from-file=sshPrivateKey=argocd_seed
|
||||
# kubectl -n argocd label secret seed-github-repo argocd.argoproj.io/secret-type=repository
|
||||
#
|
||||
# url MUST match root-app-github.yaml's repoURL (SSH form).
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: seed-github-repo
|
||||
namespace: argocd
|
||||
labels:
|
||||
argocd.argoproj.io/secret-type: repository
|
||||
stringData:
|
||||
type: git
|
||||
url: [email protected]:Riotpiaole/riotpiao.homelab.com.git
|
||||
sshPrivateKey: |
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
REPLACE-WITH-READ-ONLY-DEPLOY-KEY-PRIVATE-HALF — DO NOT COMMIT THE REAL KEY
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
@@ -15,7 +15,7 @@ spec:
|
||||
project: homelab
|
||||
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/YOUR-ORG/homelab.git # ← REPLACE with your Forgejo URL
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git # Forgejo (post-cutover source of truth)
|
||||
targetRevision: main
|
||||
path: k8s/argocd/apps
|
||||
|
||||
|
||||
Reference in New Issue
Block a user