diff --git a/README.md b/README.md index 213ced0..ba3c22f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # 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. +Personal/homelab apps (not cluster infra, not shared platform tooling) — +[Ignis](https://github.com/Nystik-gh/ignis) (self-hosted browser-based +Obsidian) and `actual-budget-exporter` (Prometheus exporter for a +docker-compose-hosted Actual Budget instance). ## Architecture @@ -71,3 +72,31 @@ kubectl logs -n ignis deploy/ignis # first boot downloads Obsidian, 1-2 min ``` Visit `http://ignis.fireflylab.local` once the pod is Ready. + +## actual-budget-exporter + +- Image: `docker.io/sakowicz/actual-budget-prometheus-exporter:latest` — + Prometheus exporter for [Actual Budget](https://actualbudget.org/), no + official Helm chart, chart here is self-authored. +- `actual_server` itself is **not** in this cluster — it's a plain + docker-compose container on the homelab host + (`homelab-docker-compose-prod/actualbudget`), published on the host LAN IP. + `values.yaml` points `ACTUAL_SERVER_URL` at that host IP:port, same as + `actual-http-api` does in that repo. +- No HTTPRoute — this only serves `/metrics`. A `ServiceMonitor` (labeled + `release: kube-prometheus-stack` to match that stack's default selector) + gets it scraped by the cluster Prometheus instead. +- `ACTUAL_PASSWORD` / `ACTUAL_BUDGET_ID_1` are **not** in `values.yaml` — + plaintext Actual credentials don't belong in a git-committed file. They + come from a Secret you create by hand once, after the Application syncs + and the `actualbudget` namespace exists: + + ```bash + kubectl create secret generic actual-budget-exporter-secrets \ + -n actualbudget \ + --from-literal=ACTUAL_PASSWORD='' \ + --from-literal=ACTUAL_BUDGET_ID_1='' + ``` + + Restart the deployment after creating/rotating it: + `kubectl rollout restart deployment/actual-budget-exporter -n actualbudget`. diff --git a/apps/actual-budget-exporter/application.yaml b/apps/actual-budget-exporter/application.yaml new file mode 100644 index 0000000..7632014 --- /dev/null +++ b/apps/actual-budget-exporter/application.yaml @@ -0,0 +1,17 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: actual-budget-exporter + namespace: argocd +spec: + project: default + source: + repoURL: https://gitea.fireflylab.cc/duynguyen/homelab-services.git + targetRevision: main + path: apps/actual-budget-exporter/chart + destination: + server: https://kubernetes.default.svc + namespace: actualbudget + syncPolicy: + syncOptions: + - CreateNamespace=true diff --git a/apps/actual-budget-exporter/chart/Chart.yaml b/apps/actual-budget-exporter/chart/Chart.yaml new file mode 100644 index 0000000..36ed1f2 --- /dev/null +++ b/apps/actual-budget-exporter/chart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: actual-budget-exporter +description: Prometheus exporter for Actual Budget (https://github.com/sakowicz/actual-budget-prometheus-exporter) +type: application +version: 0.1.0 +appVersion: "latest" diff --git a/apps/actual-budget-exporter/chart/templates/deployment.yaml b/apps/actual-budget-exporter/chart/templates/deployment.yaml new file mode 100644 index 0000000..0bb282e --- /dev/null +++ b/apps/actual-budget-exporter/chart/templates/deployment.yaml @@ -0,0 +1,52 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ .Release.Name }} + spec: + containers: + - name: actual-budget-exporter + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: metrics + containerPort: {{ .Values.service.port }} + env: + {{- range $k, $v := .Values.env }} + - name: {{ $k }} + value: {{ $v | quote }} + {{- end }} + - name: ACTUAL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.secret.name }} + key: ACTUAL_PASSWORD + - name: ACTUAL_BUDGET_ID_1 + valueFrom: + secretKeyRef: + name: {{ .Values.secret.name }} + key: ACTUAL_BUDGET_ID_1 + resources: + {{- toYaml .Values.resources | nindent 12 }} + # No documented dedicated health endpoint; /metrics is the route + # that's always up once the exporter has started. + readinessProbe: + httpGet: + path: /metrics + port: {{ .Values.service.port }} + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /metrics + port: {{ .Values.service.port }} + initialDelaySeconds: 15 + periodSeconds: 20 diff --git a/apps/actual-budget-exporter/chart/templates/service.yaml b/apps/actual-budget-exporter/chart/templates/service.yaml new file mode 100644 index 0000000..cac414b --- /dev/null +++ b/apps/actual-budget-exporter/chart/templates/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }} + labels: + app: {{ .Release.Name }} +spec: + type: {{ .Values.service.type }} + selector: + app: {{ .Release.Name }} + ports: + - name: metrics + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.port }} diff --git a/apps/actual-budget-exporter/chart/templates/servicemonitor.yaml b/apps/actual-budget-exporter/chart/templates/servicemonitor.yaml new file mode 100644 index 0000000..f6ad1b4 --- /dev/null +++ b/apps/actual-budget-exporter/chart/templates/servicemonitor.yaml @@ -0,0 +1,22 @@ +{{- if .Values.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ .Release.Name }} + labels: + # kube-prometheus-stack's default serviceMonitorSelector only picks up + # ServiceMonitors carrying this label (release name of that Helm + # release, set by its ArgoCD Application name in cluster-platform). + release: kube-prometheus-stack +spec: + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + app: {{ .Release.Name }} + endpoints: + - port: metrics + path: /metrics + interval: {{ .Values.serviceMonitor.interval }} +{{- end }} diff --git a/apps/actual-budget-exporter/chart/values.yaml b/apps/actual-budget-exporter/chart/values.yaml new file mode 100644 index 0000000..34b953a --- /dev/null +++ b/apps/actual-budget-exporter/chart/values.yaml @@ -0,0 +1,35 @@ +image: + repository: docker.io/sakowicz/actual-budget-prometheus-exporter + tag: latest + pullPolicy: IfNotPresent + +replicaCount: 1 + +service: + type: ClusterIP + port: 3001 + +# actual_server is NOT in this cluster — it's a plain docker-compose +# container on the homelab host (see homelab-docker-compose-prod/actualbudget), +# published on the host LAN IP. Reach it the same way actual-http-api does. +env: + ACTUAL_SERVER_URL: "http://192.168.1.41:8002" + # ACTUAL_BUDGET_NAME_1: "" # optional, adds a friendly name to the prometheus label + # ACTUAL_E2E_PASSWORD_1: "" # optional, only if E2E encryption is enabled on the budget + +# ACTUAL_PASSWORD and ACTUAL_BUDGET_ID_1 are NOT set here — plaintext Actual +# credentials in a git-committed values.yaml is not acceptable. They're read +# from a Secret you create by hand once (see README.md), never committed. +secret: + name: actual-budget-exporter-secrets + +resources: + requests: + cpu: 50m + memory: 64Mi + limits: + memory: 128Mi + +serviceMonitor: + enabled: true + interval: 30s