commit 57dcecedb748b414e5ecf8be7e6e0f054ed30464 Author: duynguyen Date: Mon Aug 3 00:38:30 2026 +0700 initial: homelab-services app-of-apps, ignis service diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4309daf --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +.DS_Store +*.swp +CLAUDE.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..794665c --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# Homelab Service — Apps + +Third bootstrap layer, run after `cluster-bootstrap` and `cluster-platform`. +Personal/homelab apps (not cluster infra, not shared platform tooling) — first +one here is [Ignis](https://github.com/Nystik-gh/ignis), a self-hosted +browser-based Obsidian. + +## Architecture + +Same app-of-apps pattern as `cluster-platform`: + +``` +homelab-app.yaml ← root Application, applied once by hand (kubectl apply) +apps// + application.yaml ArgoCD Application + chart/ self-authored Helm chart (no upstream chart to point at) +``` + +`homelab-app.yaml` watches `apps/*/application.yaml` (recurse) only. + +## Bootstrap + +```bash +kubectl apply -f homelab-app.yaml +``` + +## Ignis + +- Image: `nobbe/ignis:latest` — no official Helm chart, chart here is + self-authored from the docker-compose example in the upstream repo. +- 3 PVCs on `nfs-delete`: `ignis-vaults` (your actual vault data), + `ignis-data` (plugin config/state), `ignis-obsidian-app` (downloaded + Obsidian binary, avoids re-download on every restart). +- Single replica only — app has an in-process file watcher + write coalescer, + not built for multiple instances sharing a vault concurrently. +- `ignis-vaults` PVC has `argocd.argoproj.io/sync-options: Delete=false` — the + `nfs-delete` StorageClass has `reclaimPolicy: Delete`, so without this + annotation an accidental prune (app removed from git, or `helm uninstall`) + would delete your vault data on the NAS. The annotation only protects + against ArgoCD prune, not `kubectl delete pvc` by hand. +- No dedicated health endpoint upstream; probes hit `/api/version` (only + documented stable route once the server is up). +- **No built-in auth** (upstream docs explicitly warn about this). HTTPRoute + is exposed at `ignis.fireflylab.local` with no auth in front — same + LAN-only tradeoff already accepted for Vault in `cluster-platform`. Add an + Envoy Gateway `SecurityPolicy` (basic auth) later if that's not enough. + +### Migrating your existing vault + +The `ignis-vaults` PVC is backed by `nfs-delete` (NFS subdir provisioner) — +data physically lives on the NAS, not on any worker node. After the PVC first +binds, a subdir appears under the NAS export +(`/ignis--`); mount that export directly (NFS/SMB +client) or `kubectl cp` your existing vault folder into the running pod's +`/vaults` mount. + +### First login / verification + +```bash +kubectl get pods -n ignis +kubectl logs -n ignis deploy/ignis # first boot downloads Obsidian, 1-2 min +``` + +Visit `http://ignis.fireflylab.local` once the pod is Ready. diff --git a/apps/ignis/application.yaml b/apps/ignis/application.yaml new file mode 100644 index 0000000..ca64cb8 --- /dev/null +++ b/apps/ignis/application.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ignis + namespace: argocd +spec: + project: default + source: + repoURL: https://gitea.fireflylab.cc/duynguyen/homelab-services.git + targetRevision: main + path: apps/ignis/chart + destination: + server: https://kubernetes.default.svc + namespace: ignis + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/apps/ignis/chart/Chart.yaml b/apps/ignis/chart/Chart.yaml new file mode 100644 index 0000000..920ec4f --- /dev/null +++ b/apps/ignis/chart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: ignis +description: Self-hosted browser-based Obsidian (https://github.com/Nystik-gh/ignis) +type: application +version: 0.1.0 +appVersion: "latest" diff --git a/apps/ignis/chart/templates/deployment.yaml b/apps/ignis/chart/templates/deployment.yaml new file mode 100644 index 0000000..c1e42bf --- /dev/null +++ b/apps/ignis/chart/templates/deployment.yaml @@ -0,0 +1,60 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + type: Recreate # RWO volumes, single instance — never run 2 pods at once + selector: + matchLabels: + app: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ .Release.Name }} + spec: + containers: + - name: ignis + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.service.port }} + env: + {{- range $k, $v := .Values.env }} + - name: {{ $k }} + value: {{ $v | quote }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: vaults + mountPath: /vaults + - name: data + mountPath: /app/data + - name: obsidian-app + mountPath: /app/obsidian-app + # No dedicated health endpoint documented upstream; /api/version + # is the only stable route that responds once the server is up. + readinessProbe: + httpGet: + path: /api/version + port: {{ .Values.service.port }} + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /api/version + port: {{ .Values.service.port }} + initialDelaySeconds: 90 # first boot downloads Obsidian, can take 1-2min + periodSeconds: 20 + volumes: + - name: vaults + persistentVolumeClaim: + claimName: {{ .Release.Name }}-vaults + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-data + - name: obsidian-app + persistentVolumeClaim: + claimName: {{ .Release.Name }}-obsidian-app diff --git a/apps/ignis/chart/templates/httproute.yaml b/apps/ignis/chart/templates/httproute.yaml new file mode 100644 index 0000000..b99df77 --- /dev/null +++ b/apps/ignis/chart/templates/httproute.yaml @@ -0,0 +1,22 @@ +{{- if .Values.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ .Release.Name }} + # No built-in auth in Ignis itself — LAN-only exposure, same tradeoff + # already accepted for Vault in cluster-platform. +spec: + parentRefs: + - name: envoy-gateway + namespace: envoy-gateway-system + hostnames: + - {{ .Values.httpRoute.hostname | quote }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: {{ .Release.Name }} + port: {{ .Values.service.port }} +{{- end }} diff --git a/apps/ignis/chart/templates/pvc.yaml b/apps/ignis/chart/templates/pvc.yaml new file mode 100644 index 0000000..fb77a2e --- /dev/null +++ b/apps/ignis/chart/templates/pvc.yaml @@ -0,0 +1,40 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-vaults + annotations: + # nfs-delete StorageClass = reclaimPolicy Delete. This PVC holds your real + # vault data — refuse to let ArgoCD prune it even if removed from git. + # You still need to delete it by hand if you ever really want to. + argocd.argoproj.io/sync-options: Delete=false +spec: + accessModes: + - ReadWriteOnce + storageClassName: {{ .Values.persistence.vaults.storageClass }} + resources: + requests: + storage: {{ .Values.persistence.vaults.size }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-data +spec: + accessModes: + - ReadWriteOnce + storageClassName: {{ .Values.persistence.data.storageClass }} + resources: + requests: + storage: {{ .Values.persistence.data.size }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-obsidian-app +spec: + accessModes: + - ReadWriteOnce + storageClassName: {{ .Values.persistence.obsidianApp.storageClass }} + resources: + requests: + storage: {{ .Values.persistence.obsidianApp.size }} diff --git a/apps/ignis/chart/templates/service.yaml b/apps/ignis/chart/templates/service.yaml new file mode 100644 index 0000000..f63576a --- /dev/null +++ b/apps/ignis/chart/templates/service.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }} +spec: + selector: + app: {{ .Release.Name }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.port }} diff --git a/apps/ignis/chart/values.yaml b/apps/ignis/chart/values.yaml new file mode 100644 index 0000000..0cd16a1 --- /dev/null +++ b/apps/ignis/chart/values.yaml @@ -0,0 +1,39 @@ +image: + repository: nobbe/ignis + tag: latest + pullPolicy: IfNotPresent + +# Ignis is a single-process app (in-memory file watcher + write coalescer, +# no clustering) — do not scale beyond 1 replica, RWO storage is enough. +replicaCount: 1 + +service: + port: 8080 + +env: + PUID: "1000" + PGID: "1000" + # WRITE_COALESCE_MS: "500" # uncomment if NFS write latency causes issues + +resources: + requests: + cpu: 250m + memory: 512Mi + limits: + memory: 1Gi + +persistence: + # your existing Obsidian vault goes here after PVC binds (see repo NFS path) + vaults: + storageClass: nfs-delete + size: 20Gi + data: + storageClass: nfs-delete + size: 1Gi + obsidianApp: + storageClass: nfs-delete + size: 2Gi + +httpRoute: + enabled: true + hostname: ignis.fireflylab.local diff --git a/homelab-app.yaml b/homelab-app.yaml new file mode 100644 index 0000000..3ab6be5 --- /dev/null +++ b/homelab-app.yaml @@ -0,0 +1,21 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: homelab + namespace: argocd +spec: + project: default + source: + repoURL: https://gitea.fireflylab.cc/duynguyen/homelab-services.git + targetRevision: main + path: apps + directory: + recurse: true + include: "*/application.yaml" + destination: + server: https://kubernetes.default.svc + namespace: argocd + syncPolicy: + automated: + prune: true + selfHeal: true