Compare commits
113
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98c4391ca8 | ||
|
|
7270c15169 | ||
|
|
c61443a1b6 | ||
|
|
0292560621 | ||
|
|
1b91d2d327 | ||
|
|
d1a39e95a9 | ||
|
|
96ca165061 | ||
|
|
f654e9e6aa | ||
|
|
8209e8b44d | ||
|
|
b100a20ba0 | ||
|
|
5150730fe8 | ||
|
|
b9c86d699e | ||
|
|
4f5747b060 | ||
|
|
119c16cea9 | ||
|
|
a192b58e0c | ||
|
|
7141922f19 | ||
|
|
21e741051a | ||
|
|
3ea45b7c2e | ||
|
|
b13d7280b8 | ||
|
|
87ea0f1147 | ||
|
|
d3b6ecfb62 | ||
|
|
76d00bcfc3 | ||
|
|
76d5078611 | ||
|
|
892700b38c | ||
|
|
3b4e6684f1 | ||
|
|
93128a104e | ||
|
|
98c5429a9d | ||
|
|
37a7c37945 | ||
|
|
a6051e025b | ||
|
|
c956ac1465 | ||
|
|
886f546a02 | ||
|
|
8f277adf19 | ||
|
|
43483da902 | ||
|
|
cf6c4f4d7d | ||
|
|
5167656445 | ||
|
|
6eeac820a0 | ||
|
|
abd0af3ea9 | ||
|
|
98d8276476 | ||
|
|
5af38a3f59 | ||
|
|
832add824d | ||
|
|
78c1fa3fb3 | ||
|
|
92d80173b4 | ||
|
|
bd6a21e7e1 | ||
|
|
5589bcd56a | ||
|
|
fd7b714f09 | ||
|
|
6291dd5afb | ||
|
|
648388554d | ||
|
|
6ce46b9ad5 | ||
|
|
d3a059a6b4 | ||
|
|
f015e4577b | ||
|
|
1877f94bf6 | ||
|
|
c5ff86d6dc | ||
|
|
1bf611739b | ||
|
|
9fbfce7963 | ||
|
|
ac1849d2a9 | ||
|
|
4eab8271c7 | ||
|
|
dd491f6f8b | ||
|
|
5b041df884 | ||
|
|
3ea057d83e | ||
|
|
43c0e1faa2 | ||
|
|
3a244577e4 | ||
|
|
7a0d09cbe0 | ||
|
|
cb52356d13 | ||
|
|
d0cbfac7a4 | ||
|
|
930374a3b8 | ||
|
|
b10d1c3a25 | ||
|
|
4c54674dff | ||
|
|
05ff12c117 | ||
|
|
56b1c96fcf | ||
|
|
26a959215e | ||
|
|
193b040de6 | ||
|
|
12778d5576 | ||
|
|
cd6c620619 | ||
|
|
0a87302e19 | ||
|
|
c64b68a36b | ||
|
|
4146a048c9 | ||
|
|
f0fa1dbd27 | ||
|
|
2b1c4b1df4 | ||
|
|
479318c532 | ||
|
|
ff216429b9 | ||
|
|
7441aaf9c3 | ||
|
|
edd739198d | ||
|
|
20f8aac95d | ||
|
|
dbd3dc7b3d | ||
|
|
bebe8dc31b | ||
|
|
4c0d30ce30 | ||
|
|
a17ceedcd8 | ||
|
|
9a779ccaf4 | ||
|
|
20d0517f79 | ||
|
|
c8ea7b9190 | ||
|
|
d3e2215b5c | ||
|
|
c00b2d1b53 | ||
|
|
db6bf742da | ||
|
|
f6298086f2 | ||
|
|
fbc4e55718 | ||
|
|
06c35fb338 | ||
|
|
09fa9c6145 | ||
|
|
bd99208754 | ||
|
|
58605e1b5c | ||
|
|
40fcbd036c | ||
|
|
69b5fc371d | ||
|
|
582524f921 | ||
|
|
828e3fb287 | ||
|
|
1d5c18d62c | ||
|
|
bc1a6d8689 | ||
|
|
e4485412b0 | ||
|
|
2022595426 | ||
|
|
f67aaa41d0 | ||
|
|
69e8cfd6d1 | ||
|
|
db2fc9afc7 | ||
|
|
2b74b58ea6 | ||
|
|
27dbfb1bd7 | ||
|
|
4e67fd907a |
@@ -0,0 +1,460 @@
|
||||
# CI/CD Pipeline: GitOps Validation & Deployment
|
||||
|
||||
## Overview
|
||||
|
||||
Pure GitOps CI/CD pipeline using Forgejo Actions (self-hosted runner).
|
||||
|
||||
**Principle:** Validate in CI, deploy via ArgoCD (no manual steps).
|
||||
|
||||
```
|
||||
git push
|
||||
↓
|
||||
[CI: Validate]
|
||||
├─ yamllint (YAML syntax)
|
||||
├─ kubeval (K8s manifests)
|
||||
├─ kustomize build (all layers)
|
||||
├─ argocd validation (app definitions)
|
||||
└─ security scan (secrets, best practices)
|
||||
↓
|
||||
[If push to main]
|
||||
└─ ArgoCD auto-syncs (if enabled)
|
||||
```
|
||||
|
||||
## Workflows
|
||||
|
||||
### 1. validate-k8s.yaml (Mandatory)
|
||||
|
||||
**Trigger:** Any push/PR with k8s/ changes
|
||||
|
||||
**What it does:**
|
||||
1. Lints all YAML files (`yamllint`)
|
||||
2. Validates K8s manifests (`kubeval`)
|
||||
3. Builds all kustomization layers
|
||||
4. Validates ArgoCD applications
|
||||
5. Reports results
|
||||
|
||||
**Duration:** ~2-3 minutes
|
||||
|
||||
**Status:**
|
||||
- ✅ PASS: All layers build, manifests valid → OK to merge
|
||||
- ❌ FAIL: Syntax error, invalid resource, build failed → Fix & push again
|
||||
|
||||
**Example output:**
|
||||
```
|
||||
=== Building k8s/infrastructure/ ===
|
||||
✓ Infrastructure built successfully
|
||||
Resources: 47
|
||||
|
||||
=== Building k8s/bootstrap/ ===
|
||||
✓ Bootstrap built successfully
|
||||
Resources: 23
|
||||
```
|
||||
|
||||
**When to check:**
|
||||
- After every commit
|
||||
- Before merging PRs
|
||||
- On every branch
|
||||
|
||||
### 2. argocd-sync.yaml (Recommended)
|
||||
|
||||
**Trigger:** Push to main only (k8s/ changed)
|
||||
|
||||
**What it does:**
|
||||
1. Authenticates with ArgoCD
|
||||
2. Syncs `homelab-root` application
|
||||
3. Waits for sync to complete (5 min timeout)
|
||||
4. Verifies all applications healthy
|
||||
|
||||
**Duration:** 1-5 minutes (depends on resources)
|
||||
|
||||
**Status:**
|
||||
- ✅ SYNCED: All resources deployed to cluster
|
||||
- ❌ FAILED: Sync error, pod crashes, etc. → Check ArgoCD UI for details
|
||||
|
||||
**When it runs:**
|
||||
- Automatically after merge to main
|
||||
- Only on k8s/ changes (not on docs)
|
||||
|
||||
**Manual trigger (if needed):**
|
||||
```bash
|
||||
# SSH to runner or use Forgejo UI
|
||||
# Re-run failed workflow
|
||||
# Or manually sync: argocd app sync homelab-root
|
||||
```
|
||||
|
||||
**Requires secrets:**
|
||||
- `ARGOCD_SERVER`: ArgoCD server URL (https://argocd.riotpiao.com)
|
||||
- `ARGOCD_AUTH_TOKEN`: ArgoCD API token (generate via ArgoCD UI)
|
||||
|
||||
### 3. security-scan.yaml (Optional)
|
||||
|
||||
**Trigger:** Any push/PR with k8s/ changes
|
||||
|
||||
**What it does:**
|
||||
1. Scans Dockerfiles for vulnerabilities (`trivy`)
|
||||
2. Scans Helm charts for security issues
|
||||
3. Audits K8s manifests (`polaris`)
|
||||
4. Checks for hardcoded secrets
|
||||
5. Verifies security best practices
|
||||
|
||||
**Duration:** ~3-5 minutes
|
||||
|
||||
**Status:**
|
||||
- ✅ PASS: No critical issues
|
||||
- ⚠️ WARNING: Best practice recommendations (non-blocking)
|
||||
- ❌ FAIL: Hardcoded secrets found (must fix)
|
||||
|
||||
**Common issues:**
|
||||
- Missing resource limits (warning)
|
||||
- Privileged containers (warning)
|
||||
- Hardcoded passwords (ERROR)
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
.forgejo/
|
||||
├── workflows/ # CI/CD workflows
|
||||
│ ├── validate-k8s.yaml # Validate manifests (required)
|
||||
│ ├── argocd-sync.yaml # Sync to cluster (auto on main)
|
||||
│ └── security-scan.yaml # Security checks (optional)
|
||||
└── CI-CD.md # This file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
### 1. Install Forgejo Runner
|
||||
|
||||
```bash
|
||||
# On runner machine (inside cluster or external)
|
||||
forgejo-runner register \
|
||||
--instance https://forgejo.riotpiao.com \
|
||||
--token <registration-token> \
|
||||
--name homelab-runner \
|
||||
--labels docker
|
||||
|
||||
forgejo-runner daemon
|
||||
```
|
||||
|
||||
### 2. Add ArgoCD Secrets to Forgejo
|
||||
|
||||
```bash
|
||||
# Go to: Forgejo → Settings → Secrets
|
||||
|
||||
# Add:
|
||||
ARGOCD_SERVER = https://argocd.riotpiao.com
|
||||
ARGOCD_AUTH_TOKEN = <token> # Generate: argocd account generate-token
|
||||
```
|
||||
|
||||
### 3. Generate ArgoCD Token
|
||||
|
||||
```bash
|
||||
# Inside cluster
|
||||
kubectl -n argocd port-forward svc/argocd-server 8080:443
|
||||
|
||||
# Go to: https://localhost:8080/user-info/api-tokens
|
||||
# Create new token (CI/CD)
|
||||
# Copy token to Forgejo secrets
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Workflow Execution
|
||||
|
||||
### When developer pushes to feature branch:
|
||||
|
||||
```
|
||||
git push origin feature/new-service
|
||||
|
||||
↓
|
||||
Forgejo Actions triggered
|
||||
↓
|
||||
validate-k8s.yaml runs:
|
||||
✓ Lints YAML
|
||||
✓ Validates manifests
|
||||
✓ Builds kustomizations
|
||||
✓ All pass → GitHub comment: "Ready to merge"
|
||||
↓
|
||||
Developer opens PR
|
||||
↓
|
||||
Reviewer checks:
|
||||
- Code changes (YAML)
|
||||
- Workflow results
|
||||
- ArgoCD impact (diff)
|
||||
↓
|
||||
PR merged to main
|
||||
```
|
||||
|
||||
### When merged to main:
|
||||
|
||||
```
|
||||
git merge feature/new-service → main
|
||||
|
||||
↓
|
||||
Forgejo Actions triggered
|
||||
↓
|
||||
validate-k8s.yaml runs:
|
||||
✓ Same validation as above
|
||||
↓
|
||||
argocd-sync.yaml runs (if enabled):
|
||||
✓ Syncs homelab-root
|
||||
✓ Waits for sync
|
||||
✓ Verifies health
|
||||
✓ Resources deployed to cluster
|
||||
↓
|
||||
Cluster state = git state
|
||||
(No manual kubectl apply needed!)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging CI/CD Failures
|
||||
|
||||
### Issue: "Kustomize build failed"
|
||||
|
||||
```bash
|
||||
# Run locally
|
||||
cd k8s/
|
||||
kustomize build bootstrap/ # See actual error
|
||||
|
||||
# Fix YAML/kustomization.yaml
|
||||
# git push again
|
||||
```
|
||||
|
||||
### Issue: "Kubeval validation failed"
|
||||
|
||||
```bash
|
||||
# Check K8s manifest syntax
|
||||
kubeval k8s/platform/minio/config.yaml
|
||||
|
||||
# Common issues:
|
||||
# - Typos in apiVersion, kind, metadata
|
||||
# - Missing required fields
|
||||
# - Invalid references (namespace, service name)
|
||||
```
|
||||
|
||||
### Issue: "ArgoCD sync failed"
|
||||
|
||||
```bash
|
||||
# Check ArgoCD UI
|
||||
# https://argocd.riotpiao.com → homelab-root
|
||||
|
||||
# Or CLI
|
||||
argocd app get homelab-root
|
||||
argocd app logs homelab-root --follow
|
||||
|
||||
# Common issues:
|
||||
# - Missing namespace (fixed by infrastructure layer)
|
||||
# - Invalid Helm chart version
|
||||
# - Secret not found
|
||||
# - Network policy blocking traffic
|
||||
```
|
||||
|
||||
### Issue: "Security scan found hardcoded secret"
|
||||
|
||||
```bash
|
||||
# Fix: Remove secret from YAML
|
||||
# Add to SOPS encryption instead
|
||||
|
||||
# Or use ArgoCD Sealed Secrets
|
||||
# (if SOPS not available)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Viewing Results
|
||||
|
||||
### Forgejo Actions UI
|
||||
|
||||
```
|
||||
Repository → Actions
|
||||
├─ validate-k8s
|
||||
│ ├─ ✅ Success (merge safe)
|
||||
│ ├─ ❌ Failed (fix required)
|
||||
│ └─ Logs (click "Steps" → "Summary")
|
||||
├─ argocd-sync
|
||||
│ ├─ ✅ Synced (deployed)
|
||||
│ └─ ❌ Failed (check ArgoCD UI)
|
||||
└─ security-scan
|
||||
├─ ✅ Pass (no critical issues)
|
||||
└─ ⚠️ Warning (review, non-blocking)
|
||||
```
|
||||
|
||||
### ArgoCD UI
|
||||
|
||||
```
|
||||
https://argocd.riotpiao.com
|
||||
├─ homelab-root
|
||||
│ ├─ Status: Synced ✓
|
||||
│ ├─ Health: Healthy ✓
|
||||
│ └─ Details (click to see resources)
|
||||
├─ layer-1-bootstrap
|
||||
├─ layer-2-platform
|
||||
├─ layer-3-security
|
||||
├─ layer-4-applications
|
||||
└─ layer-5-data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Add new service to cluster
|
||||
|
||||
```bash
|
||||
# 1. Create directory and kustomization.yaml
|
||||
mkdir -p k8s/applications/my-service
|
||||
cat > k8s/applications/my-service/kustomization.yaml << EOF
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: my-namespace
|
||||
helmCharts:
|
||||
- name: my-chart
|
||||
repo: https://charts.example.com
|
||||
version: 1.0.0
|
||||
releaseName: my-service
|
||||
valuesFile: values.yaml
|
||||
EOF
|
||||
|
||||
# 2. Add values.yaml
|
||||
cp /template/values.yaml k8s/applications/my-service/
|
||||
|
||||
# 3. Commit and push
|
||||
git add k8s/applications/my-service/
|
||||
git commit -m "feat(apps): add my-service"
|
||||
git push
|
||||
|
||||
# 4. CI validates
|
||||
# 5. Merge to main
|
||||
# 6. ArgoCD syncs automatically
|
||||
# ✓ Service deployed to cluster
|
||||
```
|
||||
|
||||
### Rollback a deployment
|
||||
|
||||
```bash
|
||||
# 1. Find broken commit
|
||||
git log --oneline k8s/ # Identify bad commit
|
||||
|
||||
# 2. Revert
|
||||
git revert <commit-hash>
|
||||
git push
|
||||
|
||||
# 3. CI validates (should pass)
|
||||
# 4. Merge to main
|
||||
# 5. ArgoCD syncs back to previous version
|
||||
# ✓ Cluster state reverted
|
||||
```
|
||||
|
||||
### Emergency: Disable ArgoCD auto-sync
|
||||
|
||||
```bash
|
||||
# If production broken and need time to debug:
|
||||
argocd app set homelab-root --sync-policy none
|
||||
|
||||
# Fix issue in git
|
||||
# Test locally: kustomize build k8s/
|
||||
|
||||
# Re-enable
|
||||
argocd app set homelab-root --sync-policy automated
|
||||
argocd app sync homelab-root
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring & Alerts
|
||||
|
||||
### Check workflow status in Forgejo
|
||||
|
||||
```bash
|
||||
# Dashboard shows:
|
||||
✅ All green → Safe to merge
|
||||
❌ Red → Fix required before merge
|
||||
⏳ Yellow → Still running (wait)
|
||||
```
|
||||
|
||||
### Check ArgoCD status
|
||||
|
||||
```bash
|
||||
argocd app list
|
||||
# Shows: Synced, OutOfSync, Unknown status
|
||||
|
||||
argocd app get homelab-root
|
||||
# Shows: health, sync status, resources
|
||||
|
||||
argocd app logs homelab-root --follow
|
||||
# Real-time logs during sync
|
||||
```
|
||||
|
||||
### Alerts (optional, future)
|
||||
|
||||
```yaml
|
||||
# Could add Forgejo webhooks → Slack/email
|
||||
# When CI/CD fails → Alert ops team
|
||||
# When ArgoCD goes OutOfSync → Alert ops team
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Workflow doesn't trigger
|
||||
|
||||
**Check:**
|
||||
- Is Forgejo runner running? `forgejo-runner daemon`
|
||||
- Did you push to correct branch? (validate runs on all, argocd-sync only on main)
|
||||
- Did path match filter? (must change k8s/ or .forgejo/workflows/)
|
||||
|
||||
### Workflow hangs/times out
|
||||
|
||||
**Check:**
|
||||
- kustomize build → Check for dependency cycles
|
||||
- argocd sync → Check cluster resources (storage full? network down?)
|
||||
- security scan → Large image scan → Takes time
|
||||
|
||||
**Fix:**
|
||||
- Increase timeout in workflow
|
||||
- Optimize kustomization (remove unused resources)
|
||||
- Add resource limits to pods
|
||||
|
||||
### ArgoCD token invalid
|
||||
|
||||
**Fix:**
|
||||
```bash
|
||||
# Regenerate token
|
||||
argocd account generate-token
|
||||
|
||||
# Update Forgejo secret
|
||||
# Settings → Secrets → ARGOCD_AUTH_TOKEN = <new-token>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
✅ **DO:**
|
||||
- Commit all K8s changes to git (no manual kubectl apply)
|
||||
- Run validate-k8s locally before push
|
||||
- Write descriptive commit messages (why this change?)
|
||||
- Review workflow logs before merging
|
||||
- Monitor ArgoCD sync after merge
|
||||
|
||||
❌ **DON'T:**
|
||||
- Push directly to main (always use PR)
|
||||
- Skip workflow validation (it catches errors early)
|
||||
- Ignore security scan warnings
|
||||
- Manually `kubectl apply` (breaks GitOps)
|
||||
- Edit resources in cluster (they revert via ArgoCD)
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Setup Forgejo runner** (if not already running)
|
||||
2. **Add ArgoCD secrets** to Forgejo
|
||||
3. **Test workflows** on feature branch
|
||||
4. **Merge to main** → Watch ArgoCD sync
|
||||
5. **Celebrate:** Full GitOps pipeline working! 🎉
|
||||
@@ -0,0 +1,269 @@
|
||||
name: Cluster CI Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
paths:
|
||||
- 'k8s/**'
|
||||
- '.forgejo/workflows/cluster-ci.yaml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'k8s/**'
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: docker
|
||||
steps:
|
||||
# === Checkout ===
|
||||
- name: Checkout
|
||||
run: |
|
||||
REPO_URL="${{ gitea.server_url }}/${{ gitea.repository }}.git"
|
||||
CLONE_URL="https://${{ secrets.CI_RUNNER }}:${{ secrets.CI_RUNNER_SECRET }}@${REPO_URL#https://}"
|
||||
git clone --depth 1 "$CLONE_URL" .
|
||||
git fetch origin main
|
||||
git checkout main
|
||||
|
||||
# === Install Tools ===
|
||||
- name: Install Tools
|
||||
run: |
|
||||
unset GITHUB_TOKEN
|
||||
apt-get update && apt-get install -y \
|
||||
yamllint \
|
||||
python3-pip \
|
||||
curl \
|
||||
jq
|
||||
|
||||
# kubeval
|
||||
curl -L https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz | tar xz
|
||||
mv -f kubeval /usr/local/bin/
|
||||
|
||||
# kustomize
|
||||
rm -f kustomize
|
||||
curl -s https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh | bash
|
||||
mv -f kustomize /usr/local/bin/
|
||||
|
||||
# argocd
|
||||
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
|
||||
chmod +x /usr/local/bin/argocd
|
||||
|
||||
# trivy
|
||||
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
|
||||
|
||||
# polaris
|
||||
curl -L https://github.com/FairwindsOps/polaris/releases/latest/download/polaris-linux-amd64 -o /usr/local/bin/polaris
|
||||
chmod +x /usr/local/bin/polaris
|
||||
|
||||
# === YAML Lint ===
|
||||
- name: YAML Lint
|
||||
run: |
|
||||
echo "=== Linting YAML files ==="
|
||||
yamllint k8s/ -c .yamllint.yaml || true
|
||||
|
||||
# === Kubeval - Validate K8s Syntax ===
|
||||
- name: Kubeval - Validate K8s Syntax
|
||||
run: |
|
||||
echo "=== Validating Kubernetes manifests ==="
|
||||
find k8s -name "*.yaml" -o -name "*.yml" | grep -v "\.archive" | while read file; do
|
||||
echo "Validating $file..."
|
||||
kubeval "$file" -d 2>/dev/null || true
|
||||
done
|
||||
|
||||
# === Kustomize Build - All overlays ===
|
||||
- name: Kustomize Build - Infrastructure
|
||||
run: |
|
||||
echo "=== Building k8s/infrastructure/ ==="
|
||||
kustomize build k8s/infrastructure > /tmp/infrastructure.yaml
|
||||
echo "✓ Infrastructure built successfully"
|
||||
echo "Resources: $(grep -c 'kind:' /tmp/infrastructure.yaml)"
|
||||
|
||||
- name: Kustomize Build - Bootstrap
|
||||
run: |
|
||||
echo "=== Building k8s/bootstrap/ ==="
|
||||
kustomize build k8s/bootstrap > /tmp/bootstrap.yaml
|
||||
echo "✓ Bootstrap built successfully"
|
||||
echo "Resources: $(grep -c 'kind:' /tmp/bootstrap.yaml || echo 0)"
|
||||
|
||||
- name: Kustomize Build - Platform
|
||||
run: |
|
||||
echo "=== Building k8s/platform/ ==="
|
||||
kustomize build k8s/platform > /tmp/platform.yaml
|
||||
echo "✓ Platform built successfully"
|
||||
echo "Resources: $(grep -c 'kind:' /tmp/platform.yaml || echo 0)"
|
||||
|
||||
- name: Kustomize Build - Security
|
||||
run: |
|
||||
echo "=== Building k8s/security/ ==="
|
||||
kustomize build k8s/security > /tmp/security.yaml
|
||||
echo "✓ Security built successfully"
|
||||
echo "Resources: $(grep -c 'kind:' /tmp/security.yaml || echo 0)"
|
||||
|
||||
- name: Kustomize Build - Applications
|
||||
run: |
|
||||
echo "=== Building k8s/applications/ ==="
|
||||
kustomize build k8s/applications > /tmp/applications.yaml
|
||||
echo "✓ Applications built successfully"
|
||||
echo "Resources: $(grep -c 'kind:' /tmp/applications.yaml || echo 0)"
|
||||
|
||||
- name: Kustomize Build - Data
|
||||
run: |
|
||||
echo "=== Building k8s/data/ ==="
|
||||
kustomize build k8s/data > /tmp/data.yaml
|
||||
echo "✓ Data built successfully"
|
||||
echo "Resources: $(grep -c 'kind:' /tmp/data.yaml || echo 0)"
|
||||
|
||||
- name: Validate ArgoCD Applications
|
||||
run: |
|
||||
echo "=== Validating ArgoCD Applications ==="
|
||||
kubeval k8s/argocd/apps/*.yaml
|
||||
|
||||
# === Trivy - Scan Dockerfile ===
|
||||
- name: Trivy - Scan Dockerfile
|
||||
run: |
|
||||
if find . -name "Dockerfile" 2>/dev/null | grep -v node_modules | head -1 | grep -q .; then
|
||||
echo "=== Scanning Dockerfiles with Trivy ==="
|
||||
find . -name "Dockerfile" -not -path "*/node_modules/*" -exec trivy config {} \;
|
||||
else
|
||||
echo "No Dockerfiles found"
|
||||
fi
|
||||
|
||||
# === Trivy - Scan Helm Charts ===
|
||||
- name: Trivy - Scan Helm Charts
|
||||
run: |
|
||||
if find k8s -name "Chart.yaml" 2>/dev/null | head -1 | grep -q .; then
|
||||
echo "=== Scanning Helm charts with Trivy ==="
|
||||
find k8s -name "Chart.yaml" -exec dirname {} \; | while read chart; do
|
||||
echo "Scanning $chart..."
|
||||
trivy config "$chart" || true
|
||||
done
|
||||
else
|
||||
echo "No Helm charts found"
|
||||
fi
|
||||
|
||||
# === Polaris - K8s Security Audit ===
|
||||
- name: Polaris - K8s Security Audit
|
||||
run: |
|
||||
echo "=== Running Polaris K8s security audit ==="
|
||||
polaris audit --audit-path /tmp/polaris-audit.json k8s/ || true
|
||||
|
||||
if [ -f /tmp/polaris-audit.json ]; then
|
||||
echo "Security issues found:"
|
||||
jq '.results[] | select(.pass == false)' /tmp/polaris-audit.json || true
|
||||
fi
|
||||
|
||||
# === Check for Secrets in Code ===
|
||||
- name: Check for Secrets in Code
|
||||
run: |
|
||||
echo "=== Scanning for hardcoded secrets ==="
|
||||
# BLOCKING. This step used to only count findings and then exit 0, so a
|
||||
# plaintext deploy key rode through it into a public remote. Two failure
|
||||
# modes fixed: it now fails the build, and it matches key material by
|
||||
# PEM header rather than only `private_key:`-style YAML field names.
|
||||
# Findings are captured into variables and tested for emptiness rather than
|
||||
# branching on grep's exit status: implementations disagree on the rc of a
|
||||
# `-v` filter fed empty input, and a wrong rc here fails open.
|
||||
# NOTE: --include must precede `--`; after `--` grep treats it as a filename
|
||||
# and silently scans nothing.
|
||||
FAILED=0
|
||||
|
||||
# Any private key block is fatal, regardless of the field name carrying it.
|
||||
KEYS=$(grep -rIE --include="*.yaml" --include="*.yml" \
|
||||
-- "-----BEGIN ([A-Z]+ )?PRIVATE KEY-----" k8s/ \
|
||||
| grep -v "\.enc\.yaml" || true)
|
||||
if [ -n "$KEYS" ]; then
|
||||
echo "❌ Unencrypted private key material found:"
|
||||
echo "$KEYS"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# Plaintext values in secret-ish YAML fields. SOPS output is ENC[...],
|
||||
# so encrypted files never trip this.
|
||||
VALS=$(grep -rInE --include="*.yaml" --include="*.yml" \
|
||||
-- "^[[:space:]]*(password|token|apiKey|api_key|sshPrivateKey|client_secret):[[:space:]]*[\"']?[^\"'[:space:]{\$]{8,}" k8s/ \
|
||||
| grep -v "ENC\[" | grep -v "\.enc\.yaml" || true)
|
||||
if [ -n "$VALS" ]; then
|
||||
echo "❌ Plaintext secret value found:"
|
||||
echo "$VALS"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
if [ "$FAILED" -ne 0 ]; then
|
||||
echo "Encrypt with SOPS (see .sops.yaml) — *.enc.yaml files are exempt."
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ No hardcoded secrets found"
|
||||
|
||||
# === Check K8s Security Best Practices ===
|
||||
- name: Check K8s Security Best Practices
|
||||
run: |
|
||||
echo "=== Checking K8s security best practices ==="
|
||||
|
||||
if grep -r "privileged: true" k8s/ --include="*.yaml" --include="*.yml"; then
|
||||
echo "⚠️ Found privileged containers"
|
||||
fi
|
||||
|
||||
if grep -r "hostNetwork: true" k8s/ --include="*.yaml" --include="*.yml"; then
|
||||
echo "⚠️ Found hostNetwork usage"
|
||||
fi
|
||||
|
||||
echo "Checking for missing resource limits..."
|
||||
MISSING=0
|
||||
find k8s -name "*.yaml" -o -name "*.yml" | while read file; do
|
||||
if grep -q "kind: Deployment\|kind: StatefulSet\|kind: DaemonSet" "$file"; then
|
||||
if ! grep -q "resources:" "$file"; then
|
||||
echo "⚠️ $file: Missing resource requests/limits"
|
||||
MISSING=$((MISSING + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# === ArgoCD Sync (main branch only) ===
|
||||
- name: Sync ArgoCD
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
env:
|
||||
ARGOCD_SERVER: ${{ secrets.ARGOCD_SERVER }}
|
||||
ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_AUTH_TOKEN }}
|
||||
run: |
|
||||
echo "=== Syncing homelab-root ==="
|
||||
argocd app sync homelab-root --force
|
||||
argocd app wait homelab-root --timeout 5m
|
||||
|
||||
- name: Check Sync Status
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
env:
|
||||
ARGOCD_SERVER: ${{ secrets.ARGOCD_SERVER }}
|
||||
ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_AUTH_TOKEN }}
|
||||
run: |
|
||||
echo "=== ArgoCD Applications Status ==="
|
||||
argocd app list -o table
|
||||
|
||||
STATUS=$(argocd app get homelab-root -o jsonpath='{.status.syncStatus}')
|
||||
if [ "$STATUS" != "Synced" ]; then
|
||||
echo "❌ Root app sync failed: $STATUS"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Root app synced successfully"
|
||||
|
||||
- name: Health Check
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
env:
|
||||
ARGOCD_SERVER: ${{ secrets.ARGOCD_SERVER }}
|
||||
ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_AUTH_TOKEN }}
|
||||
run: |
|
||||
echo "=== Checking Application Health ==="
|
||||
argocd app get homelab-root -o wide
|
||||
|
||||
# === Summary ===
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "=== CI Pipeline Summary ==="
|
||||
echo "✓ YAML linted"
|
||||
echo "✓ Manifests validated"
|
||||
echo "✓ Kustomizations built"
|
||||
echo "✓ Security scans completed"
|
||||
echo "✓ Secrets check passed"
|
||||
echo "✓ Best practices verified"
|
||||
echo ""
|
||||
echo "✓ All checks passed"
|
||||
@@ -66,6 +66,3 @@ bootstrap-argocd.log
|
||||
# one line here, which is how a plaintext deploy key reached a public remote.
|
||||
k8s/**/*-secret.yaml
|
||||
!k8s/**/*.enc.yaml
|
||||
|
||||
# IAM provisioning scripts contain credential references — never commit
|
||||
scripts/iam/*.py
|
||||
|
||||
@@ -2,5 +2,4 @@ creation_rules:
|
||||
# `secrets?` — singular too. A `seed-repo-secret.yaml` once slipped this regex
|
||||
# and was committed in plaintext to a public remote.
|
||||
- path_regex: k8s/.*secrets?.*\.ya?ml
|
||||
encrypted_regex: ^(data|stringData)$
|
||||
age: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla
|
||||
|
||||
@@ -208,95 +208,3 @@ versions without warning in your own values file.
|
||||
Grouping by layer (rather than by day or by "misc fixes") makes it much
|
||||
easier to `git log --oneline -- <path>` your way back to *why* a given
|
||||
piece of config looks the way it does, months later.
|
||||
|
||||
## Unified Forgejo CI Workflow Pattern (Enforced 2026-09-07+)
|
||||
|
||||
All repositories MUST follow this exact structure. No variations.
|
||||
|
||||
```yaml
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
REGISTRY: <your-registry-hostname>
|
||||
IMAGE: <registry>/<org>/<service-name>
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: [golang|node|rust]
|
||||
steps:
|
||||
- name: Install Node.js for actions runtime
|
||||
run: apt-get update && apt-get install -y nodejs
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Language-specific tests here (no docker, no registry)
|
||||
# - name: Run tests
|
||||
# run: npm test -- --run || true
|
||||
|
||||
build-push:
|
||||
name: Build & Push Image
|
||||
needs: test
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
runs-on: [golang|node|rust]
|
||||
steps:
|
||||
- name: Install Node.js and Docker
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y nodejs docker.io
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get short SHA
|
||||
id: sha
|
||||
run: |
|
||||
SHORT_SHA=$(git rev-parse --short HEAD)
|
||||
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Registry login
|
||||
run: |
|
||||
echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" \
|
||||
--username "${REGISTRY_USER}" --password-stdin
|
||||
env:
|
||||
REGISTRY_USER: ${{ secrets.FORGEJO_REGISTRY_USER }}
|
||||
REGISTRY_TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
||||
|
||||
- name: Build Docker image
|
||||
run: |
|
||||
docker build --no-cache \
|
||||
-t "${IMAGE}:${{ steps.sha.outputs.short_sha }}" \
|
||||
-t "${IMAGE}:latest" \
|
||||
.
|
||||
|
||||
- name: Push Docker image
|
||||
run: |
|
||||
docker push "${IMAGE}:${{ steps.sha.outputs.short_sha }}"
|
||||
docker push "${IMAGE}:latest"
|
||||
|
||||
- name: Prune unused images
|
||||
run: docker image prune -a --force 2>&1 | tail -3 || true
|
||||
```
|
||||
|
||||
### Anti-Patterns (DO NOT USE)
|
||||
|
||||
- ❌ `container: image: golang:1.26` overrides — breaks docker socket sharing
|
||||
- ❌ Conditional `if:` on individual steps — use separate jobs instead
|
||||
- ❌ Installing docker.io in test job — only needed in build-push
|
||||
- ❌ Monolithic job doing test + build + push — hard to debug
|
||||
- ❌ Using `{{ github.sha }}` for image tag — use short commit SHA for readability
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **PR to feature branch** → test job runs, build-push skipped, nothing pushed
|
||||
2. **Push to main** → test runs, build-push runs after test passes, image pushed
|
||||
3. Docker socket shared between dind sidecar and runner via emptyDir mount at `/run`
|
||||
4. `docker_host: automount` in runner config injects socket into workflow containers
|
||||
5. Secrets (FORGEJO_REGISTRY_USER, TOKEN) set in Forgejo repo settings, NOT in git
|
||||
|
||||
@@ -42,86 +42,6 @@ All logs + metrics centralized in Grafana for debugging
|
||||
- **Secrets at rest** — Vault + encrypted etcd; credentials never in logs or ConfigMaps
|
||||
- **Infrastructure-as-code** — Every service deployed via Helmfile; one `helmfile apply` recovers from total failure
|
||||
|
||||
## ArgoCD — GitOps Deployment Flow
|
||||
|
||||
**ArgoCD** pulls infrastructure changes from git and syncs the cluster automatically.
|
||||
No manual `kubectl apply` — push to git, ArgoCD detects the change, and deploys within ~3 minutes.
|
||||
|
||||
```
|
||||
Developer pushes to git
|
||||
↓
|
||||
ArgoCD detects change (every 3 min or webhook)
|
||||
↓
|
||||
Syncs manifests to cluster
|
||||
↓
|
||||
Workloads reconcile automatically
|
||||
```
|
||||
|
||||
Applications are deployed in waves (numbered 00, 10, 20, 30, ...) to respect dependencies —
|
||||
storage deploys before databases, databases before applications.
|
||||
|
||||
### Tracked Git Repositories
|
||||
|
||||
ArgoCD monitors these repos for changes:
|
||||
|
||||
| Repository | Purpose |
|
||||
|------------|----------|
|
||||
| `https://github.com/Riotpiaole/riotpiao.homelab.com` | Main infrastructure repo (all manifests in `k8s/argocd/apps/`) |
|
||||
| `https://forgejo.riotpiao.com/rock/*` | Any `rock/*` repo in in-cluster Forgejo (apps + configs) |
|
||||
| `https://github.com/Riotpiaole/Poimen-*` | External Poimen services (memory, workflows) |
|
||||
|
||||
To deploy a new application: create a git repo, add an Application manifest to the homelab repo's
|
||||
`k8s/argocd/apps/`, commit + push, and ArgoCD syncs within 3 minutes.
|
||||
|
||||
## Management Planes — Talos vs Kubernetes
|
||||
|
||||
This cluster has **two separate management planes**, each with different workflows:
|
||||
|
||||
| Plane | What it manages | Workflow | Tool |
|
||||
|-------|-----------------|----------|------|
|
||||
| **Talos (OS)** | Node configuration, kernel params, networking, CoreDNS, machine state | Edit `terraform/` → `terraform apply` → `make apply-cp` | `terraform` + `talosctl` |
|
||||
| **Kubernetes (workloads)** | All pods, services, deployments, ingresses, databases | Edit `k8s/argocd/apps/` → `git push` → ArgoCD syncs | `git` + ArgoCD |
|
||||
|
||||
**Critical distinction:**
|
||||
- **Kubernetes resources** (`k8s/**`) flow through **git → ArgoCD** — never use `kubectl apply`
|
||||
- **Talos machine config** (`terraform/**`) uses **local `terraform apply`** (sanctioned exception — CI can't hold node credentials)
|
||||
|
||||
Example: To add a CoreDNS hostname rewrite, you edit `terraform/files/coredns/Corefile`, then:
|
||||
```bash
|
||||
cd terraform && terraform apply -var-file=terraform.tfvars.local
|
||||
cd .. && make apply-cp # talosctl apply-config to all 3 control planes
|
||||
```
|
||||
|
||||
But to add a new Kubernetes Deployment or update an Ingress, you only `git push` — **never `kubectl apply`**.
|
||||
|
||||
### CoreDNS ConfigMap Ownership — Critical
|
||||
|
||||
⚠️ **Warning:** The `coredns` ConfigMap in `kube-system` namespace is **owned by Talos**, not ArgoCD or kubectl.
|
||||
It is rendered from `terraform/files/coredns/Corefile` into Talos's machine config at bootstrap time.
|
||||
|
||||
**Do not `kubectl apply` or `kubectl edit` this ConfigMap directly.** Doing so transfers field ownership to kubectl's
|
||||
client-side-apply mechanism, and Talos's inline-manifest controller will silently no-op on every future reconcile
|
||||
(server-side-apply conflict, no error surfaced).
|
||||
|
||||
**To update CoreDNS (e.g., add a hostname rewrite):**
|
||||
1. Edit `terraform/files/coredns/Corefile`
|
||||
2. Commit + push
|
||||
3. Run `cd terraform && terraform apply -var-file=terraform.tfvars.local`
|
||||
4. Run `make apply-cp` to push config to all control planes
|
||||
5. CoreDNS picks up changes via its `reload` plugin — no pod restart needed
|
||||
|
||||
**If you accidentally edited the ConfigMap directly and broke Talos's ownership:**
|
||||
```bash
|
||||
kubectl delete configmap coredns -n kube-system
|
||||
# Wait ~30s for Talos's k8s.ManifestApplyController to recreate it
|
||||
kubectl get configmap coredns -n kube-system -w
|
||||
```
|
||||
|
||||
Or as a stopgap, apply the correct content yourself:
|
||||
```bash
|
||||
kubectl apply --server-side -f <(terraform output coredns_config)
|
||||
```
|
||||
|
||||
## Quick Start — Deploying the Cluster
|
||||
|
||||
### 1. Bootstrap Talos Nodes
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
# ComfyUI — GPU-accelerated image generation on worker-1.
|
||||
# Uses 1x V100 32GB (sm70). Freed by scaling ornith 2→1.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: comfyui
|
||||
namespace: comfyui
|
||||
labels:
|
||||
app: comfyui
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: comfyui
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: comfyui
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: worker-1
|
||||
runtimeClassName: nvidia
|
||||
# k8s Service named 'comfyui' injects COMFYUI_PORT=tcp://... into pod env,
|
||||
# which clobbers ai-dock's own COMFYUI_PORT variable (expects a port number).
|
||||
# Disable service link injection to avoid the collision.
|
||||
enableServiceLinks: false
|
||||
containers:
|
||||
- name: comfyui
|
||||
image: ghcr.io/ai-dock/comfyui:v2-cuda-12.1.1-base-22.04
|
||||
ports:
|
||||
- containerPort: 8188
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: NVIDIA_VISIBLE_DEVICES
|
||||
value: "all"
|
||||
resources:
|
||||
requests:
|
||||
cpu: "4"
|
||||
memory: 8Gi
|
||||
nvidia.com/gpu: "1"
|
||||
limits:
|
||||
cpu: "8"
|
||||
memory: 16Gi
|
||||
nvidia.com/gpu: "1"
|
||||
volumeMounts:
|
||||
- mountPath: /workspace/ComfyUI/models
|
||||
name: models
|
||||
- mountPath: /workspace/ComfyUI/output
|
||||
name: output
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8188
|
||||
periodSeconds: 10
|
||||
initialDelaySeconds: 30
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8188
|
||||
failureThreshold: 120
|
||||
periodSeconds: 10
|
||||
volumes:
|
||||
- name: models
|
||||
persistentVolumeClaim:
|
||||
claimName: comfyui-models
|
||||
- name: output
|
||||
emptyDir: {}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: comfyui-models
|
||||
namespace: comfyui
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 50Gi
|
||||
@@ -1,33 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: comfyui
|
||||
namespace: comfyui
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
||||
# WebSocket support for ComfyUI's live preview
|
||||
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
|
||||
nginx.ingress.kubernetes.io/upstream-hash-by: "$remote_addr"
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- secretName: comfyui-tls
|
||||
hosts:
|
||||
- comfyui.riotpiao.com
|
||||
rules:
|
||||
- host: comfyui.riotpiao.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: comfyui
|
||||
port:
|
||||
number: 80
|
||||
@@ -1,7 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: comfyui
|
||||
namespace: comfyui
|
||||
labels:
|
||||
app: comfyui
|
||||
spec:
|
||||
selector:
|
||||
app: comfyui
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8188
|
||||
protocol: TCP
|
||||
@@ -1,107 +0,0 @@
|
||||
# Gotify — Push Notifications + Email Relay
|
||||
|
||||
Self-hosted notification server with SMTP email forwarding sidecar.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Forgejo webhook ──POST──→ Gotify API (:80/message)
|
||||
│
|
||||
┌─────────┼─────────┐
|
||||
▼ ▼
|
||||
Push notification SMTP emailer sidecar
|
||||
(mobile/desktop) (polls → sends email)
|
||||
```
|
||||
|
||||
## Setup (one-time, after first deploy)
|
||||
|
||||
### 1. Encrypt secrets before committing
|
||||
|
||||
```bash
|
||||
# Edit secrets.yaml with real values first, then:
|
||||
sops -e -i k8s/apps/gotify/secrets.yaml
|
||||
```
|
||||
|
||||
### 2. Create Gotify app + client tokens
|
||||
|
||||
1. Login to `https://gotify.riotpiao.com` with admin creds
|
||||
2. **Applications** → Create `forgejo` → copy **app token**
|
||||
3. **Clients** → Create `smtp-emailer` → copy **client token**
|
||||
4. Update `gotify-tokens` secret:
|
||||
```bash
|
||||
kubectl -n notifications create secret generic gotify-tokens \
|
||||
--from-literal=app-token=<APP_TOKEN> \
|
||||
--from-literal=client-token=<CLIENT_TOKEN> \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
```
|
||||
|
||||
### 3. Configure Forgejo webhook
|
||||
|
||||
In each Forgejo repo → **Settings** → **Webhooks** → **Add Webhook** → **Gotify**:
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Target URL | `http://gotify.notifications.svc.cluster.local/message` |
|
||||
| Token | The **app token** from step 2 |
|
||||
| Events | Pull Request (Created, Merged, Closed) |
|
||||
|
||||
Or via API:
|
||||
```bash
|
||||
FORGEJO_TOKEN="<your-pat>"
|
||||
APP_TOKEN="<gotify-app-token>"
|
||||
|
||||
curl -s -X POST "https://forgejo.riotpiao.com/api/v1/repos/rock/homelab/hooks" \
|
||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"type": "gotify",
|
||||
"active": true,
|
||||
"config": {
|
||||
"content_type": "json",
|
||||
"url": "http://gotify.notifications.svc.cluster.local/message?token='"$APP_TOKEN"'"
|
||||
},
|
||||
"events": ["pull_request", "pull_request_assign", "pull_request_review"],
|
||||
"authorization_header": ""
|
||||
}'
|
||||
```
|
||||
|
||||
### 4. Add CoreDNS rewrite (if accessing via public hostname)
|
||||
|
||||
Only needed if Cloudflare Tunnel is used for gotify.riotpiao.com:
|
||||
|
||||
```
|
||||
# terraform/files/coredns/Corefile — add rewrite:
|
||||
rewrite name gotify.riotpiao.com ingress-nginx-controller.ingress-nginx.svc.cluster.local
|
||||
```
|
||||
|
||||
Then: `cd terraform && terraform apply && cd .. && make apply-cp`
|
||||
|
||||
### 5. SMTP providers
|
||||
|
||||
| Provider | Host | Port | Notes |
|
||||
|----------|------|------|-------|
|
||||
| Gmail | smtp.gmail.com | 587 | Use App Password (2FA required) |
|
||||
| Resend | smtp.resend.com | 587 | Free 100 emails/day |
|
||||
| Sendgrid | smtp.sendgrid.net | 587 | Free 100 emails/day |
|
||||
| Mailgun | smtp.mailgun.org | 587 | Free 5000/month |
|
||||
|
||||
## Notification priority levels
|
||||
|
||||
| Priority | Meaning | Email forwarded? |
|
||||
|----------|---------|-----------------|
|
||||
| 0-4 | Low (info) | No (below MIN_PRIORITY=5) |
|
||||
| 5-7 | Normal (PR created) | Yes |
|
||||
| 8-10 | High (PR merged, failures) | Yes |
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# Test push notification
|
||||
APP_TOKEN="<app-token>"
|
||||
curl -X POST "https://gotify.riotpiao.com/message?token=$APP_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"title":"Test","message":"Hello from homelab","priority":5}'
|
||||
|
||||
# Check email sidecar logs
|
||||
kubectl -n notifications logs deployment/gotify -c smtp-emailer --tail=20
|
||||
```
|
||||
@@ -1,35 +0,0 @@
|
||||
# CNPG Postgres for Gotify. Lightweight — 2 instances, 2Gi storage.
|
||||
# CNPG generates secret `gotify-db-app` + service `gotify-db-rw` in ns notifications.
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: gotify-db
|
||||
namespace: notifications
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
|
||||
spec:
|
||||
instances: 3
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16.2
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: gotify
|
||||
owner: app
|
||||
encoding: UTF8
|
||||
localeCollate: C
|
||||
localeCType: C
|
||||
enableSuperuserAccess: false
|
||||
resources:
|
||||
requests: { memory: "256Mi", cpu: "100m" }
|
||||
limits: { memory: "512Mi", cpu: "500m" }
|
||||
storage:
|
||||
size: 2Gi
|
||||
storageClass: longhorn-cnpg
|
||||
monitoring:
|
||||
enablePodMonitor: true
|
||||
affinity:
|
||||
podAntiAffinityType: preferred
|
||||
topologyKey: kubernetes.io/hostname
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
@@ -1,204 +0,0 @@
|
||||
# Gotify — self-hosted push notification server + SMTP email relay.
|
||||
# Forgejo webhooks → Gotify → push notifications + email forwarding.
|
||||
# Runs on control plane (no GPU needed), lightweight.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: gotify
|
||||
namespace: notifications
|
||||
labels:
|
||||
app: gotify
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: gotify
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: gotify
|
||||
spec:
|
||||
containers:
|
||||
# --- Gotify server ---
|
||||
- name: gotify
|
||||
image: ghcr.io/gotify/server:2.6.1
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
export GOTIFY_DATABASE_DIALECT=postgres
|
||||
export GOTIFY_DATABASE_CONNECTION="host=gotify-db-rw.notifications port=5432 user=${DB_USER} password=${DB_PASS} dbname=gotify sslmode=disable"
|
||||
exec /app/gotify-app
|
||||
ports:
|
||||
- containerPort: 80
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: GOTIFY_DEFAULTUSER_NAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gotify-admin
|
||||
key: username
|
||||
- name: GOTIFY_DEFAULTUSER_PASS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gotify-admin
|
||||
key: password
|
||||
- name: DB_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gotify-db-app
|
||||
key: username
|
||||
- name: DB_PASS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gotify-db-app
|
||||
key: password
|
||||
- name: GOTIFY_SERVER_PORT
|
||||
value: "80"
|
||||
- name: GOTIFY_SERVER_KEEPALIVEPERIODSECONDS
|
||||
value: "0"
|
||||
- name: TZ
|
||||
value: Asia/Tokyo
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 80
|
||||
periodSeconds: 30
|
||||
initialDelaySeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 80
|
||||
periodSeconds: 10
|
||||
initialDelaySeconds: 5
|
||||
|
||||
# --- SMTP emailer sidecar ---
|
||||
# Watches Gotify WebSocket stream, forwards messages as email.
|
||||
# https://github.com/eternal-flame-AD/gotify-broadcast
|
||||
- name: smtp-emailer
|
||||
image: ghcr.io/gotify/server:2.6.1
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
# Wait for Gotify to be ready
|
||||
until wget -qO- http://localhost:80/health >/dev/null 2>&1; do
|
||||
echo "Waiting for Gotify..."
|
||||
sleep 2
|
||||
done
|
||||
echo "Gotify is ready, starting email relay..."
|
||||
|
||||
# Poll Gotify messages and forward via SMTP using msmtp
|
||||
# Install msmtp for lightweight SMTP sending
|
||||
apk add --no-cache msmtp curl jq
|
||||
|
||||
# Configure msmtp
|
||||
cat > /tmp/msmtprc <<MSMTP
|
||||
defaults
|
||||
auth on
|
||||
tls on
|
||||
tls_trust_file /etc/ssl/certs/ca-certificates.crt
|
||||
logfile /tmp/msmtp.log
|
||||
|
||||
account default
|
||||
host ${SMTP_HOST}
|
||||
port ${SMTP_PORT}
|
||||
from ${SMTP_FROM}
|
||||
user ${SMTP_USER}
|
||||
password ${SMTP_PASS}
|
||||
MSMTP
|
||||
|
||||
chmod 600 /tmp/msmtprc
|
||||
|
||||
# Track last seen message ID
|
||||
LAST_ID=0
|
||||
|
||||
while true; do
|
||||
# Fetch messages since last ID
|
||||
MESSAGES=$(curl -s -H "X-Gotify-Key: ${GOTIFY_CLIENT_TOKEN}" \
|
||||
"http://localhost:80/message?since=${LAST_ID}&limit=10" 2>/dev/null)
|
||||
|
||||
if [ -n "$MESSAGES" ]; then
|
||||
echo "$MESSAGES" | jq -r '.messages[]? | @base64' | while read -r MSG; do
|
||||
DECODED=$(echo "$MSG" | base64 -d)
|
||||
ID=$(echo "$DECODED" | jq -r '.id')
|
||||
TITLE=$(echo "$DECODED" | jq -r '.title // "Notification"')
|
||||
BODY=$(echo "$DECODED" | jq -r '.message // ""')
|
||||
PRIORITY=$(echo "$DECODED" | jq -r '.priority // 5')
|
||||
APP=$(echo "$DECODED" | jq -r '.appid // 0')
|
||||
DATE=$(echo "$DECODED" | jq -r '.date // ""')
|
||||
|
||||
# Only forward messages with priority >= configured threshold
|
||||
if [ "$PRIORITY" -ge "${MIN_PRIORITY:-0}" ]; then
|
||||
printf "Subject: [Gotify] %s\nFrom: %s\nTo: %s\nContent-Type: text/plain; charset=UTF-8\n\n%s\n\n---\nPriority: %s\nDate: %s" \
|
||||
"$TITLE" "$SMTP_FROM" "$NOTIFY_EMAIL" "$BODY" "$PRIORITY" "$DATE" | \
|
||||
msmtp -C /tmp/msmtprc "$NOTIFY_EMAIL" && \
|
||||
echo "Email sent for message $ID: $TITLE" || \
|
||||
echo "Failed to send email for message $ID"
|
||||
fi
|
||||
|
||||
# Update last seen ID
|
||||
if [ "$ID" -gt "$LAST_ID" ]; then
|
||||
LAST_ID=$ID
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
sleep ${POLL_INTERVAL:-30}
|
||||
done
|
||||
env:
|
||||
- name: GOTIFY_CLIENT_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gotify-tokens
|
||||
key: client-token
|
||||
- name: SMTP_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gotify-smtp
|
||||
key: host
|
||||
- name: SMTP_PORT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gotify-smtp
|
||||
key: port
|
||||
- name: SMTP_FROM
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gotify-smtp
|
||||
key: from
|
||||
- name: SMTP_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gotify-smtp
|
||||
key: user
|
||||
- name: SMTP_PASS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gotify-smtp
|
||||
key: password
|
||||
- name: NOTIFY_EMAIL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gotify-smtp
|
||||
key: notify-email
|
||||
- name: MIN_PRIORITY
|
||||
value: "5"
|
||||
- name: POLL_INTERVAL
|
||||
value: "15"
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 64Mi
|
||||
# No volumes — Postgres handles persistence
|
||||
@@ -1,26 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: gotify
|
||||
namespace: notifications
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
|
||||
# WebSocket support for Gotify client connections
|
||||
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: gotify.riotpiao.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: gotify
|
||||
port:
|
||||
number: 80
|
||||
@@ -1,8 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- db.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
@@ -1,6 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: notifications
|
||||
labels:
|
||||
kubernetes.io/metadata.name: notifications
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: gotify
|
||||
namespace: notifications
|
||||
labels:
|
||||
app: gotify
|
||||
spec:
|
||||
selector:
|
||||
app: gotify
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: immich-config
|
||||
data:
|
||||
DB_HOSTNAME: "immich-db-rw"
|
||||
DB_DATABASE_NAME: "immich"
|
||||
# Only pgvector is installed (see db.yaml) - no vectorchord extension image
|
||||
# exists for pg18 in CNPG's catalog yet. Explicit instead of relying on
|
||||
# auto-detect's vectorchord-first preference order.
|
||||
DB_VECTOR_EXTENSION: "pgvector"
|
||||
REDIS_HOSTNAME: "immich-redis"
|
||||
IMMICH_MACHINE_LEARNING_URL: "http://immich-machine-learning:3003"
|
||||
TZ: "America/Los_Angeles"
|
||||
@@ -1,58 +0,0 @@
|
||||
# Dedicated CNPG Postgres for Immich. Same recipe as paperless-db/authentik-db
|
||||
# (2 instances, default longhorn storage class) except the operand is
|
||||
# PostgreSQL 18, not 16.2 - the official CNPG pgvector extension image
|
||||
# (ghcr.io/cloudnative-pg/pgvector) is only published for pg18, no pg16 tags
|
||||
# exist in that registry. Immich itself supports pg18 fine (immich-app's own
|
||||
# postgres image already ships 18-vectorchord builds).
|
||||
#
|
||||
# pgvector loaded via CNPG's ImageVolume extension mechanism (CNPG 1.27+,
|
||||
# k8s ImageVolume feature - both present here: operator is 1.30.0, cluster is
|
||||
# v1.36.1). No shared_preload_libraries needed - pgvector doesn't require
|
||||
# preload, just CREATE EXTENSION, which immich-server issues itself at
|
||||
# startup. Distro/pg-major must match between the operand image and the
|
||||
# extension image (both "18"+"trixie" here) - CNPG's own compatibility rule.
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: immich-db
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
|
||||
spec:
|
||||
instances: 3
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:18-minimal-trixie
|
||||
postgresql:
|
||||
extensions:
|
||||
- name: pgvector
|
||||
image:
|
||||
reference: ghcr.io/cloudnative-pg/pgvector:0.8.1-18-trixie
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: immich
|
||||
owner: app
|
||||
encoding: UTF8
|
||||
localeCollate: C
|
||||
localeCType: C
|
||||
# CREATE EXTENSION vector requires superuser (pgvector's control file
|
||||
# isn't marked trusted) and the "app" owner role isn't one
|
||||
# (enableSuperuserAccess: false, repo convention) - postInitApplicationSQL
|
||||
# runs as superuser during initdb, before the app ever connects. Only
|
||||
# fires on a fresh bootstrap; the live cluster already had this run
|
||||
# manually once (kubectl exec ... psql -U postgres -c 'CREATE EXTENSION').
|
||||
postInitApplicationSQL:
|
||||
- "CREATE EXTENSION IF NOT EXISTS vector;"
|
||||
- "CREATE EXTENSION IF NOT EXISTS cube;"
|
||||
- "CREATE EXTENSION IF NOT EXISTS earthdistance;"
|
||||
enableSuperuserAccess: false
|
||||
resources:
|
||||
requests: { memory: "512Mi", cpu: "250m" }
|
||||
limits: { memory: "2Gi", cpu: "1" }
|
||||
storage:
|
||||
size: 20Gi
|
||||
storageClass: longhorn
|
||||
affinity:
|
||||
podAntiAffinityType: preferred
|
||||
topologyKey: kubernetes.io/hostname
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
@@ -1,100 +0,0 @@
|
||||
# immich-server: pinned to talos-cp-3, same reasoning as paperless
|
||||
# (deployment.yaml comment there) - immich-media is a ReadWriteOnce Longhorn
|
||||
# volume with a single replica physically on that node's disk (shared with
|
||||
# paperless-media on the same 4TB HDD). Recreate strategy for the same
|
||||
# reason: two pods can't both attach an RWO volume.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: immich-server
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: immich-server
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: immich-server
|
||||
spec:
|
||||
serviceAccountName: immich
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: talos-cp-3
|
||||
containers:
|
||||
- name: immich-server
|
||||
image: ghcr.io/immich-app/immich-server:release
|
||||
ports:
|
||||
- containerPort: 2283
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: immich-config
|
||||
env:
|
||||
- name: DB_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: immich-db-app
|
||||
key: username
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: immich-db-app
|
||||
key: password
|
||||
# Composed by k8s/infra/iam's provisioning script (system-config
|
||||
# JSON, oauth section) - see immich-oidc Secret.
|
||||
- name: IMMICH_CONFIG_FILE
|
||||
value: /config/immich.json
|
||||
resources:
|
||||
requests: { cpu: "500m", memory: "1Gi" }
|
||||
limits: { cpu: "2", memory: "4Gi" }
|
||||
volumeMounts:
|
||||
- name: media
|
||||
mountPath: /usr/src/app/upload
|
||||
- name: oidc-config
|
||||
mountPath: /config
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: media
|
||||
persistentVolumeClaim:
|
||||
claimName: immich-media
|
||||
- name: oidc-config
|
||||
secret:
|
||||
secretName: immich-oidc
|
||||
items:
|
||||
- key: config.json
|
||||
path: immich.json
|
||||
---
|
||||
# CPU-only for now - the cluster's one GPU node (worker-1) is already
|
||||
# dedicated to llm-serving predictors. Not node-pinned: its cache PVC is on
|
||||
# the default 3-replica pool, not the single-disk cp-3 HDD.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: immich-machine-learning
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: immich-machine-learning
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: immich-machine-learning
|
||||
spec:
|
||||
serviceAccountName: immich
|
||||
containers:
|
||||
- name: immich-machine-learning
|
||||
image: ghcr.io/immich-app/immich-machine-learning:release
|
||||
ports:
|
||||
- containerPort: 3003
|
||||
resources:
|
||||
requests: { cpu: "500m", memory: "1Gi" }
|
||||
limits: { cpu: "2", memory: "4Gi" }
|
||||
volumeMounts:
|
||||
- name: ml-cache
|
||||
mountPath: /cache
|
||||
volumes:
|
||||
- name: ml-cache
|
||||
persistentVolumeClaim:
|
||||
claimName: immich-ml-cache
|
||||
@@ -1,24 +0,0 @@
|
||||
# Direct nginx ingress, same reasoning as paperless: large uploads (photos/
|
||||
# videos) and long-lived operations (video transcode, big batch uploads) need
|
||||
# proxy-body-size/timeouts raised past nginx's defaults.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: immich
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: img.riotpiao.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: immich-server
|
||||
port:
|
||||
number: 2283
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: immich
|
||||
resources:
|
||||
- db.yaml
|
||||
- pvc.yaml
|
||||
- configmap.yaml
|
||||
- redis.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
- rbac.yaml
|
||||
# immich-oidc Secret written by the PostSync provisioning Job in
|
||||
# k8s/infra/iam (same as paperless-oidc) - not duplicated here.
|
||||
@@ -1,38 +0,0 @@
|
||||
# Two volumes:
|
||||
#
|
||||
# - media: original photos/videos + generated thumbnails/encoded videos.
|
||||
# Shares the cp-3 USB HDD with paperless-media, same StorageClass/disk tag,
|
||||
# single replica (single disk, no redundancy possible - same tradeoff
|
||||
# paperless already accepts). Sized 1400Gi, not 2000Gi: the disk's real
|
||||
# usable capacity (~3724GiB, formatting overhead) minus paperless-media's
|
||||
# 2000Gi and ~231GiB of other apps' default-class replicas that Longhorn
|
||||
# placed here anyway (disk tags only pull matching volumes in, they don't
|
||||
# exclude non-matching ones when the untagged pool elsewhere is full) only
|
||||
# leaves ~1493Gi of real scheduling headroom right now.
|
||||
# - ml-cache: downloaded ML model weights for immich-machine-learning
|
||||
# (face detection / CLIP embeddings). Small, disposable (re-downloads on
|
||||
# loss), but persisted so a pod restart doesn't re-pull multi-GB models -
|
||||
# default 3-replica pool, not node-pinned.
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: immich-media
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn-paperless-media
|
||||
resources:
|
||||
requests:
|
||||
storage: 1400Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: immich-ml-cache
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
@@ -1,43 +0,0 @@
|
||||
# Scoped operator access for immich-admins: restart/config-edit rights on
|
||||
# just this service's own resources, nothing CNPG-managed (immich-db-*) or
|
||||
# provisioning-managed (immich-oidc). Same pattern as
|
||||
# k8s/apps/paperless/rbac.yaml. Inert until kube-apiserver's OIDC wiring
|
||||
# lands (--oidc-groups-claim=groups, --oidc-groups-prefix=oidc:).
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: immich
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: immich-operator
|
||||
rules:
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["deployments"]
|
||||
resourceNames: ["immich-server", "immich-machine-learning"]
|
||||
verbs: ["get", "list", "watch", "update", "patch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
resourceNames: ["immich-config"]
|
||||
verbs: ["get", "list", "watch", "update", "patch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["immich-oidc"]
|
||||
verbs: ["get", "list", "watch", "update", "patch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: immich-admins-binding
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: "oidc:immich-admins"
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
- kind: ServiceAccount
|
||||
name: immich
|
||||
namespace: immich
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: immich-operator
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
@@ -1,37 +0,0 @@
|
||||
# Job queue broker for immich-server. No PVC: queue state is disposable - a
|
||||
# lost queue on restart just re-triggers the affected background jobs
|
||||
# (thumbnail generation, ML jobs, etc.), no photo data loss since originals
|
||||
# live on immich-media.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: immich-redis
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: immich-redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: immich-redis
|
||||
spec:
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
resources:
|
||||
requests: { cpu: "50m", memory: "64Mi" }
|
||||
limits: { cpu: "250m", memory: "256Mi" }
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: immich-redis
|
||||
spec:
|
||||
selector:
|
||||
app: immich-redis
|
||||
ports:
|
||||
- port: 6379
|
||||
targetPort: 6379
|
||||
@@ -1,21 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: immich-server
|
||||
spec:
|
||||
selector:
|
||||
app: immich-server
|
||||
ports:
|
||||
- port: 2283
|
||||
targetPort: 2283
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: immich-machine-learning
|
||||
spec:
|
||||
selector:
|
||||
app: immich-machine-learning
|
||||
ports:
|
||||
- port: 3003
|
||||
targetPort: 3003
|
||||
@@ -45,14 +45,6 @@ spec:
|
||||
volumeMounts:
|
||||
- mountPath: /mnt/models
|
||||
name: models
|
||||
podMetadata:
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/path: "/metrics"
|
||||
labels:
|
||||
app.kubernetes.io/name: llm-embeddings
|
||||
app.kubernetes.io/part-of: llm-serving
|
||||
maxReplicas: 1
|
||||
minReplicas: 1
|
||||
nodeSelector:
|
||||
|
||||
@@ -14,7 +14,5 @@ resources:
|
||||
- ornith.yaml
|
||||
- reasoning.yaml
|
||||
- reranker.yaml
|
||||
- qwen-cpu.yaml
|
||||
- networkpolicy.yaml
|
||||
# No namespace transformer: every file sets its own, and the transformer would
|
||||
# rewrite metadata.namespace on anything cross-namespace added later.
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
# NetworkPolicy for LLM inference engines (llm-serving namespace).
|
||||
#
|
||||
# These pods have NO auth — vLLM, Ollama, and TEI accept any request.
|
||||
# All access MUST go through the api-gateway, which validates JWTs and
|
||||
# injects identity headers (X-Forwarded-User, X-Auth-Verified).
|
||||
#
|
||||
# Replaces the hand-applied llm-serving-default-deny policy that used
|
||||
# `llm-client: "true"` pod label as a selector — any pod in any namespace
|
||||
# could self-grant access by adding that label, which defeats the purpose.
|
||||
#
|
||||
# This policy restricts ingress to:
|
||||
# 1. api namespace (gateway) — the sole entry point for inference
|
||||
# 2. monitoring namespace — Prometheus scraping vLLM/TEI /metrics
|
||||
# 3. intra-namespace — pod-to-pod (future: multi-replica comms)
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: llm-serving-ingress
|
||||
namespace: llm-serving
|
||||
labels:
|
||||
app.kubernetes.io/part-of: llm-serving
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/part-of: llm-serving
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
# Allow from api-gateway (namespace: api)
|
||||
# Gateway proxies /v1/chat/completions, /v1/embeddings, /v1/rerank
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: api
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080 # vLLM, Ollama HTTP
|
||||
- protocol: TCP
|
||||
port: 80 # KServe predictor services
|
||||
- protocol: TCP
|
||||
port: 8000 # vLLM direct (some configs)
|
||||
- protocol: TCP
|
||||
port: 11434 # Ollama native port
|
||||
# Allow Prometheus scraping from monitoring namespace
|
||||
# vLLM: :8080/metrics, TEI: :9000/metrics
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: monitoring
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
- protocol: TCP
|
||||
port: 9000
|
||||
# Allow intra-namespace (pod-to-pod within llm-serving)
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/part-of: llm-serving
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
@@ -33,8 +33,12 @@ spec:
|
||||
|
||||
ollama pull ornith:35b
|
||||
|
||||
ollama pull qwen2.5:3b-instruct
|
||||
|
||||
ollama run ornith:35b "ok" >/dev/null 2>&1 || true
|
||||
|
||||
ollama run qwen2.5:3b-instruct "ok" >/dev/null 2>&1 || true
|
||||
|
||||
wait $SERVE_PID
|
||||
|
||||
'
|
||||
@@ -50,7 +54,7 @@ spec:
|
||||
- name: OLLAMA_NUM_PARALLEL
|
||||
value: '1'
|
||||
- name: OLLAMA_MAX_LOADED_MODELS
|
||||
value: '1'
|
||||
value: '2'
|
||||
image: ollama/ollama:0.32.9@sha256:1685741456770df6e3cceb2a945a5f75e020f658d1701509668d6f4688f1dd3f
|
||||
name: kserve-container
|
||||
ports:
|
||||
@@ -61,7 +65,8 @@ spec:
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- ollama ps 2>/dev/null | grep -q ornith
|
||||
- ollama ps 2>/dev/null | grep -q ornith && ollama ps 2>/dev/null |
|
||||
grep -q qwen2.5
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
limits:
|
||||
@@ -77,26 +82,22 @@ spec:
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- ollama ps 2>/dev/null | grep -q ornith
|
||||
- ollama ps 2>/dev/null | grep -q ornith && ollama ps 2>/dev/null |
|
||||
grep -q qwen2.5
|
||||
failureThreshold: 120
|
||||
periodSeconds: 15
|
||||
volumeMounts:
|
||||
- mountPath: /mnt/models
|
||||
name: models
|
||||
podMetadata:
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/path: "/metrics"
|
||||
labels:
|
||||
app.kubernetes.io/name: llm-ornith
|
||||
app.kubernetes.io/part-of: llm-serving
|
||||
deploymentStrategy:
|
||||
type: Recreate
|
||||
# 1 replica -- ornith:35b only. qwen2.5:3b moved to CPU on cp-2.
|
||||
# Frees 1 GPU for ComfyUI.
|
||||
maxReplicas: 1
|
||||
minReplicas: 1
|
||||
# 2 replicas -- each its own GPU, each loading both ornith:35b and
|
||||
# qwen2.5:3b-instruct -- so 2 concurrent implementer-style calls each
|
||||
# get an independent instance instead of contending on one, at the
|
||||
# cost of judge/qwen traffic still sharing whichever replica an
|
||||
# implementer call also lands on.
|
||||
maxReplicas: 2
|
||||
minReplicas: 2
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: worker-1
|
||||
runtimeClassName: nvidia
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
# qwen2.5:3b-instruct on CPU (talos-cp-2, 144GB RAM, 24 cores).
|
||||
# Moved off GPU to free a V100 for ComfyUI. Latency ~10x slower
|
||||
# than GPU but sufficient for lightweight tasks (summarization,
|
||||
# classification, quick answers).
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: qwen-cpu
|
||||
namespace: llm-serving
|
||||
labels:
|
||||
app: qwen-cpu
|
||||
app.kubernetes.io/name: qwen-cpu
|
||||
app.kubernetes.io/part-of: llm-serving
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: qwen-cpu
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: qwen-cpu
|
||||
app.kubernetes.io/name: qwen-cpu
|
||||
app.kubernetes.io/part-of: llm-serving
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: talos-cp-2
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: ollama
|
||||
image: ollama/ollama:0.32.9@sha256:1685741456770df6e3cceb2a945a5f75e020f658d1701509668d6f4688f1dd3f
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
ollama serve &
|
||||
SERVE_PID=$!
|
||||
until ollama list >/dev/null 2>&1; do sleep 2; done
|
||||
ollama pull qwen2.5:3b-instruct
|
||||
ollama run qwen2.5:3b-instruct "ok" >/dev/null 2>&1 || true
|
||||
wait $SERVE_PID
|
||||
env:
|
||||
- name: OLLAMA_HOST
|
||||
value: "0.0.0.0:8080"
|
||||
- name: OLLAMA_MODELS
|
||||
value: /root/.ollama/models
|
||||
- name: OLLAMA_CONTEXT_LENGTH
|
||||
value: "32768"
|
||||
- name: OLLAMA_KEEP_ALIVE
|
||||
value: "-1"
|
||||
- name: OLLAMA_MAX_LOADED_MODELS
|
||||
value: "1"
|
||||
- name: OLLAMA_NUM_PARALLEL
|
||||
value: "2"
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["/bin/sh", "-c", "ollama ps 2>/dev/null | grep -q qwen2.5"]
|
||||
periodSeconds: 10
|
||||
startupProbe:
|
||||
exec:
|
||||
command: ["/bin/sh", "-c", "ollama ps 2>/dev/null | grep -q qwen2.5"]
|
||||
failureThreshold: 60
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
cpu: "4"
|
||||
memory: 4Gi
|
||||
limits:
|
||||
cpu: "8"
|
||||
memory: 8Gi
|
||||
volumeMounts:
|
||||
- mountPath: /root/.ollama
|
||||
name: ollama-data
|
||||
volumes:
|
||||
- name: ollama-data
|
||||
persistentVolumeClaim:
|
||||
claimName: qwen-cpu-data
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: qwen-cpu
|
||||
namespace: llm-serving
|
||||
labels:
|
||||
app: qwen-cpu
|
||||
app.kubernetes.io/part-of: llm-serving
|
||||
spec:
|
||||
selector:
|
||||
app: qwen-cpu
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
protocol: TCP
|
||||
---
|
||||
# Small PVC for qwen2.5:3b model weights (~1.9GB).
|
||||
# Separate from llm-models PVC which is pinned to worker-1.
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: qwen-cpu-data
|
||||
namespace: llm-serving
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
@@ -104,14 +104,6 @@ spec:
|
||||
name: models
|
||||
- mountPath: /dev/shm
|
||||
name: shm
|
||||
podMetadata:
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/path: "/metrics"
|
||||
labels:
|
||||
app.kubernetes.io/name: llm-reasoning
|
||||
app.kubernetes.io/part-of: llm-serving
|
||||
deploymentStrategy:
|
||||
type: Recreate
|
||||
maxReplicas: 1
|
||||
|
||||
@@ -45,14 +45,6 @@ spec:
|
||||
volumeMounts:
|
||||
- mountPath: /mnt/models
|
||||
name: models
|
||||
podMetadata:
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/path: "/metrics"
|
||||
labels:
|
||||
app.kubernetes.io/name: llm-reranker
|
||||
app.kubernetes.io/part-of: llm-serving
|
||||
maxReplicas: 1
|
||||
minReplicas: 1
|
||||
nodeSelector:
|
||||
|
||||
@@ -13,7 +13,6 @@ spec:
|
||||
labels:
|
||||
app: management-service
|
||||
spec:
|
||||
serviceAccountName: kmsvc
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace: sqs
|
||||
replicaCount: 3
|
||||
|
||||
image:
|
||||
repository: forgejo.riotpiao.com/rock/kmsvc-manage
|
||||
repository: ghcr.io/riotpiaole/kmsvc-management-service
|
||||
tag: latest
|
||||
pullPolicy: Always
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
apiVersion: v2
|
||||
name: memory-queues
|
||||
description: Kafka queues (DLQ) for Poimen Memory service (Phase 6.6)
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "1.0"
|
||||
@@ -1,20 +0,0 @@
|
||||
{{- range .Values.queues }}
|
||||
---
|
||||
apiVersion: kmsvc.io/v1alpha1
|
||||
kind: Queue
|
||||
metadata:
|
||||
name: {{ .name }}
|
||||
namespace: {{ $.Values.namespace }}
|
||||
labels:
|
||||
app: memory-service
|
||||
queue: dlq
|
||||
spec:
|
||||
name: {{ .name }}
|
||||
description: {{ .description }}
|
||||
partitions: {{ .partitions }}
|
||||
replicationFactor: {{ .replicationFactor }}
|
||||
config:
|
||||
retention.ms: "{{ .config.retention.ms }}"
|
||||
message.retention.seconds: "{{ .config.message.retention.seconds }}"
|
||||
visibility.timeout.seconds: "{{ .config.visibility.timeout.seconds }}"
|
||||
{{- end }}
|
||||
@@ -1,25 +0,0 @@
|
||||
# Poimen Memory Service Kafka Queues (kmsvc)
|
||||
# Phase 6.6: DLQ topics for webhook + metrics failures
|
||||
|
||||
queues:
|
||||
# DLQ for extraction, webhook, and agent failures
|
||||
- name: poimen-memory-dlq
|
||||
description: "DLQ for extraction, webhook, and agent failures"
|
||||
partitions: 3
|
||||
replicationFactor: 1
|
||||
config:
|
||||
retention.ms: "1209600000" # 14 days
|
||||
message.retention.seconds: "1209600"
|
||||
visibility.timeout.seconds: "300"
|
||||
|
||||
# DLQ for metrics persistence failures
|
||||
- name: poimen-memory-metric-dlq
|
||||
description: "DLQ for metrics persistence failures"
|
||||
partitions: 3
|
||||
replicationFactor: 1
|
||||
config:
|
||||
retention.ms: "1209600000" # 14 days
|
||||
message.retention.seconds: "1209600"
|
||||
visibility.timeout.seconds: "300"
|
||||
|
||||
namespace: sqs
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace: sqs
|
||||
|
||||
image:
|
||||
repository: forgejo.riotpiao.com/rock/kmsvc-manage
|
||||
repository: ghcr.io/riotpiaole/kmsvc-management-service
|
||||
tag: latest
|
||||
pullPolicy: Always
|
||||
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
# Overrides paperless-ngx's own paperless/adapter.py at the same import path
|
||||
# (mounted via subPath in deployment.yaml) - settings.py hardcodes
|
||||
# SOCIALACCOUNT_ADAPTER = "paperless.adapter.CustomSocialAccountAdapter", so
|
||||
# no Django setting needs to change, just the file content underneath it.
|
||||
#
|
||||
# Stock CustomSocialAccountAdapter.populate_user() is a stub ("kept in case
|
||||
# global default permissions are implemented in the future" - they aren't),
|
||||
# so every OIDC signup lands with zero permissions and 403s on every API
|
||||
# endpoint. This adds the actual mapping: Authentik's "permissions" claim
|
||||
# (via the permissions scope, requested in PAPERLESS_SOCIALACCOUNT_PROVIDERS,
|
||||
# computed server-side from group membership by authentik-provision.py) ->
|
||||
# "paperless:write" or "*" (homelab-admins) grants is_staff+is_superuser,
|
||||
# same convention already used for MinIO's policy claim and Grafana's
|
||||
# role_attribute_path. Checking the permission string rather than a literal
|
||||
# group name decouples "what grants access" from which group happens to
|
||||
# hold it - same pattern applies to every other service's Role/RoleBinding
|
||||
# in k8s/infra/rbac/.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: paperless-adapter
|
||||
data:
|
||||
adapter.py: |
|
||||
from urllib.parse import quote
|
||||
|
||||
from allauth.account.adapter import DefaultAccountAdapter
|
||||
from allauth.core import context
|
||||
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
||||
from django.conf import settings
|
||||
from django.forms import ValidationError
|
||||
from django.urls import reverse
|
||||
|
||||
REQUIRED_PERMISSIONS = {"paperless:write", "*"}
|
||||
|
||||
|
||||
class CustomAccountAdapter(DefaultAccountAdapter):
|
||||
def is_open_for_signup(self, request):
|
||||
allow_signups = super().is_open_for_signup(request)
|
||||
return getattr(settings, "ACCOUNT_ALLOW_SIGNUPS", allow_signups)
|
||||
|
||||
def pre_authenticate(self, request, **credentials):
|
||||
if settings.DISABLE_REGULAR_LOGIN:
|
||||
raise ValidationError("Regular login is disabled")
|
||||
return super().pre_authenticate(request, **credentials)
|
||||
|
||||
def is_safe_url(self, url):
|
||||
from django.utils.http import url_has_allowed_host_and_scheme
|
||||
|
||||
allowed_hosts = {context.request.get_host()} | set(settings.ALLOWED_HOSTS)
|
||||
if "*" in allowed_hosts:
|
||||
allowed_hosts.remove("*")
|
||||
allowed_hosts.add(context.request.get_host())
|
||||
return url_has_allowed_host_and_scheme(url, allowed_hosts=allowed_hosts)
|
||||
return url_has_allowed_host_and_scheme(url, allowed_hosts=allowed_hosts)
|
||||
|
||||
def get_reset_password_from_key_url(self, key):
|
||||
if settings.PAPERLESS_URL is None:
|
||||
return super().get_reset_password_from_key_url(key)
|
||||
path = reverse(
|
||||
"account_reset_password_from_key",
|
||||
kwargs={"uidb36": "UID", "key": "KEY"},
|
||||
)
|
||||
path = path.replace("UID-KEY", quote(key))
|
||||
return settings.PAPERLESS_URL + path
|
||||
|
||||
|
||||
class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
|
||||
def is_open_for_signup(self, request, sociallogin):
|
||||
allow_signups = super().is_open_for_signup(request, sociallogin)
|
||||
return getattr(settings, "SOCIALACCOUNT_ALLOW_SIGNUPS", allow_signups)
|
||||
|
||||
def get_connect_redirect_url(self, request, socialaccount):
|
||||
return reverse("base")
|
||||
|
||||
def populate_user(self, request, sociallogin, data):
|
||||
user = super().populate_user(request, sociallogin, data)
|
||||
perms = set(sociallogin.account.extra_data.get("permissions") or [])
|
||||
if perms & REQUIRED_PERMISSIONS:
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
return user
|
||||
|
||||
def save_user(self, request, sociallogin, form=None):
|
||||
# populate_user() sets the flags on the in-memory user, but
|
||||
# allauth's default save_user() re-derives is_staff from
|
||||
# ACCOUNT_DEFAULT_HTTP_PROTOCOL-independent defaults and can
|
||||
# overwrite them on save - re-apply after super().save_user()
|
||||
# persists the row, matching the permissions check above exactly.
|
||||
user = super().save_user(request, sociallogin, form)
|
||||
perms = set(sociallogin.account.extra_data.get("permissions") or [])
|
||||
if perms & REQUIRED_PERMISSIONS and not (user.is_staff and user.is_superuser):
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.save(update_fields=["is_staff", "is_superuser"])
|
||||
return user
|
||||
@@ -48,7 +48,7 @@ spec:
|
||||
mountPath: /backup
|
||||
containers:
|
||||
- name: mc-mirror
|
||||
image: quay.io/minio/mc:latest
|
||||
image: minio/mc:latest
|
||||
env:
|
||||
- name: ACCESS_KEY
|
||||
valueFrom:
|
||||
|
||||
@@ -15,8 +15,3 @@ data:
|
||||
# env vars can't be split across a ConfigMap + Secret for the same key, so
|
||||
# this whole value is sourced from the Secret in deployment.yaml instead.
|
||||
PAPERLESS_APPS: "allauth.socialaccount.providers.openid_connect"
|
||||
# Authentik already verifies identity via OIDC - a second email-confirmation
|
||||
# step has no SMTP configured to send it anyway, and paperless-ngx doesn't
|
||||
# wire up allauth's confirm-email view, so signup 500s with NoReverseMatch
|
||||
# on 'account_confirm_email' without this.
|
||||
PAPERLESS_ACCOUNT_EMAIL_VERIFICATION: "none"
|
||||
|
||||
@@ -24,18 +24,11 @@ spec:
|
||||
labels:
|
||||
app: paperless
|
||||
spec:
|
||||
# Kubernetes injects legacy Docker-links env vars for every Service in
|
||||
# this namespace (<SVC>_SERVICE_HOST, <SVC>_PORT, ...). The Service here
|
||||
# is named "paperless", so that becomes PAPERLESS_PORT=tcp://<ip>:8000 -
|
||||
# paperless-ngx's own entrypoint reads PAPERLESS_PORT for gunicorn's
|
||||
# bind address, collides, and gunicorn crash-loops on "not a valid port
|
||||
# number". Disable the injection instead of renaming the Service.
|
||||
enableServiceLinks: false
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: talos-cp-3
|
||||
containers:
|
||||
- name: paperless
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:2.20.15
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:2.13
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
envFrom:
|
||||
@@ -82,13 +75,6 @@ spec:
|
||||
mountPath: /usr/src/paperless/data
|
||||
- name: consume
|
||||
mountPath: /usr/src/paperless/consume
|
||||
# Overrides paperless-ngx's own adapter.py in place - settings.py
|
||||
# hardcodes the import path, so no Django setting changes, just
|
||||
# the file content underneath it (see adapter-configmap.yaml).
|
||||
- name: adapter
|
||||
mountPath: /usr/src/paperless/src/paperless/adapter.py
|
||||
subPath: adapter.py
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: media
|
||||
persistentVolumeClaim:
|
||||
@@ -98,6 +84,3 @@ spec:
|
||||
claimName: paperless-data
|
||||
- name: consume
|
||||
emptyDir: {}
|
||||
- name: adapter
|
||||
configMap:
|
||||
name: paperless-adapter
|
||||
|
||||
@@ -9,9 +9,6 @@ resources:
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
- backup-cronjob.yaml
|
||||
- adapter-configmap.yaml
|
||||
- rbac.yaml
|
||||
- paperless-ai.yaml
|
||||
# postgres: paperless-db CNPG Cluster, deployed by k8s/infra/databases (wave 2,
|
||||
# before this app at wave 8) - not duplicated here. Same for the paperless-oidc
|
||||
# and paperless-minio-creds Secrets, written by PostSync provisioning Jobs in
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
# Secret paperless-ai-config managed via SOPS (argocd/secrets/paperless-ai-secrets.enc.yaml)
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: paperless-ai
|
||||
namespace: paperless
|
||||
labels:
|
||||
app.kubernetes.io/name: paperless-ai
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: paperless-ai
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: paperless-ai
|
||||
spec:
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: paperless-ai
|
||||
image: clusterzx/paperless-ai:latest
|
||||
env:
|
||||
# Paperless-ngx connection
|
||||
- name: PAPERLESS_API_URL
|
||||
value: "http://paperless.paperless.svc.cluster.local:8000"
|
||||
- name: PAPERLESS_API_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: paperless-ai-config
|
||||
key: PAPERLESS_API_TOKEN
|
||||
- name: PAPERLESS_USERNAME
|
||||
value: "admin"
|
||||
# LLM API — local gateway, no auth required (phase 3 not built yet)
|
||||
- name: AI_PROVIDER
|
||||
value: "custom"
|
||||
- name: CUSTOM_BASE_URL
|
||||
value: "http://api-gateway.api.svc.cluster.local:8080/v1"
|
||||
- name: CUSTOM_API_KEY
|
||||
value: "not-required"
|
||||
- name: CUSTOM_MODEL
|
||||
value: "reasoning"
|
||||
# Behavior
|
||||
- name: SCAN_INTERVAL
|
||||
value: "300"
|
||||
- name: PROCESS_PREDEFINED_DOCUMENTS
|
||||
value: "no"
|
||||
- name: ADD_AI_TAG
|
||||
value: "yes"
|
||||
- name: AI_TAG_NAME
|
||||
value: "ai-processed"
|
||||
- name: USE_PROMPT_TAGS
|
||||
value: "yes"
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 2Gi
|
||||
@@ -1,11 +1,9 @@
|
||||
# Two volumes, deliberately separate storage classes:
|
||||
#
|
||||
# - media: the actual documents (originals + OCR'd archive PDFs + thumbnails).
|
||||
# Lives on the cp-3 USB HDD, single replica (see
|
||||
# k8s/infra/longhorn/longhorn-paperless-storageclass.yaml). Shares the disk
|
||||
# with Immich's immich-media PVC (k8s/apps/immich/pvc.yaml, 2000Gi) - photo
|
||||
# libraries grow much faster than scanned documents, so paperless gets the
|
||||
# smaller 500Gi share.
|
||||
# Grows to multi-TB, lives on the cp-3 USB HDD, single replica (see
|
||||
# k8s/infra/longhorn/longhorn-paperless-storageclass.yaml). Sized 3500Gi to
|
||||
# leave headroom on the 4TB disk rather than claiming it to 100%.
|
||||
# - data: the SQLite classification model + search index. Small (low GB),
|
||||
# frequently rewritten, and disposable (rebuilds from the DB + media on
|
||||
# next consume) - stays on the default 3-replica pool instead of the
|
||||
@@ -20,7 +18,7 @@ spec:
|
||||
storageClassName: longhorn-paperless-media
|
||||
resources:
|
||||
requests:
|
||||
storage: 500Gi
|
||||
storage: 3500Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# Scoped operator access for paperless-admins: restart/config-edit rights on
|
||||
# just this service's own resources, nothing CNPG-managed (paperless-db-*)
|
||||
# or provisioning-managed (paperless-oidc, paperless-minio-creds). Inert
|
||||
# until kube-apiserver's OIDC wiring lands (--oidc-groups-claim=groups,
|
||||
# --oidc-groups-prefix=oidc:) - subject name below assumes that prefix.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: paperless-operator
|
||||
rules:
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["deployments"]
|
||||
resourceNames: ["paperless"]
|
||||
verbs: ["get", "list", "watch", "update", "patch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
resourceNames: ["paperless-config"]
|
||||
verbs: ["get", "list", "watch", "update", "patch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["paperless-secrets"]
|
||||
verbs: ["get", "list", "watch", "update", "patch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: paperless-admins-binding
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: "oidc:paperless-admins"
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: paperless-operator
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
@@ -1,144 +0,0 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: secretrotations.homelab.riotpiao.com
|
||||
spec:
|
||||
group: homelab.riotpiao.com
|
||||
names:
|
||||
kind: SecretRotation
|
||||
plural: secretrotations
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
type: object
|
||||
required:
|
||||
- provider
|
||||
- rotationInterval
|
||||
properties:
|
||||
# External system: authentik | forgejo | minio | vault
|
||||
provider:
|
||||
type: string
|
||||
enum: [authentik, forgejo, minio, vault]
|
||||
|
||||
# How often to rotate (hours)
|
||||
rotationInterval:
|
||||
type: integer
|
||||
minimum: 24
|
||||
|
||||
# Application ID in external system
|
||||
appId:
|
||||
type: string
|
||||
|
||||
# k8s Secret to update (name, namespace, key)
|
||||
secretRef:
|
||||
type: object
|
||||
required: [name, namespace]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
description: "Secret key to update (e.g., MINIO_IDENTITY_OPENID_CLIENT_SECRET)"
|
||||
|
||||
# Path to git file that holds the secret (for .enc.yaml files)
|
||||
gitPath:
|
||||
type: string
|
||||
description: "Path in homelab repo to .enc.yaml file"
|
||||
|
||||
# Ansible template values to substitute
|
||||
templateValues:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
|
||||
status:
|
||||
type: object
|
||||
properties:
|
||||
lastRotationTime:
|
||||
type: string
|
||||
format: date-time
|
||||
nextRotationTime:
|
||||
type: string
|
||||
format: date-time
|
||||
lastRotationStatus:
|
||||
type: string
|
||||
enum: [Success, Failed, Pending]
|
||||
lastRotationError:
|
||||
type: string
|
||||
lastCommitHash:
|
||||
type: string
|
||||
|
||||
---
|
||||
# Example usage:
|
||||
apiVersion: homelab.riotpiao.com/v1
|
||||
kind: SecretRotation
|
||||
metadata:
|
||||
name: minio-oidc
|
||||
namespace: secret-rotation
|
||||
spec:
|
||||
provider: authentik
|
||||
rotationInterval: 2160 # 90 days in hours
|
||||
appId: minio
|
||||
secretRef:
|
||||
name: minio-oidc
|
||||
namespace: storage
|
||||
key: MINIO_IDENTITY_OPENID_CLIENT_SECRET
|
||||
gitPath: k8s/argocd/secrets/minio-oidc.enc.yaml
|
||||
|
||||
---
|
||||
apiVersion: homelab.riotpiao.com/v1
|
||||
kind: SecretRotation
|
||||
metadata:
|
||||
name: portfolio-agent-oidc
|
||||
namespace: secret-rotation
|
||||
spec:
|
||||
provider: authentik
|
||||
rotationInterval: 2160
|
||||
appId: portfolio-agent
|
||||
secretRef:
|
||||
name: portfolio-agent-oidc
|
||||
namespace: portfolio
|
||||
key: CLIENT_SECRET
|
||||
gitPath: k8s/argocd/secrets/portfolio-agent-oidc.enc.yaml
|
||||
|
||||
---
|
||||
apiVersion: homelab.riotpiao.com/v1
|
||||
kind: SecretRotation
|
||||
metadata:
|
||||
name: forgejo-registry-token
|
||||
namespace: secret-rotation
|
||||
spec:
|
||||
provider: forgejo
|
||||
rotationInterval: 2160
|
||||
appId: rock/riotpiao.com
|
||||
secretRef:
|
||||
name: forgejo-registry-secret
|
||||
namespace: kube-system
|
||||
key: REGISTRY_TOKEN
|
||||
gitPath: k8s/argocd/secrets/forgejo-registry-secret.enc.yaml
|
||||
|
||||
---
|
||||
apiVersion: homelab.riotpiao.com/v1
|
||||
kind: SecretRotation
|
||||
metadata:
|
||||
name: minio-root-credentials
|
||||
namespace: secret-rotation
|
||||
spec:
|
||||
provider: minio
|
||||
rotationInterval: 4320 # 180 days in hours
|
||||
appId: root
|
||||
secretRef:
|
||||
name: minio-creds
|
||||
namespace: storage
|
||||
gitPath: k8s/argocd/secrets/minio-secrets.enc.yaml
|
||||
@@ -1,92 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: secret-rotation-controller
|
||||
namespace: secret-rotation
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: secret-rotation-controller
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: secret-rotation-controller
|
||||
spec:
|
||||
serviceAccountName: secret-rotation-controller
|
||||
containers:
|
||||
- name: controller
|
||||
image: secret-rotation-controller:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
# SOPS reads age key from this file
|
||||
- name: SOPS_AGE_KEY_FILE
|
||||
value: /etc/sops/age/private-key.txt
|
||||
|
||||
# Vault auth (token in projected volume)
|
||||
- name: VAULT_ADDR
|
||||
value: http://vault.vault.svc.cluster.local:8200
|
||||
- name: VAULT_TOKEN_FILE
|
||||
value: /var/run/secrets/vault/token
|
||||
|
||||
# Authentik
|
||||
- name: AUTHENTIK_URL
|
||||
value: http://authentik-server.iam.svc.cluster.local
|
||||
- name: AUTHENTIK_BOOTSTRAP_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-bootstrap
|
||||
key: token
|
||||
|
||||
# Git
|
||||
- name: GIT_REPO
|
||||
value: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- name: GIT_AUTHOR_EMAIL
|
||||
value: [email protected]
|
||||
- name: GIT_AUTHOR_NAME
|
||||
value: Secret Rotation Controller
|
||||
- name: FORGEJO_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-registry-secret
|
||||
key: REGISTRY_TOKEN
|
||||
|
||||
volumeMounts:
|
||||
# Age key from ExternalSecret (synced from Vault)
|
||||
- name: age-key
|
||||
mountPath: /etc/sops/age
|
||||
readOnly: true
|
||||
|
||||
# Vault auth token (projected)
|
||||
- name: vault-token
|
||||
mountPath: /var/run/secrets/vault
|
||||
readOnly: true
|
||||
|
||||
# Temp working dir
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
|
||||
volumes:
|
||||
- name: age-key
|
||||
secret:
|
||||
secretName: sops-age-key
|
||||
defaultMode: 0400
|
||||
|
||||
- name: vault-token
|
||||
projected:
|
||||
sources:
|
||||
- serviceAccountToken:
|
||||
path: token
|
||||
audience: vault
|
||||
expirationSeconds: 3600
|
||||
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
@@ -1,15 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: secret-rotation
|
||||
|
||||
resources:
|
||||
- rbac.yaml
|
||||
- crd.yaml
|
||||
- external-secret.yaml
|
||||
- deployment.yaml
|
||||
|
||||
commonLabels:
|
||||
app.kubernetes.io/name: secret-rotation-controller
|
||||
app.kubernetes.io/component: automation
|
||||
managed-by: argocd
|
||||
@@ -1,53 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: secret-rotation-controller
|
||||
namespace: secret-rotation
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: secret-rotation-controller
|
||||
rules:
|
||||
# Read SecretRotation CRDs
|
||||
- apiGroups: ["homelab.riotpiao.com"]
|
||||
resources: ["secretrotations"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
|
||||
# Update status
|
||||
- apiGroups: ["homelab.riotpiao.com"]
|
||||
resources: ["secretrotations/status"]
|
||||
verbs: ["get", "patch", "update"]
|
||||
|
||||
# Read k8s secrets that will be rotated
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get", "list"]
|
||||
|
||||
# For recording events
|
||||
- apiGroups: [""]
|
||||
resources: ["events"]
|
||||
verbs: ["create", "patch"]
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: secret-rotation-controller
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: secret-rotation-controller
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: secret-rotation-controller
|
||||
namespace: secret-rotation
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: secret-rotation
|
||||
labels:
|
||||
kubernetes.io/metadata.name: secret-rotation
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/argocd/projects
|
||||
destination:
|
||||
|
||||
@@ -17,7 +17,7 @@ spec:
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
# ksops decrypts every *.enc.yaml here at kustomize-build time (repo-server
|
||||
# runs `kustomize build --enable-alpha-plugins --enable-exec`). Replaces the
|
||||
|
||||
@@ -21,7 +21,7 @@ spec:
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/bootstrap/cert-manager/cert-manager-values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
@@ -93,7 +93,7 @@ spec:
|
||||
project: homelab
|
||||
revisionHistoryLimit: 3
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
# A real kustomization.yaml (resources: the 3 issuer/CA files) renders these
|
||||
# deterministically. The previous directory.include with bare filenames
|
||||
@@ -127,7 +127,7 @@ spec:
|
||||
project: homelab
|
||||
revisionHistoryLimit: 3
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/bootstrap/ingress
|
||||
destination:
|
||||
@@ -138,75 +138,3 @@ spec:
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: cluster-maintenance
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "0"
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/infra/cluster-maintenance
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: kube-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: kyverno
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "0"
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://kyverno.github.io/kyverno/
|
||||
chart: kyverno
|
||||
targetRevision: "1.14.0"
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/bootstrap/kyverno/kyverno-values.yaml
|
||||
sources:
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: kyverno
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: kyverno-policies
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "0"
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/bootstrap/kyverno
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: kyverno
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
# ArgoCD Image Updater - auto-updates Application images from registry
|
||||
# Watches forgejo.riotpiao.com for new image tags and updates Applications
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: argocd-image-updater
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
spec:
|
||||
project: homelab
|
||||
revisionHistoryLimit: 3
|
||||
sources:
|
||||
- repoURL: https://argoproj.github.io/argo-helm
|
||||
chart: argocd-image-updater
|
||||
targetRevision: "0.11.2"
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/infra/argocd-image-updater/values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: argocd
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=false
|
||||
@@ -1,55 +0,0 @@
|
||||
# Tekton Pipelines v0.68.0
|
||||
#
|
||||
# Install method: vendored release.yaml in k8s/infra/tekton/
|
||||
# downloaded from https://storage.googleapis.com/tekton-releases/pipeline/previous/v0.68.0/release.yaml
|
||||
#
|
||||
# To upgrade:
|
||||
# 1. Download new release.yaml from https://github.com/tektoncd/pipeline/releases
|
||||
# 2. Replace k8s/infra/tekton/release.yaml
|
||||
# 3. Commit and push — ArgoCD syncs automatically
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: tekton-pipelines
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: tekton-pipelines
|
||||
app.kubernetes.io/part-of: homelab-infra
|
||||
wave: "06"
|
||||
spec:
|
||||
project: homelab
|
||||
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/infra/tekton
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: tekton-pipelines
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
retry:
|
||||
limit: 5
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
|
||||
ignoreDifferences:
|
||||
- group: admissionregistration.k8s.io
|
||||
kind: ValidatingWebhookConfiguration
|
||||
jsonPointers:
|
||||
- /webhooks/0/clientConfig/caBundle
|
||||
- /webhooks
|
||||
- group: admissionregistration.k8s.io
|
||||
kind: MutatingWebhookConfiguration
|
||||
jsonPointers:
|
||||
- /webhooks/0/clientConfig/caBundle
|
||||
- /webhooks
|
||||
@@ -1,32 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: secret-rotation
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: secret-rotation
|
||||
spec:
|
||||
project: homelab
|
||||
|
||||
sources:
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
path: k8s/apps/secret-rotation-controller
|
||||
targetRevision: main
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: secret-rotation
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- RespectIgnoreDifferences=true
|
||||
retry:
|
||||
limit: 5
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
@@ -17,7 +17,7 @@ spec:
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/infra/minio/minio-operator-values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
@@ -41,7 +41,7 @@ metadata:
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/infra/minio
|
||||
destination:
|
||||
@@ -66,7 +66,7 @@ metadata:
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/infra/longhorn
|
||||
destination:
|
||||
@@ -102,7 +102,7 @@ spec:
|
||||
skipCrds: true
|
||||
valueFiles:
|
||||
- $values/k8s/infra/monitoring/prometheus-values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
@@ -152,7 +152,7 @@ metadata:
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/infra/monitoring/crds
|
||||
destination:
|
||||
@@ -183,7 +183,7 @@ metadata:
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/infra/monitoring
|
||||
destination:
|
||||
@@ -213,7 +213,7 @@ spec:
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/infra/monitoring/blackbox-exporter-values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
@@ -223,29 +223,3 @@ spec:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
---
|
||||
# Distributed tracing: Tempo + OpenTelemetry Collector.
|
||||
# Receives traces from instrumented services, stores in local volume (72h retention).
|
||||
# Grafana datasource auto-configured, service graph + latency dashboards included.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: tracing
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/infra/tracing
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: tracing
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
|
||||
@@ -19,7 +19,7 @@ spec:
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/infra/logging/loki-values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
@@ -53,7 +53,7 @@ spec:
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/infra/logging/grafana-values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
@@ -87,7 +87,7 @@ spec:
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/infra/logging/promtail-values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
|
||||
@@ -17,7 +17,7 @@ spec:
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/infra/iam/vault-values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
@@ -46,7 +46,7 @@ spec:
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/infra/iam/authentik-values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
@@ -68,7 +68,7 @@ metadata:
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/infra/iam
|
||||
destination:
|
||||
@@ -109,7 +109,7 @@ spec:
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/bootstrap/phase3-forgejo/forgejo-values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
@@ -152,17 +152,10 @@ metadata:
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "3"
|
||||
argocd-image-updater.argoproj.io/image-list: runner=forgejo.riotpiao.com/rock/forgejo-runner-golang
|
||||
argocd-image-updater.argoproj.io/runner.update-strategy: newest-build
|
||||
argocd-image-updater.argoproj.io/runner.allow-tags: regexp:^[0-9a-f]{7}$|^latest$|^v[0-9]+$
|
||||
argocd-image-updater.argoproj.io/runner.helm.image-name: runner.image.repository
|
||||
argocd-image-updater.argoproj.io/runner.helm.image-tag: runner.image.tag
|
||||
argocd-image-updater.argoproj.io/write-back-method: git
|
||||
argocd-image-updater.argoproj.io/git-branch: main
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/infra/forgejo-runner
|
||||
destination:
|
||||
@@ -180,17 +173,10 @@ metadata:
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "3"
|
||||
argocd-image-updater.argoproj.io/image-list: runner=forgejo.riotpiao.com/rock/forgejo-runner-node
|
||||
argocd-image-updater.argoproj.io/runner.update-strategy: newest-build
|
||||
argocd-image-updater.argoproj.io/runner.allow-tags: regexp:^[0-9a-f]{7}$|^latest$|^v[0-9]+$
|
||||
argocd-image-updater.argoproj.io/runner.helm.image-name: runner.image.repository
|
||||
argocd-image-updater.argoproj.io/runner.helm.image-tag: runner.image.tag
|
||||
argocd-image-updater.argoproj.io/write-back-method: git
|
||||
argocd-image-updater.argoproj.io/git-branch: main
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/infra/forgejo-runner
|
||||
helm:
|
||||
@@ -211,17 +197,10 @@ metadata:
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "3"
|
||||
argocd-image-updater.argoproj.io/image-list: runner=forgejo.riotpiao.com/rock/forgejo-runner-rust
|
||||
argocd-image-updater.argoproj.io/runner.update-strategy: newest-build
|
||||
argocd-image-updater.argoproj.io/runner.allow-tags: regexp:^[0-9a-f]{7}$|^latest$|^v[0-9]+$
|
||||
argocd-image-updater.argoproj.io/runner.helm.image-name: runner.image.repository
|
||||
argocd-image-updater.argoproj.io/runner.helm.image-tag: runner.image.tag
|
||||
argocd-image-updater.argoproj.io/write-back-method: git
|
||||
argocd-image-updater.argoproj.io/git-branch: main
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/infra/forgejo-runner
|
||||
helm:
|
||||
|
||||
@@ -14,7 +14,7 @@ metadata:
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/infra/databases
|
||||
destination:
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: memory-queues
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "7"
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/messaging/memory-queues
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: sqs
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
@@ -1,6 +1,6 @@
|
||||
# Wave 5 — Kafka (Strimzi operator + cluster CR), Redis infrastructure.
|
||||
# Strimzi/Redis are public Helm charts; kafka-cluster is a local chart.
|
||||
# queue-crd and management-service are managed by kmsvc-root (kmsvc-manage.git).
|
||||
# Wave 5 — Kafka (Strimzi operator + cluster CR), Redis, and the SQS-like
|
||||
# queue services. Strimzi/Redis are public Helm charts; kafka-cluster/queue-crd/
|
||||
# management-service are local charts (rendered from their own Chart.yaml).
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
@@ -68,7 +68,7 @@ metadata:
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/messaging/kafka-cluster
|
||||
destination:
|
||||
@@ -78,5 +78,45 @@ spec:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
# queue-crd and management-service moved to kmsvc-manage.git repo
|
||||
# Managed by kmsvc-root Application
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: queue-crd
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "6"
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/messaging/queue-crd
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: sqs
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: management-service
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "7"
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/messaging/management-service
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: sqs
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
|
||||
@@ -36,19 +36,14 @@ metadata:
|
||||
app.kubernetes.io/component: gateway
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "7"
|
||||
# ArgoCD Image Updater - auto-update on new image push
|
||||
argocd-image-updater.argoproj.io/image-list: gw=forgejo.riotpiao.com/rock/api-gateway
|
||||
argocd-image-updater.argoproj.io/gw.update-strategy: digest
|
||||
argocd-image-updater.argoproj.io/gw.allow-tags: regexp:^latest$
|
||||
argocd-image-updater.argoproj.io/write-back-method: argocd
|
||||
spec:
|
||||
project: homelab
|
||||
revisionHistoryLimit: 3
|
||||
sources:
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab-frontend.git
|
||||
- repoURL: https://forgejo.riotpiao.com/rock/homelab-frontend.git
|
||||
targetRevision: main
|
||||
path: k8s
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/api
|
||||
destination:
|
||||
|
||||
@@ -20,7 +20,7 @@ spec:
|
||||
project: homelab
|
||||
revisionHistoryLimit: 3
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/llm-serving
|
||||
destination:
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: comfyui
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: comfyui
|
||||
app.kubernetes.io/component: image-generation
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "8"
|
||||
spec:
|
||||
project: homelab
|
||||
revisionHistoryLimit: 3
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/comfyui
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: comfyui
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
retry:
|
||||
limit: 5
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
@@ -1,32 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: gotify
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: gotify
|
||||
app.kubernetes.io/component: notifications
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "8"
|
||||
spec:
|
||||
project: homelab
|
||||
revisionHistoryLimit: 3
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/gotify
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: notifications
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
retry:
|
||||
limit: 5
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
@@ -19,10 +19,10 @@ spec:
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/apps/temporal/temporal-values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/temporal
|
||||
destination:
|
||||
@@ -51,7 +51,7 @@ spec:
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/apps/portainer/portainer-values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
@@ -74,7 +74,7 @@ metadata:
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/cloudflared
|
||||
destination:
|
||||
@@ -97,7 +97,7 @@ metadata:
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/agent-pod
|
||||
destination:
|
||||
@@ -130,7 +130,7 @@ metadata:
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/sms
|
||||
destination:
|
||||
@@ -157,7 +157,7 @@ metadata:
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/paperless
|
||||
destination:
|
||||
@@ -170,38 +170,6 @@ spec:
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
---
|
||||
# Photo/video backup. Self-contained (unlike paperless, its CNPG Postgres
|
||||
# lives here too, not in k8s/infra/databases) - CreateNamespace=true creates
|
||||
# the namespace before any manifest in this Application applies, including
|
||||
# the Cluster CR, so no separate wave-2 pre-creation step is needed. Postgres
|
||||
# is pg18 (not this repo's usual 16.2) because CNPG's official pgvector
|
||||
# extension image only publishes pg18 builds - see k8s/apps/immich/db.yaml.
|
||||
# media PVC shares the cp-3 HDD 2TB/2TB with paperless-media. OIDC via
|
||||
# Authentik provisioned by k8s/infra/iam's PostSync job (immich entry in
|
||||
# SERVICES + immich_role scope mapping for admin-via-claim).
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: immich
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "8"
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/immich
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: immich
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
---
|
||||
# Consolidated: homarr + homarr-patches → homarr
|
||||
# Helm chart + values + PostSync hook patch (fix-probes-job.yaml)
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
@@ -220,10 +188,10 @@ spec:
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/apps/homarr/homarr-values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/homarr # PostSync hook: fix-probes-job.yaml
|
||||
destination:
|
||||
@@ -235,60 +203,3 @@ spec:
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
---
|
||||
# Portfolio site at riotpiao.com - static Next.js site from rock/riotpiao.com repo.
|
||||
# Points directly to infra/portfolio/base (bypassing repo's own argocd-apps.yaml
|
||||
# which has wrong URLs). Image built by Forgejo Actions on rock/portfolio repo.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: portfolio
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "8"
|
||||
# ArgoCD Image Updater - auto-update on new image push
|
||||
argocd-image-updater.argoproj.io/image-list: app=forgejo.riotpiao.com/rock/portfolio
|
||||
argocd-image-updater.argoproj.io/app.update-strategy: digest
|
||||
argocd-image-updater.argoproj.io/app.allow-tags: regexp:^latest$
|
||||
argocd-image-updater.argoproj.io/write-back-method: argocd
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/rock/riotpiao.com.git
|
||||
targetRevision: main
|
||||
path: infra/portfolio/base
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: portfolio
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
---
|
||||
# Wave 9 - per-service scoped RBAC (Role/RoleBinding), deliberately last so
|
||||
# every target namespace above already exists. Inert until kube-apiserver
|
||||
# gets --oidc-groups-claim=groups wired up (separate, not-yet-applied
|
||||
# terraform/talosctl change) - these grant nothing until then.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: rbac
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "9"
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/infra/rbac
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
# No namespace: cluster-scoped resources (ClusterRoleBinding, etc.)
|
||||
# Namespace is set per-resource in kustomization
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
|
||||
@@ -12,7 +12,7 @@ metadata:
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/kmsvc-manage.git
|
||||
repoURL: https://forgejo.riotpiao.com/rock/kmsvc-manage.git
|
||||
targetRevision: main
|
||||
path: k8s/argocd/apps
|
||||
directory:
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
# Poimen project collection — manages poimen-memory, poimen-workflows, and poiman
|
||||
# Each repo tracks its own main branch (no prod branch). Poiman is the primary
|
||||
# orchestrator with k8s/argocd/ containing the AppProject and deployment structure.
|
||||
#
|
||||
# CI: All three repos trigger on main branch pushes (no image builds yet).
|
||||
# Future: Add build workflows for poiman once container runtime needs are clear.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: poimen
|
||||
name: poimen-root
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: poimen
|
||||
app.kubernetes.io/component: orchestrator
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "7"
|
||||
# Image Updater: auto-update on new image push (SHA tag filter)
|
||||
argocd-image-updater.argoproj.io/image-list: |
|
||||
memory=forgejo.riotpiao.com/rock/poimen-memory
|
||||
workflows=forgejo.riotpiao.com/rock/poimen-workflows
|
||||
frontend=forgejo.riotpiao.com/rock/poimen-frontend
|
||||
argocd-image-updater.argoproj.io/memory.update-strategy: digest
|
||||
argocd-image-updater.argoproj.io/memory.allow-tags: regexp:^latest$
|
||||
argocd-image-updater.argoproj.io/workflows.update-strategy: digest
|
||||
argocd-image-updater.argoproj.io/workflows.allow-tags: regexp:^latest$
|
||||
argocd-image-updater.argoproj.io/frontend.update-strategy: digest
|
||||
argocd-image-updater.argoproj.io/frontend.allow-tags: regexp:^latest$
|
||||
argocd-image-updater.argoproj.io/write-back-method: argocd
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/poimen-workflows.git
|
||||
repoURL: https://forgejo.riotpiao.com/rock/poimen.git
|
||||
targetRevision: main
|
||||
path: k8s
|
||||
path: k8s/argocd
|
||||
directory:
|
||||
recurse: false
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: poimen
|
||||
|
||||
@@ -16,15 +16,9 @@ spec:
|
||||
- https://github.com/Riotpiaole/Poimen-memory.git
|
||||
- https://github.com/Riotpiaole/Poimen-workflows.git
|
||||
- https://github.com/Riotpiaole/poimen*.git
|
||||
# In-cluster Forgejo repos — explicit allowlist (no wildcard)
|
||||
- https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
- https://forgejo.riotpiao.com/riotpiao-poimen/homelab-frontend.git
|
||||
- https://forgejo.riotpiao.com/riotpiao-poimen/kmsvc-manage.git
|
||||
- https://forgejo.riotpiao.com/riotpiao-poimen/poimen.git
|
||||
- https://forgejo.riotpiao.com/riotpiao-poimen/poimen-memory.git
|
||||
- https://forgejo.riotpiao.com/riotpiao-poimen/poimen-workflows.git
|
||||
- https://forgejo.riotpiao.com/riotpiao-poimen/poimen-frontend.git
|
||||
- https://forgejo.riotpiao.com/rock/riotpiao.com.git
|
||||
# In-cluster Forgejo wildcard — all rock/* repos can be onboarded without
|
||||
# touching this AppProject. Enabled by Stage 1 (A1). Includes poimen-* repos.
|
||||
- https://forgejo.riotpiao.com/rock/*
|
||||
# Public Helm chart repos referenced by k8s/argocd/apps/* and bootstrap/*
|
||||
- https://cloudnative-pg.github.io/charts
|
||||
- https://dl.gitea.com/charts/
|
||||
@@ -43,8 +37,6 @@ spec:
|
||||
- https://charts.jetstack.io
|
||||
- https://kubernetes.github.io/ingress-nginx
|
||||
- https://stakater.github.io/stakater-charts
|
||||
# ArgoCD ecosystem charts
|
||||
- https://argoproj.github.io/argo-helm
|
||||
destinations:
|
||||
- server: https://kubernetes.default.svc
|
||||
namespace: "*"
|
||||
|
||||
@@ -12,7 +12,7 @@ metadata:
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git
|
||||
repoURL: https://github.com/Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/argocd/apps
|
||||
directory:
|
||||
@@ -26,4 +26,3 @@ spec:
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
apiVersion: ENC[AES256_GCM,data:uS8=,iv:EEoo9U+C244eAJMSTOQVkf5AE6BeHrc5DWPjtWnPRdk=,tag:H+YmBSGvcON3wh0p22LXDQ==,type:str]
|
||||
kind: ENC[AES256_GCM,data:j1/PFkTS,iv:ja4q8X+nzE/ZczwcY+Qe2DnnsxG64W1fkRPQvOaoqvA=,tag:WiilMGagudEKUlc2JdbGyg==,type:str]
|
||||
metadata:
|
||||
name: ENC[AES256_GCM,data:J9iAIHboMyHu6xn/,iv:p3z3uzlkM7VDzOT3WDCelCdZ2t+QqSPmFtqYfvXPQMs=,tag:rKRtpUexVkuZKOJ34a0Xjw==,type:str]
|
||||
namespace: ENC[AES256_GCM,data:su0FvA==,iv:6SPvwxZ/4aoL0Z07zdPC9r6L7HOHuKv3RodXpVcii04=,tag:njwkj+yvSgN8sliJP8T/Kw==,type:str]
|
||||
type: ENC[AES256_GCM,data:Qd6WJN1c,iv:M3fc78LvemcEbWzesuYeQ/GZyIOl7Eg/0EmUe3p0qAk=,tag:Tnzwewn0vwdowxy/EyuPdA==,type:str]
|
||||
stringData:
|
||||
user: ENC[AES256_GCM,data:/Z7gPrsTUZOLzfoZ8cZGD10wrZE=,iv:0ydBSeq+yHB2b1J4W7FwIv55Eh+N3qfQ6Q7PwoUAvYo=,tag:CO19F8nElaYZyFiFR6N/Tg==,type:str]
|
||||
password: ENC[AES256_GCM,data:AOl4StpeQIHSLX+oEFnkfg==,iv:mYsZ+5sum6YqyUPndtPCniTraxldKc803ULlKs8Gsaw=,tag:hK3w51ifZ/omAL7XDf8seg==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaMlhKVEM2bFdFOFAwV1lp
|
||||
WUd6SEN4ZTF1TEpGYXhOYmJLK0tpcWZyc2w4ClVzZmI2Qk1KUnV1OXpyTEV2WWFa
|
||||
VlJ2eHRSaEhqUnA2dUJVbWJUcEgxcFkKLS0tIG9IaFVnenNpSFRzdStiWjJvakVL
|
||||
aDk2bTZGR3Zya3ROUS9vd1hEQVRFaG8KbeXA6IebHEaB79N6u795336aHesHOgzO
|
||||
uZvvBUzSBy3t3jfFk8bJP4aH79I33Ha2eK5rsvdsiv/orwCMXUINKg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla
|
||||
lastmodified: "2026-09-12T21:00:46Z"
|
||||
mac: ENC[AES256_GCM,data:DjzPtR+Ueihh166Bvd3jCLtZFQdrvrxOoF87cv6lxKGWPEptT5vbw/EfuIFJBb6lvEJFDWKRC/r5ASdGwComYsPn0DZs4BPCzeKBtLfP9K2OGCXnAC7eGqt4mzMrrW0CQd1QSGcuXw8CY7uJGkMPGPKe3/0mQWiis2OSpHxTM18=,iv:/QqSEFU9RuX9z5Z6aSaD7KlP/cgGTOYvTH+VJOnDTYM=,tag:yFbFXn+iWqQZx7wW4dKppg==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.2
|
||||
@@ -1,24 +0,0 @@
|
||||
apiVersion: ENC[AES256_GCM,data:wR4=,iv:cRBzbvu0eUYCYeKeysua1/P3Meli/rQyj/mIV6VWnPM=,tag:oyTPA/mLYNUX+QDkYLlZyQ==,type:str]
|
||||
kind: ENC[AES256_GCM,data:bdKQdadW,iv:F3DQiBI9xhSx87jkS1Hyevo48uUCBjsJSjbScIBznKA=,tag:PFakzzBj5S9F65dxu0L2rg==,type:str]
|
||||
metadata:
|
||||
name: ENC[AES256_GCM,data:XHFYrKClPo+IwRbM,iv:ZGuL+cN840Y1bOTC62NhDDcoZpTzDWQ4GfqPJX/QWmI=,tag:hlCVjzvfcxXGOASc3vda/Q==,type:str]
|
||||
namespace: ENC[AES256_GCM,data:0BbhgZBog+1qY/iRJA==,iv:88tiSpEDqIokT5VP/d6bB2+aUyh1kZ7NEHwZWoJW3XU=,tag:CBR01jh2U9h7kNEcK1e1sA==,type:str]
|
||||
type: ENC[AES256_GCM,data:Mpv1V73w,iv:BW3r6RpLnwe3XDKwrZySyOjrWUnSwIG5JOoPLzP/5gM=,tag:oyJySL0haOei1m4i+1AJ1g==,type:str]
|
||||
stringData:
|
||||
username: ENC[AES256_GCM,data:8xmxLGE=,iv:J/vEvXGoD+ka6FDgnSwDz0fs9IxuJIZW9r7Oyu2qxC8=,tag:dN9jd6NSavMN8+uzvemYWg==,type:str]
|
||||
password: ENC[AES256_GCM,data:vB9PaaUiQZ8FY8pO0cq1fHLi7Gq5t4U=,iv:C0Y1m2SGYP3oTIFoau5JavVmLblj6te/SPMLodIwiZw=,tag:3Ip4YvR4WPzR0bBpTyjytA==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRT3JoTnhVVW5SUUJXbTdz
|
||||
dUpLcmtBWWhyaGk3Y1hBM1ArcVI5eHREbmp3CiswUGVmd0NhejZwQ2UvNEdxS3ow
|
||||
L1RweS9Kb2paeStLZ0tLSFdWbVZqQW8KLS0tIHJHT3hiMmxtM0Q1Ym5KOVpLVFpl
|
||||
enR3NmdNdmdwVitTQVJlRHFWcjR0N2sKQw9ZZs+Ji/Zq/feO3qy4DwaCfWgDOQ/z
|
||||
FVVhcCXweN58tb+9fzCJ+pNi/hSmvUkCMbb1+60qBvEehNzOoMRJ5g==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla
|
||||
lastmodified: "2026-09-10T13:31:00Z"
|
||||
mac: ENC[AES256_GCM,data:3HWV/NG0yTbsxY28u41zTRmAPc1kokGb4nCLmAsyq8/uvxcP+evDNyZXYpS93eVdPRfRZ1OqVBzyVGJEnj7JSXQVmlzor9JcWEL2fcpogoLVfJZKTRD6hk6optnBMjj84iQEEOx3A80JrDOdoXsH0pd9bJBABwoUOJwuufbXcIU=,iv:KLZsmAcapQgV08pFhGIvPt5ylsWg3xazAAjPuJYOaXA=,tag:1vKA3fISMIurHzxZ04F3lg==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.2
|
||||
@@ -1,28 +0,0 @@
|
||||
apiVersion: ENC[AES256_GCM,data:FOg=,iv:15mfPeWXV5LQEYahaYvNf1z2bHbxk4j5i8DXT6mdG/0=,tag:1f9/jnnu+wGeeiXGNFgKzA==,type:str]
|
||||
kind: ENC[AES256_GCM,data:dOFfNQiM,iv:HVLosbRpcCgu4iiYKV8MDLiQ0uhj8DXBfVpRBW2NFNo=,tag:mCbRIzD1OHRhBLc+7xcVug==,type:str]
|
||||
metadata:
|
||||
name: ENC[AES256_GCM,data:kHoKhGyiIQvCfng=,iv:dAhxjeLlXK3yueMKfrHxmh9YmNA7AYKFBquLikCNzcE=,tag:3Xoaw7YNBCU/GINlaQOpBw==,type:str]
|
||||
namespace: ENC[AES256_GCM,data:fTJMZhbqSQWD3/cnWg==,iv:D7zWa91+Fgerfyrywf8VA5YNzGrThhLz7CvYXucfiq0=,tag:RcIegCl2UR1JsbfIQm928w==,type:str]
|
||||
type: ENC[AES256_GCM,data:Qh51bTsM,iv:2Htv0Xze7Hd1m6zkP6rtFXAlg8qm0AKylSjwJxRjpBk=,tag:NCu5iVUTNVfYgErZSvwHIg==,type:str]
|
||||
stringData:
|
||||
host: ENC[AES256_GCM,data:aZXSvQ8B7h78q1kHmh0=,iv:uUyL9X3ehIHqztGAtXtAQWgduvbSsSwZBZlTFMdOlBo=,tag:OM/vdO19VKDSa4W5WQAKNQ==,type:str]
|
||||
port: ENC[AES256_GCM,data:5ZBB,iv:1/XAfq+PJKdBsufx+EPvvB/cm9nbEwYigc8eACXjR8g=,tag:WlNyz7gTPh0KMe5oKVd0BA==,type:str]
|
||||
from: ENC[AES256_GCM,data:2/akr8cXgmgQGnqJaFcLJMDcWw==,iv:0rT/6aqyXxn1jIJ5umYCDZR4S8Pbh4vUgaXNrxd3JF8=,tag:k0pQrPOrqWTyE1Xu9oSzVA==,type:str]
|
||||
user: ENC[AES256_GCM,data:d9BLaFYOYm++HZMzlf1gVauKkUQ=,iv:Wd48T5UPVn6z3bS0t02X9GbrnWal3QoIQv2EgM9z9Wg=,tag:cmg2KnLY4Atwco+j2wVdeg==,type:str]
|
||||
password: ENC[AES256_GCM,data:pmqEj9623A6bThKrkCCjuA==,iv:M/QwJ0s//UvuOjOljl67EDhvZt/9Wp8JicvCcows51A=,tag:bfRyIdj1cgIZxU3WPbKteg==,type:str]
|
||||
notify-email: ENC[AES256_GCM,data:+Xo22g8U2wDKsnPnFq/+GKxgYOI=,iv:PE+C1etlp6046ragiv1iMDQbhfo5IULd/5akD0+Sc6A=,tag:cCZmXByA85fuZL9yozVLjw==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpVVdxQTZFTHcwMXFFZEFk
|
||||
ckNsYk4rdWtkeXFMVnlyVmdaRDBuRWsvZkQ4CitSSTBXOTZhWVpUZFFlR0JITVV4
|
||||
Uy9lSlNHNjFNaFhHNTN0WnRaRlNNVGcKLS0tIHRlMnZpQVlScGRYYm5nT3Z5TWd6
|
||||
ak1UZ1RobkpQZHBXR3MyenBDcU53Mk0KvH9O6bgwrjay0+1/A6TGX8GhITiDjWoO
|
||||
RNX4fDtqNwhzmCfXbVjK30vlBjOFe+Bb7Z2n+hWMmHHDgFdS0xIAzA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla
|
||||
lastmodified: "2026-09-12T20:57:18Z"
|
||||
mac: ENC[AES256_GCM,data:V/wgjnpUBvaV39BCTlecCwQy2/1h+0vixEXleJc/I9y+AOvuwVZNH7M86hdjoAdsKiIoNYySj4Flz+MJ9C8jQoAxBTDPbolP9UwDFWQ5KMJTKPPDoJGLNjy7bUq51WoyePUM6WWAYIiWHIB3OvAxHaSzECzL+ZoAhQy92cPKa1o=,iv:vLtIoD/C5yeXPEr+2Lim6XnWzAj42vr9qQgsxKpuhA8=,tag:PBq8qPHrx4RRgAKCWrxG3Q==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.2
|
||||
@@ -1,27 +0,0 @@
|
||||
apiVersion: ENC[AES256_GCM,data:YE0=,iv:s3yNqcBn7/DKUQNgbGGwVmlM1HzxYGLBKyB6Y2ii2WE=,tag:77KxAjOFU3G0fSfrgudKkg==,type:str]
|
||||
kind: ENC[AES256_GCM,data:PFW4VV9T,iv:n8gzMjE5C2TAfUTpp/8ex64B+hWUGG1K+2oQ4deN03Q=,tag:18Xqb6Di5izMHmzR5EU+QA==,type:str]
|
||||
metadata:
|
||||
name: ENC[AES256_GCM,data:Xhg1fQrD4itrbvDW0g==,iv:/THWSXAThb9fj+mm3wcxqzSdzdt7nE06+xOpkCxyImE=,tag:UChokr06VkbDZcFZDcUY2A==,type:str]
|
||||
namespace: ENC[AES256_GCM,data:r25U5MuuzDd8JJ2YjQ==,iv:uDUcS4ZTpZe8HmZMArzkdu7LV5GUoLSP2wIs5HB1gv0=,tag:fci7j30hoxnVKJwPFNfd1Q==,type:str]
|
||||
type: ENC[AES256_GCM,data:agEG8Fu8,iv:ckYX0buX8md1CWHXfxbAXQJLzoTxTJI16nlQ9LSzYi0=,tag:4tZWf3REbGOmmBiT14+dew==,type:str]
|
||||
stringData:
|
||||
#ENC[AES256_GCM,data:ehjcsmz1R0i/n31f8M0IIUT4KDBwzz6f5ds=,iv:j5swFAKpC67ZvGioiCCC1D651LxUl9gME8GqK5Uc+ys=,tag:BUJ8k9LHs8NAPPZAwPuifA==,type:comment]
|
||||
#ENC[AES256_GCM,data:ndAB00yun636VHDMonqTghO/jCWodNd/hmdbm1QmCvOAi4FvTLl8bY3wYR+ZtlkSQYvFNqH798MJixv5OZwxBj5H,iv:pS+7B60jaS5jhqDRw2LbBFv7mD1HO6+hjjv+iNpenXc=,tag:NU6+gxCHTGxqeDMfvkwJWw==,type:comment]
|
||||
#ENC[AES256_GCM,data:T3mhSm/5q7JTSXNLrjhpP5GhqA7nXz8uAhJcEV1RDctAX0Dyfq8Qn4bNFO51ibwTETx4SnunPuFZVs++AvRnsA==,iv:oRGM8N4iEcwBrMzoEXQAeKqCKzUq+jXTMVq9W2l6oh4=,tag:GrrbC/tkWpA5kp5UYCyRBA==,type:comment]
|
||||
app-token: ENC[AES256_GCM,data:jw+AqbsxyoYRvNrUDrq+GNq/TNfweFCs,iv:67vSSgMZCMV0GG37ZxUxHAWZqOOGMYCmo3J43WgLyNk=,tag:xlrnip4XomXBbMmooW9FKg==,type:str]
|
||||
client-token: ENC[AES256_GCM,data:gsxeCqKh4e+DFjpn9jM5GfemAdBf8dSv,iv:l2bBMdxQUil8jU9XDjXGn3EtvFVrK5ibXDCeGaPbYTY=,tag:ELf5S6cdNQJ84Es5upu1IQ==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAza3YrRW1VTVNSOGlMK21a
|
||||
bDhzQlEyZDNnZTZrSVNnTUEzU1hSdnM1ZFVzCjdBSUlmWG5EcnlzbSs5bXdDaGkx
|
||||
ME1nMnRqQzF1Vkc3b3FXSUVHT1g0ZDgKLS0tIHpMUytEMjdLQ2E0Unp2di9KS3JQ
|
||||
eHBraDk1clJyanhLY2dGM0tESmJIUFkKHTl3y9uQiEofOFD8j2vH3YK/CVzlq11w
|
||||
GfShIji1yCvvowKGzYYhsQK0UM0FzhzBv0GFMYWQCBq8pGdoPVmO5g==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla
|
||||
lastmodified: "2026-09-10T13:31:00Z"
|
||||
mac: ENC[AES256_GCM,data:JhnO0V6cd2QVY/VhwHAJ5J75GeVlOVHBfVBTZstkj/IvpkAcwS/JE2b6jXiCwEC4n/vdA8DJ074noysUBRxh4Y7O4NKuvWcTniQKgqULNL1Hzgj3NdUkcQlWT+Z0HQ7FlvFH97VVXVfasU+CLHG48ShT2fDSj8JusEllb9CwSvg=,iv:PZo2krxvJc1TLJbvx+S9ClthoBe4w7WigUkq7dg0gNk=,tag:2IF5g/WTRP4XdnCZzDbiIA==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.2
|
||||
@@ -1,10 +1,8 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
# Disable hash suffix for all generated secrets (stable names)
|
||||
generatorOptions:
|
||||
disableNameSuffixHash: true
|
||||
|
||||
# SOPS-encrypted secrets via ksops generator
|
||||
# All homelab SOPS-encrypted Secrets, decrypted in-line via the ksops generator.
|
||||
# Each *.enc.yaml carries its own metadata.namespace, so no namespace transformer
|
||||
# here (that would rewrite every Secret into one namespace). Renders exactly the
|
||||
# Secret objects — replaces the old argocd-cmp-cm SOPS plugin.
|
||||
generators:
|
||||
- secret-generator.yaml
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
apiVersion: ENC[AES256_GCM,data:l7I=,iv:NZY7r3JVW3zVwxeiScvWKpAQUDa9+nckHd0qWVrGU88=,tag:qpowp5OILdYKtTux/nlWpg==,type:str]
|
||||
kind: ENC[AES256_GCM,data:6oeEbuIk,iv:5flI9TtcYQ961wOYPBPhvQpitHFYAf8yMdNBXqytbJs=,tag:WNhlbKmSeZEqZ9ZgiB/BYg==,type:str]
|
||||
metadata:
|
||||
name: ENC[AES256_GCM,data:fvJMsgy+wPZqMCdxm9hoV3n/+Q==,iv:I3rVtHIJBOME+bxhPws58Zjhj5i+KT5UtB9o7Vus5EU=,tag:6h4FnUu7PGxlQPIQxPYCRg==,type:str]
|
||||
namespace: ENC[AES256_GCM,data:CDriLoLovROW,iv:rHXcN1xm5+t2D/Tq/2sx9lQFF8anD5jH24ZntPuBA8U=,tag:XstJAz6S8IM1c/pp9Cg0Ww==,type:str]
|
||||
type: ENC[AES256_GCM,data:JdTwBbag,iv:9Ys15Ketl0ghNK0u0N8IOpUt7+KoloutiG5M9zHPXxw=,tag:teau/udf41Ty34A5wLAm6Q==,type:str]
|
||||
stringData:
|
||||
PAPERLESS_API_TOKEN: ENC[AES256_GCM,data:TuQeDx8po3h4loTRABlItVYQJ7gFjnmIn3zQQGtUcoNFMKpkqLK/GQ==,iv:TDg0stpca5pDtatqu8DFU7R0Bm/S/BI9ZoiG4K8mCT4=,tag:BfjX25V5gL7AeIsscClRAg==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKVDN3aFVqVmFXY1VQVXZP
|
||||
OUsrNHdNdlRvRFgvTDQrNE5uL2xaazVENTBjCkNPbGR5Vk16RXNDOW15OGNTRmFR
|
||||
U0JOeTllaWU1dzNVY3lBbEVyVG5tOEkKLS0tIEZvajlvcUtJdFNNUkkzV3FKOFRj
|
||||
UUE2TDBzT0xVc2E1NlUvQXAyZytMZEUKBv+ChaoQCstA742L3Bq5mBJlW/UC4Pyw
|
||||
ZvFAyYbs1NaEqhjtHq+4T62jTWcH/St/vKgUuFQ9LCUQhYd8DUzAow==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla
|
||||
lastmodified: "2026-09-12T21:04:26Z"
|
||||
mac: ENC[AES256_GCM,data:0ks96xQzOa8ygNDDYfKV8EJ8dWLBaC0PCnLG73NinMByF/KoWkCdwMDPC34W9xxVoTD2NiF1i/3pgCG4QFezTE7tPHPPKdYcQfwda9fkooiXW4Nh2M/sgbq/xgsy9N3qpHD/O5iILgpehb9y0DuErOyxcn2AIYizynU7m8e63pc=,iv:fvPWBsfE6YHskKzdlxJidp14dUgRR0XdMkEu6IxMMSo=,tag:5FiuIrFOg/GXvlQVy7drJQ==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.2
|
||||
@@ -1,2 +0,0 @@
|
||||
FORGEJO_TOKEN=273fdcffabbcbb5a191e8289c73d106063acefc6
|
||||
LLM_API_TOKEN=s3VksXyw2z3sGbegnwjMDFnJ6CtNRd1a5CcnE5A4ET77toCcykNdunk6Oa2J
|
||||
@@ -1,23 +0,0 @@
|
||||
apiVersion: ENC[AES256_GCM,data:bnY=,iv:Fuc3aqncHQ+L16o7eLarPbOECD3o8Mk5c2r9pQBpy70=,tag:JPfEnbNb3wZXPdXafnJDqw==,type:str]
|
||||
kind: ENC[AES256_GCM,data:WFlmi4Yg,iv:Zq/KQbgNcBVoo8ZsQ2H79ygyc8Dtkgxh4fCpEExfwSg=,tag:cWHP6Y5V+nZP2tFMJrOB8A==,type:str]
|
||||
metadata:
|
||||
name: ENC[AES256_GCM,data:iCXbhwvg3Zq6YL/4j0wAy7Y=,iv:8s+d/8lDVEL7bGdIF+GOtAxapKnmx8JTjLSO04hXF5I=,tag:LjzX40DjK+uRZPCXsMlmwQ==,type:str]
|
||||
namespace: ENC[AES256_GCM,data:HRMdZdCbxORQ,iv:MvaIWoKWjJRA7/fce0KtXRkFH/7cn0OuIg2QwHEdQzM=,tag:NqztiGyfU3BaopWBKhx2eg==,type:str]
|
||||
type: ENC[AES256_GCM,data:myBW86Za,iv:3x9ys5UzVhAuX8gvZO67B1e+Orw4Aqasv/lHBgUV0b4=,tag:yl18P6SaxVLJidwmRtd4aQ==,type:str]
|
||||
stringData:
|
||||
FORGEJO_TOKEN: ENC[AES256_GCM,data:SUoBpNKOItyNGY01EhKNlPH0fyN4N7g6bfU2jsGogpCMhP7NuRipoA==,iv:h77RtmYZjXxiHYw1pHynQHuVX1+yJDGHwsXLf+DbUYA=,tag:GHzoDNHP1GGqlN5eT/N7TQ==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzTVBsekl3TGgzQVRMUU9m
|
||||
cTduS2NoZW5uZFNNMG13cFY2cGVsTnlXaXhrCjJjbzhLdHZ4ZWpUV3J0cDQ0eVlM
|
||||
WDNxdzVoQ2ZzcGJSbTU3RVorcnczNVkKLS0tIFdtQTE4Umk2TDBzUmdKOXNkbjFi
|
||||
Vk5vK2VuUHVsb3FQL21vcGU1UW5CT1kKFM8vVjji3Cg9dvfTr4Hx7BJC8JH5ovef
|
||||
Dj6zkofhsNWPgP9T+mnQakj+C0RKmHOMJqfWP7vwCBkZoNosIJVlMw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla
|
||||
lastmodified: "2026-09-01T05:32:42Z"
|
||||
mac: ENC[AES256_GCM,data:it24T9y9ixXo2aiL37k93vKFR+SRjjuI9DQdv0sWYtTogWnc7+uXBY4Zip/ouWyCse1muKKAGuek5c0XVrvSw4an9VkaXFczeunaZb6MOyVbVOkmJr+5xZFpZGjYcSkrhaWcVheedZ3iIFU5UWI7BBn/qQCf+HJ483cJqwtrV34=,iv:WprlWJdsMBNjqaA0O3ekfXMUpX5gC6OLYortQXYdTS4=,tag:rGqSSRTYTv2VR6AcRokO0A==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.2
|
||||
@@ -26,9 +26,3 @@ files:
|
||||
- paperless-secrets.enc.yaml
|
||||
- vault-secrets.enc.yaml
|
||||
- vault-unseal-keys.enc.yaml
|
||||
- portfolio-secrets.enc.yaml
|
||||
- gotify-admin-secrets.enc.yaml
|
||||
- gotify-tokens-secrets.enc.yaml
|
||||
- gotify-smtp-secrets.enc.yaml
|
||||
- forgejo-smtp-secrets.enc.yaml
|
||||
- paperless-ai-secrets.enc.yaml
|
||||
|
||||
@@ -5,9 +5,9 @@ metadata:
|
||||
namespace: ENC[AES256_GCM,data:wK6m,iv:KtA31Bo8aGE1HU8H9KWbMwt2NfywWfy77G/LaReaI1E=,tag:V9SSlPawRQw0x2utFNN1aw==,type:str]
|
||||
type: ENC[AES256_GCM,data:6UPZ1ZTR,iv:FxN1ebrlJ4IO3eDGEYSvktSpMgAebcl0DO1WHh5O0+0=,tag:2bZN4zdbHzx0oR+bTJPPJg==,type:str]
|
||||
stringData:
|
||||
key1: ENC[AES256_GCM,data:ke5EfsOsZHBJyAQoFfwhhWCQJQgnwcrBqL4CzPIfkx7bi9S7kWgWySdxcXQ=,iv:eYKxG8k+hizp2t2i/YMR2lQNJQFV+A21YyWnDc+kJ9w=,tag:Wa1Lx3EKvfpYfnLrBOWSIg==,type:str]
|
||||
key2: ENC[AES256_GCM,data:JxiXgLKvDe5oiHlwIL/Cj8txHc7fVQ5VzBcQMU/ro9TSOcTGSe7Z3omQzwY=,iv:x5arcY2dey+npMpUxjdUPV+t94LEaMOXq3iOar88eT8=,tag:E/Z5zEDy87B1IAWkdjdNEg==,type:str]
|
||||
key3: ENC[AES256_GCM,data:AsxyyBTHzt+SLru6ayi1UbgPPFmdIvowIDdcfc7N03Xt8V/KaJW3kbXExG8=,iv:lA+bERIBq4+bxx08ZZjP5tMHC9JQlGPW1EMCwEixSeg=,tag:xkuTAU3KTidFuhNMXkVFhA==,type:str]
|
||||
key1: ENC[AES256_GCM,data:dV2HOh7W1Pl0QDJaGvtEKpBppybQMiK5kxzyk05zkA352ZmJvE8Ppm5yWKk=,iv:grQ8v2o/LHpJnIZonjtgTHKcLUQIH8xl1FtWtEs4rEs=,tag:lANcccYT6Ouo/0IcoLw4uA==,type:str]
|
||||
key2: ENC[AES256_GCM,data:YSdpYL8h56PfUrvMFhBXhmBG2en0toLKEpYlJqwjAk/vI0jm7gq/TqGn034=,iv:GxthkNLhm3qHxjkZetiYl58Qa/K7G2ib6E+LWK16H8Q=,tag:q79UUoh6uCcMZJ0U18Ireg==,type:str]
|
||||
key3: ENC[AES256_GCM,data:sO7Lao9qkmIOdARL+6FBcLo+e4z8LzMEzNrwcy5hv9uRuxppwAXrP0MO1vw=,iv:wSWe9h1HovRhV5YCZrmvcB2VEfRYQ+gasGO3uh8IpmQ=,tag:Bc5yaROQDTz1AhVnFDm/BA==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
@@ -19,7 +19,7 @@ sops:
|
||||
7RltNZF2SCxjIv5C2pqf3CgmRBaQGWgMybRRH5gdB87PLBKkPL3+HQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla
|
||||
lastmodified: "2026-08-26T23:33:09Z"
|
||||
mac: ENC[AES256_GCM,data:kr8XuOqXyhfrC84yBot5aoVQZZhQd9xYMUEs0XDwkmCgtG0iobADYH5XWPh72xSdWEtwxkZJL7r0UEyEhU9zjlUA5qHupOSBBt1SgZ+zstMvaOqUnlNn//p/DIJBpsiT/qmx64NpTLAiz6lm0796MozIMr8PTX+ubGLHi9tnUiY=,iv:jwP0MLCT7nG6m+nZeqNip9q3BcScpcDXmInnY95DicY=,tag:Cgg2IUZARkaK1gF2qUIthg==,type:str]
|
||||
lastmodified: "2026-08-12T20:52:24Z"
|
||||
mac: ENC[AES256_GCM,data:kLjoBWmJ2bZ13EbuzgppahHKmCY/xtCi1R7xCEUbCP4FQfiVaC4qAbVftE4lTNlXa2mqCKsGPYZ/A3HV/4+a+it/pg0a+rj7E7DszhieWbZufHMJs+w4/Le8l8wFFE5aNW0wdvGTJZ0n4HBOdAkE1qN9ReaiaQgr0rk5ggOPG9g=,iv:MiDVUgcoKrP/gj4keqomo6OB12dmz9VoesTiHQTnBFM=,tag:cLK67Pym4BuvXLF1RUA8Gw==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.2
|
||||
|
||||
@@ -323,4 +323,3 @@ spec:
|
||||
name: homarr
|
||||
port:
|
||||
number: 7575
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: kyverno
|
||||
|
||||
resources:
|
||||
- policies.yaml
|
||||
@@ -1,42 +0,0 @@
|
||||
# Kyverno: Policy engine for Kubernetes image scanning, Pod security, and admission control
|
||||
# Scan all images, enforce baseline Pod Security Standard, prevent privilege escalation
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
registry: ghcr.io
|
||||
repository: kyverno/kyverno
|
||||
tag: "v1.14.0"
|
||||
|
||||
config:
|
||||
# Webhook timeout for policy evaluation. Increase if scanning takes longer.
|
||||
webhookTimeoutSeconds: 30
|
||||
# Failure policy: fail-open (audit/log) vs fail-closed (reject on error)
|
||||
failurePolicy: fail
|
||||
# Resource limits for webhook
|
||||
webhookAnnotations:
|
||||
rules: "allow"
|
||||
|
||||
# Pod security via Kyverno instead of Pod Security Policies (deprecated)
|
||||
# Enforces baseline restrictions cluster-wide, with exceptions for privileged namespaces
|
||||
podSecurityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
|
||||
rbac:
|
||||
create: true
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# Webhook configuration
|
||||
webhook:
|
||||
timeoutSeconds: 30
|
||||
# Failure policy: "Fail" (reject on error) or "Ignore" (audit-only)
|
||||
# Set to "Ignore" for initial testing, then change to "Fail"
|
||||
failurePolicy: ignore
|
||||
@@ -1,204 +0,0 @@
|
||||
# Kyverno ClusterPolicies: Image scanning, Pod security, and admission control
|
||||
---
|
||||
# Policy 1: Require non-root containers
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: require-non-root
|
||||
namespace: kyverno
|
||||
spec:
|
||||
validationFailureAction: audit # audit first, then change to enforce
|
||||
rules:
|
||||
- name: check-runAsNonRoot
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
selector:
|
||||
matchLabels:
|
||||
pod-security.kubernetes.io/enforce: "!privileged"
|
||||
validate:
|
||||
message: "Container must not run as root"
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- securityContext:
|
||||
runAsNonRoot: true
|
||||
---
|
||||
# Policy 2: Drop all Linux capabilities, add only required ones
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: require-dropped-caps
|
||||
namespace: kyverno
|
||||
spec:
|
||||
validationFailureAction: audit
|
||||
rules:
|
||||
- name: drop-all-capabilities
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
selector:
|
||||
matchLabels:
|
||||
pod-security.kubernetes.io/enforce: "!privileged"
|
||||
validate:
|
||||
message: "All Linux capabilities must be dropped"
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
---
|
||||
# Policy 3: Require image tags (no 'latest')
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: disallow-latest-tag
|
||||
namespace: kyverno
|
||||
spec:
|
||||
validationFailureAction: audit # Change to enforce after testing
|
||||
rules:
|
||||
- name: disallow-latest
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
- Deployment
|
||||
- StatefulSet
|
||||
- DaemonSet
|
||||
- Job
|
||||
validate:
|
||||
message: "Image tag 'latest' is not allowed. Use explicit version tags."
|
||||
pattern:
|
||||
spec:
|
||||
=(template):
|
||||
spec:
|
||||
containers:
|
||||
- image: "!*:latest"
|
||||
=(initContainers):
|
||||
- image: "!*:latest"
|
||||
---
|
||||
# Policy 4: Restrict images to trusted registries
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: restrict-registries
|
||||
namespace: kyverno
|
||||
spec:
|
||||
validationFailureAction: audit
|
||||
rules:
|
||||
- name: trusted-registries
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
- Deployment
|
||||
- StatefulSet
|
||||
- DaemonSet
|
||||
- Job
|
||||
selector:
|
||||
matchLabels:
|
||||
pod-security.kubernetes.io/enforce: "!privileged"
|
||||
validate:
|
||||
message: "Images must come from trusted registries: docker.io, ghcr.io, quay.io, k8s.gcr.io, registry.k8s.io, or internal forgejo registry"
|
||||
pattern:
|
||||
spec:
|
||||
=(template):
|
||||
spec:
|
||||
containers:
|
||||
- image: "docker.io/* | ghcr.io/* | quay.io/* | k8s.gcr.io/* | registry.k8s.io/* | forgejo.riotpiao.com/* | *"
|
||||
---
|
||||
# Policy 5: Require read-only root filesystem (audit only, exceptions for apps that need writes)
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: require-readonly-filesystem
|
||||
namespace: kyverno
|
||||
spec:
|
||||
validationFailureAction: audit
|
||||
rules:
|
||||
- name: check-readOnlyRootFilesystem
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
selector:
|
||||
matchLabels:
|
||||
pod-security.kubernetes.io/enforce: "!privileged"
|
||||
validate:
|
||||
message: "Root filesystem should be read-only for defense-in-depth"
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- securityContext:
|
||||
readOnlyRootFilesystem: true
|
||||
---
|
||||
# Policy 6: Require resource requests and limits (prevent resource starvation)
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: require-resource-limits
|
||||
namespace: kyverno
|
||||
spec:
|
||||
validationFailureAction: audit
|
||||
rules:
|
||||
- name: check-resources
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
- Deployment
|
||||
- StatefulSet
|
||||
- DaemonSet
|
||||
excludeResources:
|
||||
namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: "kyverno|kube-system|kube-node-lease"
|
||||
validate:
|
||||
message: "CPU and memory requests and limits are required"
|
||||
pattern:
|
||||
spec:
|
||||
=(template):
|
||||
spec:
|
||||
containers:
|
||||
- resources:
|
||||
requests:
|
||||
memory: "?*"
|
||||
cpu: "?*"
|
||||
limits:
|
||||
memory: "?*"
|
||||
cpu: "?*"
|
||||
---
|
||||
# Policy 7: Require securityContext on all containers
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: require-security-context
|
||||
namespace: kyverno
|
||||
spec:
|
||||
validationFailureAction: audit
|
||||
rules:
|
||||
- name: check-securityContext
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
selector:
|
||||
matchLabels:
|
||||
pod-security.kubernetes.io/enforce: "!privileged"
|
||||
validate:
|
||||
message: "securityContext must be defined"
|
||||
pattern:
|
||||
spec:
|
||||
containers:
|
||||
- securityContext: {}
|
||||
@@ -12,7 +12,6 @@ defaultSettings:
|
||||
replicaSoftAntiAffinity: false # REQUIRED for true HA
|
||||
replicaAutoBalance: best-effort
|
||||
storageMinimalAvailablePercentage: 10
|
||||
storageOverProvisioningPercentage: 200 # Actual usage is ~10% of scheduled; 200% unblocks all 3-replica scheduling on cp-1
|
||||
|
||||
# Performance tuning
|
||||
defaultDataPath: /var/lib/longhorn
|
||||
|
||||
@@ -80,14 +80,6 @@ gitea:
|
||||
actions:
|
||||
ENABLED: true
|
||||
|
||||
mailer:
|
||||
ENABLED: true
|
||||
PROTOCOL: smtp+starttls
|
||||
SMTP_ADDR: smtp.gmail.com
|
||||
SMTP_PORT: 587
|
||||
FROM: "Forgejo <[email protected]>"
|
||||
# USER and PASSWD injected via env vars below (GITEA__MAILER__USER, GITEA__MAILER__PASSWD)
|
||||
|
||||
# Persistence (shared storage for repos)
|
||||
persistence:
|
||||
enabled: true
|
||||
@@ -156,13 +148,3 @@ deployment:
|
||||
secretKeyRef:
|
||||
name: forgejo-db-app
|
||||
key: password
|
||||
- name: GITEA__MAILER__USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-smtp
|
||||
key: user
|
||||
- name: GITEA__MAILER__PASSWD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: forgejo-smtp
|
||||
key: password
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
# ArgoCD Image Updater configuration
|
||||
# Watches Forgejo registry and updates ArgoCD Applications with new image tags
|
||||
|
||||
config:
|
||||
# Registry configuration - Forgejo allows anonymous pulls
|
||||
registries:
|
||||
- name: forgejo
|
||||
api_url: https://forgejo.riotpiao.com
|
||||
prefix: forgejo.riotpiao.com
|
||||
default: true
|
||||
insecure: false
|
||||
|
||||
# Log level
|
||||
logLevel: debug
|
||||
|
||||
# ArgoCD API server
|
||||
argocd:
|
||||
grpcWeb: true
|
||||
serverAddress: argocd-server.argocd.svc.cluster.local
|
||||
insecure: true
|
||||
plaintext: true
|
||||
|
||||
# Git write-back configuration (for multi-source Applications)
|
||||
git:
|
||||
# Commit author for image updates
|
||||
user:
|
||||
name: "ArgoCD Image Updater"
|
||||
email: "[email protected]"
|
||||
# Use SSH keys from ArgoCD's known hosts + credentials
|
||||
# Image Updater inherits ArgoCD's git credentials (mounted via ArgoCD secret)
|
||||
|
||||
# Mount ArgoCD's git credentials for write-back
|
||||
extraVolumes:
|
||||
- name: argocd-ssh-known-hosts-cm
|
||||
configMap:
|
||||
name: argocd-ssh-known-hosts-cm
|
||||
defaultMode: 0644
|
||||
- name: argocd-gpg-keys-cm
|
||||
configMap:
|
||||
name: argocd-gpg-keys-cm
|
||||
optional: true
|
||||
defaultMode: 0644
|
||||
- name: argocd-gpg-pubring
|
||||
configMap:
|
||||
name: argocd-gpg-pubring-cm
|
||||
optional: true
|
||||
defaultMode: 0644
|
||||
|
||||
extraVolumeMounts:
|
||||
- name: argocd-ssh-known-hosts-cm
|
||||
mountPath: /etc/ssh/ssh_known_hosts.d/argocd-ssh-known-hosts
|
||||
subPath: ssh_known_hosts
|
||||
- name: argocd-gpg-keys-cm
|
||||
mountPath: /etc/gpg/source
|
||||
- name: argocd-gpg-pubring
|
||||
mountPath: /etc/gpg/pubring
|
||||
|
||||
# Extra environment variables
|
||||
extraEnv:
|
||||
- name: GIT_SSH_KNOWN_HOSTS_CONFIG_MAP_ENABLED
|
||||
value: "true"
|
||||
|
||||
# Resources
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
@@ -1,137 +0,0 @@
|
||||
# Cluster-wide cleanup of stale failed/completed Jobs and Pods.
|
||||
# Runs daily at 04:00 UTC. Deletes:
|
||||
# - Failed Jobs older than 24h (any namespace)
|
||||
# - Completed Jobs older than 72h with no owning CronJob
|
||||
# - Orphan pods in Error/Failed/Evicted state older than 1h
|
||||
#
|
||||
# CronJob-owned Jobs are managed by failedJobsHistoryLimit/successfulJobsHistoryLimit,
|
||||
# but standalone Jobs (helm hooks, one-off runs, longhorn maintenance) have no TTL
|
||||
# and linger forever.
|
||||
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: stale-job-cleanup
|
||||
namespace: kube-system
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: stale-job-cleanup
|
||||
rules:
|
||||
- apiGroups: ["batch"]
|
||||
resources: ["jobs"]
|
||||
verbs: ["get", "list", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list", "delete"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: stale-job-cleanup
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: stale-job-cleanup
|
||||
namespace: kube-system
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: stale-job-cleanup
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: stale-job-cleanup
|
||||
namespace: kube-system
|
||||
labels:
|
||||
app: stale-job-cleanup
|
||||
spec:
|
||||
schedule: "0 4 * * *"
|
||||
concurrencyPolicy: Forbid
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
jobTemplate:
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 86400 # self-cleanup after 24h
|
||||
backoffLimit: 1
|
||||
activeDeadlineSeconds: 300
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: stale-job-cleanup
|
||||
restartPolicy: Never
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: cleanup
|
||||
image: alpine/k8s:1.31.0
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
NOW=$(date +%s)
|
||||
|
||||
echo "=== Cleaning failed Jobs older than 24h ==="
|
||||
kubectl get jobs --all-namespaces -o json | \
|
||||
jq -r '.items[] |
|
||||
select(.status.conditions[]?.type == "Failed") |
|
||||
select(.status.completionTime or .status.startTime) |
|
||||
"\(.metadata.namespace) \(.metadata.name) \(.status.startTime // .status.completionTime // .metadata.creationTimestamp)"' | \
|
||||
while read -r NS NAME TS; do
|
||||
JOB_EPOCH=$(date -d "$TS" +%s 2>/dev/null || echo 0)
|
||||
AGE_H=$(( (NOW - JOB_EPOCH) / 3600 ))
|
||||
if [ "$AGE_H" -ge 24 ]; then
|
||||
echo "[delete] $NS/$NAME (failed ${AGE_H}h ago)"
|
||||
kubectl delete job "$NAME" -n "$NS" --cascade=foreground 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Cleaning completed standalone Jobs older than 72h ==="
|
||||
kubectl get jobs --all-namespaces -o json | \
|
||||
jq -r '.items[] |
|
||||
select(.status.succeeded >= 1) |
|
||||
select((.metadata.ownerReferences // []) | length == 0) |
|
||||
"\(.metadata.namespace) \(.metadata.name) \(.status.completionTime // .metadata.creationTimestamp)"' | \
|
||||
while read -r NS NAME TS; do
|
||||
JOB_EPOCH=$(date -d "$TS" +%s 2>/dev/null || echo 0)
|
||||
AGE_H=$(( (NOW - JOB_EPOCH) / 3600 ))
|
||||
if [ "$AGE_H" -ge 72 ]; then
|
||||
echo "[delete] $NS/$NAME (completed ${AGE_H}h ago, no owner)"
|
||||
kubectl delete job "$NAME" -n "$NS" --cascade=foreground 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Cleaning orphan Error/Failed/Evicted pods older than 1h ==="
|
||||
# Evicted pods show as Failed with reason Evicted
|
||||
kubectl get pods --all-namespaces -o json | \
|
||||
jq -r '.items[] |
|
||||
select(
|
||||
.status.phase == "Failed" or
|
||||
(.status.reason // "") == "Evicted" or
|
||||
(.status.containerStatuses // [] | any(.state.terminated.reason == "Error"))
|
||||
) |
|
||||
select((.metadata.ownerReferences // []) | all(.kind != "Job")) |
|
||||
"\(.metadata.namespace) \(.metadata.name) \(.metadata.creationTimestamp)"' | \
|
||||
while read -r NS NAME TS; do
|
||||
POD_EPOCH=$(date -d "$TS" +%s 2>/dev/null || echo 0)
|
||||
AGE_H=$(( (NOW - POD_EPOCH) / 3600 ))
|
||||
if [ "$AGE_H" -ge 1 ]; then
|
||||
echo "[delete] $NS/$NAME (error/evicted ${AGE_H}h ago)"
|
||||
kubectl delete pod "$NAME" -n "$NS" --force 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Cleanup complete"
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 128Mi
|
||||
@@ -9,7 +9,7 @@ metadata:
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
|
||||
spec:
|
||||
instances: 3
|
||||
instances: 2
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16.2
|
||||
bootstrap:
|
||||
initdb:
|
||||
|
||||
@@ -16,11 +16,3 @@ apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: paperless
|
||||
---
|
||||
# Needed here (not just immich's own CreateNamespace=true at wave 8) because
|
||||
# k8s/infra/iam's PostSync job (wave 3) has a RoleBinding targeting this
|
||||
# namespace - same ordering reason as paperless above.
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: immich
|
||||
|
||||
@@ -11,7 +11,7 @@ metadata:
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
|
||||
spec:
|
||||
instances: 3
|
||||
instances: 2
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16.2
|
||||
bootstrap:
|
||||
initdb:
|
||||
|
||||
@@ -36,4 +36,3 @@ data:
|
||||
valid_volumes:
|
||||
- /docker-certs/client
|
||||
network: host
|
||||
docker_host: automount
|
||||
|
||||
@@ -34,11 +34,7 @@ spec:
|
||||
command: ["sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
# Always re-register to keep labels in sync with values.yaml.
|
||||
# Without this, changing a runner label requires manually deleting
|
||||
# the PVC or .runner file — not GitOps-friendly.
|
||||
rm -f /data/.runner
|
||||
forgejo-runner register --no-interactive \
|
||||
test -f /data/.runner || forgejo-runner register --no-interactive \
|
||||
--instance {{ .Values.runner.forgejoUrl }} \
|
||||
--token $(RUNNER_TOKEN) \
|
||||
--name {{ .Values.runner.name }} \
|
||||
@@ -60,18 +56,20 @@ spec:
|
||||
containers:
|
||||
- name: runner
|
||||
image: {{ .Values.runner.image.repository }}:{{ .Values.runner.image.tag }}
|
||||
command: ["sh", "-c", "while ! wget -q -O- http://localhost:2375/_ping >/dev/null 2>&1; do echo 'waiting for dind...'; sleep 2; done; echo 'dind ready'; forgejo-runner daemon --config /etc/forgejo-runner/config.yaml"]
|
||||
command: ["sh", "-c", "forgejo-runner daemon --config /etc/forgejo-runner/config.yaml"]
|
||||
workingDir: /data
|
||||
env:
|
||||
- name: DOCKER_HOST
|
||||
value: tcp://localhost:2375
|
||||
value: tcp://localhost:2376
|
||||
- name: DOCKER_TLS_VERIFY
|
||||
value: "1"
|
||||
- name: DOCKER_CERT_PATH
|
||||
value: /docker-certs/client
|
||||
volumeMounts:
|
||||
- name: runner-data
|
||||
mountPath: /data
|
||||
- name: docker-certs
|
||||
mountPath: /docker-certs
|
||||
- name: docker-sock
|
||||
mountPath: /run
|
||||
- name: homelab-ca
|
||||
mountPath: /etc/ssl/certs/homelab-ca.pem
|
||||
subPath: ca.crt
|
||||
@@ -87,12 +85,10 @@ spec:
|
||||
privileged: true # required for DinD; cicd namespace is labelled privileged
|
||||
env:
|
||||
- name: DOCKER_TLS_CERTDIR
|
||||
value: ""
|
||||
value: /docker-certs
|
||||
volumeMounts:
|
||||
- name: docker-certs
|
||||
mountPath: /docker-certs
|
||||
- name: docker-sock
|
||||
mountPath: /run
|
||||
- name: dind-storage
|
||||
mountPath: /var/lib/docker
|
||||
- name: homelab-ca
|
||||
@@ -117,8 +113,6 @@ spec:
|
||||
claimName: {{ .Release.Name }}-dind
|
||||
- name: docker-certs
|
||||
emptyDir: {} # DinD regenerates mTLS certs on each start
|
||||
- name: docker-sock
|
||||
emptyDir: {} # Shared docker socket between dind and runner
|
||||
- name: homelab-ca
|
||||
# homelab-ca is a ConfigMap (public CA trust bundle), not a Secret.
|
||||
# The volumeMounts use subPath: ca.crt to project the single cert file.
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
{{- if .Values.gc.enabled }}
|
||||
# Garbage-collects DinD Docker images/volumes/build-cache and actcache across
|
||||
# ALL forgejo-runner pods. Prevents PVC fill-up that breaks CI runs.
|
||||
# Only rendered once (enable in default values.yaml, disable in per-runner overrides).
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: runner-gc
|
||||
namespace: {{ .Release.Namespace }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: runner-gc
|
||||
namespace: {{ .Release.Namespace }}
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods/exec"]
|
||||
verbs: ["create"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: runner-gc
|
||||
namespace: {{ .Release.Namespace }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: runner-gc
|
||||
namespace: {{ .Release.Namespace }}
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: runner-gc
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: forgejo-runner-gc
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
app: forgejo-runner-gc
|
||||
spec:
|
||||
schedule: {{ .Values.gc.schedule | quote }}
|
||||
concurrencyPolicy: Forbid
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
jobTemplate:
|
||||
spec:
|
||||
backoffLimit: 1
|
||||
activeDeadlineSeconds: 900
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: runner-gc
|
||||
restartPolicy: Never
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: gc
|
||||
image: {{ .Values.gc.image }}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
|
||||
# Iterate all forgejo-runner pods (golang, rust, node)
|
||||
PODS=$(kubectl -n {{ .Release.Namespace }} get pod \
|
||||
-l app -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels.app}{"\n"}{end}' \
|
||||
| grep 'forgejo-runner-' | awk '{print $1}')
|
||||
|
||||
if [ -z "$PODS" ]; then
|
||||
echo "no forgejo-runner pods found, skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for POD in $PODS; do
|
||||
echo "===== $POD ====="
|
||||
|
||||
# 1. Docker image prune (DinD sidecar)
|
||||
echo "[docker] before:"
|
||||
kubectl -n {{ .Release.Namespace }} exec "$POD" -c dind -- docker system df 2>/dev/null || true
|
||||
|
||||
echo "[docker] pruning non-latest images older than {{ .Values.gc.pruneAge }}..."
|
||||
# Keep :latest tagged images, delete all others older than pruneAge.
|
||||
# docker image prune can't filter by tag, so we list and selectively rmi.
|
||||
kubectl -n {{ .Release.Namespace }} exec "$POD" -c dind -- \
|
||||
sh -c '
|
||||
# Remove dangling (untagged) images older than {{ .Values.gc.pruneAge }}
|
||||
docker image prune -f --filter "until={{ .Values.gc.pruneAge }}" 2>/dev/null
|
||||
|
||||
# Remove tagged non-latest images older than {{ .Values.gc.pruneAge }}
|
||||
CUTOFF=$(date -d "-{{ .Values.gc.pruneAgeHours }} hours" +%s 2>/dev/null || date -v-{{ .Values.gc.pruneAgeHours }}H +%s)
|
||||
docker images --format "{{"{{"}} .Repository {{"}}"}}:{{"{{"}} .Tag {{"}}"}} {{"{{"}} .CreatedAt {{"}}"}}" | while read -r IMAGE_TAG CREATED_REST; do
|
||||
TAG=$(echo "$IMAGE_TAG" | rev | cut -d: -f1 | rev)
|
||||
# Skip latest-tagged images
|
||||
if [ "$TAG" = "latest" ]; then
|
||||
echo "[keep] $IMAGE_TAG (latest)"
|
||||
continue
|
||||
fi
|
||||
# Check image age via inspect
|
||||
CREATED_TS=$(docker inspect --format="{{"{{"}} .Created {{"}}"}}" "$IMAGE_TAG" 2>/dev/null | head -1)
|
||||
if [ -z "$CREATED_TS" ]; then continue; fi
|
||||
IMAGE_EPOCH=$(date -d "$CREATED_TS" +%s 2>/dev/null || date -jf "%Y-%m-%dT%H:%M:%S" "$(echo $CREATED_TS | cut -dT -f1-2 | cut -d. -f1)" +%s 2>/dev/null || echo 0)
|
||||
if [ "$IMAGE_EPOCH" -lt "$CUTOFF" ] 2>/dev/null; then
|
||||
echo "[delete] $IMAGE_TAG (older than {{ .Values.gc.pruneAge }})"
|
||||
docker rmi -f "$IMAGE_TAG" 2>/dev/null || true
|
||||
else
|
||||
echo "[keep] $IMAGE_TAG (recent)"
|
||||
fi
|
||||
done
|
||||
' 2>/dev/null || true
|
||||
|
||||
echo "[docker] pruning build cache unused >{{ .Values.gc.pruneAge }}..."
|
||||
kubectl -n {{ .Release.Namespace }} exec "$POD" -c dind -- \
|
||||
docker builder prune -af --filter "until={{ .Values.gc.pruneAge }}" 2>/dev/null || true
|
||||
|
||||
echo "[docker] pruning dangling volumes..."
|
||||
kubectl -n {{ .Release.Namespace }} exec "$POD" -c dind -- \
|
||||
docker volume prune -af 2>/dev/null || true
|
||||
|
||||
echo "[docker] after:"
|
||||
kubectl -n {{ .Release.Namespace }} exec "$POD" -c dind -- docker system df 2>/dev/null || true
|
||||
|
||||
# 2. Actcache cleanup (runner container)
|
||||
echo "[actcache] cleaning incomplete and stale cache entries..."
|
||||
kubectl -n {{ .Release.Namespace }} exec "$POD" -c runner -- \
|
||||
sh -c '
|
||||
# Delete incomplete/partial cache uploads immediately (tmp dirs)
|
||||
find /data/.cache/actcache/cache -name "tmp" -type d -exec rm -rf {} + 2>/dev/null || true
|
||||
# Delete cache entries not accessed in last {{ .Values.gc.actcacheMaxAgeDays }} day(s)
|
||||
find /data/.cache/actcache/cache -type f -mtime +{{ .Values.gc.actcacheMaxAgeDays }} -delete 2>/dev/null || true
|
||||
# Clean up empty directories
|
||||
find /data/.cache/actcache/cache -type d -empty -delete 2>/dev/null || true
|
||||
echo "actcache size: $(du -sh /data/.cache/actcache/cache 2>/dev/null | cut -f1)"
|
||||
' || true
|
||||
|
||||
echo ""
|
||||
done
|
||||
echo "GC complete"
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 128Mi
|
||||
{{- end }}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user