diff --git a/apps/ghostfolio-postgresql/values.yaml b/apps/ghostfolio-postgresql/values.yaml new file mode 100644 index 0000000..9017ae3 --- /dev/null +++ b/apps/ghostfolio-postgresql/values.yaml @@ -0,0 +1,16 @@ +auth: + username: ghostfolio + database: ghostfolio + existingSecret: ghostfolio-postgres + secretKeys: + adminPasswordKey: postgres-password + userPasswordKey: password + +primary: + persistence: + storageClass: rook-ceph-block + size: 8Gi + persistentVolumeClaimRetentionPolicy: + enabled: true + whenDeleted: Retain + whenScaled: Retain diff --git a/apps/ghostfolio-valkey/values.yaml b/apps/ghostfolio-valkey/values.yaml new file mode 100644 index 0000000..5377d4a --- /dev/null +++ b/apps/ghostfolio-valkey/values.yaml @@ -0,0 +1,23 @@ +fullnameOverride: ghostfolio-valkey + +dataStorage: + enabled: true + requestedSize: 2Gi + className: rook-ceph-block + keepPvc: true + +auth: + enabled: true + usersExistingSecret: ghostfolio-valkey + aclUsers: + default: + permissions: "~* &* +@all" + passwordKey: password + +readinessProbe: + enabled: true + +resources: + requests: + cpu: 25m + memory: 64Mi diff --git a/apps/ghostfolio/README.md b/apps/ghostfolio/README.md new file mode 100644 index 0000000..4bc8393 --- /dev/null +++ b/apps/ghostfolio/README.md @@ -0,0 +1,21 @@ +# Ghostfolio + +This Argo CD deployment runs in the dedicated `ghostfolio` namespace. Its +PostgreSQL and Valkey services are separate Argo Applications in that same +namespace rather than shared services from `default`. + +The upstream Ghostfolio chart currently lags the application release and its +cache dependency is Redis rather than Valkey. GitOps therefore uses the +chart's supported external-service settings to connect to the dedicated +same-namespace services, and overrides only the Ghostfolio image tag. The +release-specific image selection lives in `values.yaml`; remove that override +when the upstream chart's `appVersion` catches up. + +`ghostfolio-auth` and the database/cache credentials are strict-scope +SealedSecrets declared in `bootstrap/ghostfolio-secrets.yaml`. Never put their +cleartext values in this directory. + +The migration uses a logical dump/restore for the Ghostfolio database. The old +`default/postgresql` database is intentionally retained as a recovery copy; +do not drop it as routine cleanup. Valkey is a cache/queue dependency and is +started clean in its dedicated instance. diff --git a/apps/ghostfolio/values.yaml b/apps/ghostfolio/values.yaml new file mode 100644 index 0000000..74ae7f3 --- /dev/null +++ b/apps/ghostfolio/values.yaml @@ -0,0 +1,110 @@ +# The upstream chart has not kept up with Ghostfolio releases. This tag-only +# override selects the current application release while retaining the latest +# chart package; remove it once a newer chart appVersion catches up. +image: + repository: ghostfolio/ghostfolio + tag: 3.89.0 + +resources: + requests: + cpu: 25m + memory: 384Mi + limits: + cpu: "1" + memory: 1Gi + +startupProbe: + httpGet: + path: /api/v1/health + port: http + failureThreshold: 30 + periodSeconds: 10 + timeoutSeconds: 3 + +readinessProbe: + httpGet: + path: /api/v1/health + port: http + failureThreshold: 3 + periodSeconds: 10 + timeoutSeconds: 3 + +livenessProbe: + httpGet: + path: /api/v1/health + port: http + failureThreshold: 3 + periodSeconds: 30 + timeoutSeconds: 3 + +serviceAccount: + automount: false + +ghostfolio: + # These stable placeholders only satisfy the chart-generated Secret. The + # Deployment explicitly overrides both env vars from ghostfolio-auth below. + ACCESS_TOKEN_SALT: managed-by-sealed-secrets + JWT_SECRET_KEY: managed-by-sealed-secrets + BASE_CURRENCY: EUR + existingSecret: ghostfolio-auth + +postgresql: + enabled: false + +externalPostgresql: + host: ghostfolio-postgresql.ghostfolio.svc.cluster.local + port: 5432 + auth: + username: ghostfolio + database: ghostfolio + existingSecret: ghostfolio-postgres + secretKeys: + userPasswordKey: password + options: connect_timeout=300&sslmode=disable + +redis: + enabled: false + +externalRedis: + host: ghostfolio-valkey.ghostfolio.svc.cluster.local + port: 6379 + auth: + enabled: true + existingSecret: ghostfolio-valkey + existingSecretPasswordKey: password + +ingress: + enabled: false + className: traefik + hosts: + - host: ghostfolio.brunner.ninja + paths: + - path: / + pathType: ImplementationSpecific + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + +extraManifests: + - apiVersion: monitoring.coreos.com/v1 + kind: PrometheusRule + metadata: + name: ghostfolio + labels: + release: prometheus + spec: + groups: + - name: ghostfolio + rules: + - alert: GhostfolioUnavailable + expr: kube_deployment_status_replicas_available{namespace="ghostfolio", deployment="ghostfolio"} < 1 + for: 5m + labels: + severity: critical + annotations: + summary: Ghostfolio has no available replicas + - alert: GhostfolioContainerRestarting + expr: increase(kube_pod_container_status_restarts_total{namespace="ghostfolio", container="ghostfolio"}[15m]) > 2 + labels: + severity: warning + annotations: + summary: Ghostfolio is repeatedly restarting diff --git a/bootstrap/ghostfolio-namespace.yaml b/bootstrap/ghostfolio-namespace.yaml new file mode 100644 index 0000000..0e1e264 --- /dev/null +++ b/bootstrap/ghostfolio-namespace.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: ghostfolio + annotations: + argocd.argoproj.io/sync-wave: "-2" + argocd.argoproj.io/sync-options: Prune=false diff --git a/bootstrap/ghostfolio-postgresql.yaml b/bootstrap/ghostfolio-postgresql.yaml new file mode 100644 index 0000000..e64daaf --- /dev/null +++ b/bootstrap/ghostfolio-postgresql.yaml @@ -0,0 +1,29 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ghostfolio-postgresql + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + project: ghostfolio + sources: + - repoURL: registry-1.docker.io/bitnamicharts + chart: postgresql + targetRevision: 18.6.2 + helm: + releaseName: ghostfolio-postgresql + valueFiles: [$values/apps/ghostfolio-postgresql/values.yaml] + - repoURL: https://code.brunner.ninja/feedc0de/homelab-gitops.git + targetRevision: main + ref: values + destination: + server: https://kubernetes.default.svc + namespace: ghostfolio + syncPolicy: + automated: + enabled: true + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/bootstrap/ghostfolio-project.yaml b/bootstrap/ghostfolio-project.yaml new file mode 100644 index 0000000..c0dd309 --- /dev/null +++ b/bootstrap/ghostfolio-project.yaml @@ -0,0 +1,30 @@ +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: ghostfolio + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "-2" +spec: + description: Ghostfolio and its private PostgreSQL and Valkey dependencies + sourceRepos: + - https://code.brunner.ninja/feedc0de/homelab-gitops.git + - https://bythehugo.github.io/ghostfolio-helm/ + - registry-1.docker.io/bitnamicharts + - https://valkey.io/valkey-helm/ + destinations: + - namespace: ghostfolio + server: https://kubernetes.default.svc + namespaceResourceWhitelist: + - {group: "", kind: ConfigMap} + - {group: "", kind: PersistentVolumeClaim} + - {group: "", kind: Secret} + - {group: "", kind: Service} + - {group: "", kind: ServiceAccount} + - {group: "", kind: Pod} + - {group: apps, kind: Deployment} + - {group: apps, kind: StatefulSet} + - {group: networking.k8s.io, kind: Ingress} + - {group: networking.k8s.io, kind: NetworkPolicy} + - {group: policy, kind: PodDisruptionBudget} + - {group: monitoring.coreos.com, kind: PrometheusRule} diff --git a/bootstrap/ghostfolio-secrets.yaml b/bootstrap/ghostfolio-secrets.yaml new file mode 100644 index 0000000..7c83105 --- /dev/null +++ b/bootstrap/ghostfolio-secrets.yaml @@ -0,0 +1,52 @@ +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + name: ghostfolio-auth + namespace: ghostfolio + annotations: + argocd.argoproj.io/sync-wave: "-1" + argocd.argoproj.io/sync-options: Prune=false +spec: + encryptedData: + ACCESS_TOKEN_SALT: AgAzHuqHA04hdtOs5WB7eh80PlRUTtO4QMwCvCv+8AH+mSy6dVcYnUBNBk0TB7kfC2XF7TeUxFxXaXyD18DRi2emps6loJk9VlTM4O09HOb2wnEcZhx5gDRv/QYDAa3aYnQCF4I67J7B0JpcvU0ICp6AYw/yU5ZAOxlkkflS1kGLbUusg/Fzg8u+SgXpCKyGdPMynGWLTnjm8EbnprvnYrfGnP8YV+QqK/YJ62AXC9viTNNUlIgm2eGMs5ZBOZkWgbyRyJccQZoyLHQ1o3tVTzidjtDZQEDqB5vk3cfy/zSdcyV1MfLBNHEayasjZNqxt3aJBSgN4kFTqr4/C2CdxkEggnSFS3Cqkx9GPRWOFkni3QHzBsAoUnIuyd+9OUV4cbUDXCo+ozXm3Ojpfb7z7NaTDa59Dx0H8D3fo1+g2XbrYmR7cA0k65uzilMymerGEY2yioSSGGqRzQe3vIwbd2sA716NGRW02bJipN0iz12jcsWSytPAoN/7K+ekSLC0t+53KTN87XdPAP/pQe5dLaXPHorw/0B+ni5RNP6qfuwVAO18kV1XTL7zmJtl/l2cW5oygI7dMMJtQK9tQ1YS2iPg5CKCbraWF/dmLybe3RvXw8gS23KZanASPm5iRPtUqDtTV9BLdP3kxvzUhxI9fDUqBYeY6dzLRkjc/o+jMFJlWkL1vG53pb8WmwjyW0zvp7X0SEPfFMVM4IyyvnkHYxfGcxOIQ/thdIgbLQgFYROewg== + JWT_SECRET_KEY: AgCDVmZw7z39PskhW+IdHBb2I584hRDbhDIXNK+7vUmL5Y6NZullKX5NFK2yL7Wiairx2EwYu2Dkjm8WRf2wGUJfq+WHYhQrsATSYSvjXWRTYxddnP8zRCP3cnfkgAKjqRuP7D5Q2PYTfLMAUtquWf74HbLNmfQhm3Wbtl9y5y/Xf+IS7wVwwrlTDmjrQ0M4oMWG5N6c8naJc4pFL4OCMDHW9VfVLisiRf4AwZbyEVUrYw2CzJTh8Ik7ZXzsKMnNisrCkYzf125JV55mXNnyQuAye7cyk6mJ0qJGE14/7ctOeQGWXUy+MFxWeYi4gwKWBEDOPMFghEvEq7cBEzElwsA4pT6J9L5B+hTbIWe24PbXtlREv73dcu9yVXuEUDq5gKwHMv3PwObxH3t0fB20emPmQpoMQOpxEOeBbnEY4j5+/coFGYANPCEGFvvzynn+hO3/Oj6eOHuKWnKnzBSFVDA/RoeCLlbTL/u7ft35Eq63JdhFXtMbvjnIj+4bNjGYFHluNzhSkrbDlPuQZsZH2J02o6Nl36gvrtxABhyL9lqjAMy1O/4nH/ojv3k99n4R2fsRxPC9eUbywhIq1WN2PxjqO45l7SYdDcQAnfV1h3TM7xp9IyNRw0q/NFp9qWvazzxSQmMYapcuTatlekz6bcVbQ624dHpJ8QB5M0oo1P43k6LhS/F3RIep/ZM2KaBRgNtVi/+nvuw4LnpwLYzTthCY8PAKFtvoiy/B8f8aX+fLgw== + template: + metadata: + name: ghostfolio-auth + namespace: ghostfolio + type: Opaque +--- +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + name: ghostfolio-postgres + namespace: ghostfolio + annotations: + argocd.argoproj.io/sync-wave: "-1" + argocd.argoproj.io/sync-options: Prune=false +spec: + encryptedData: + password: AgCS6+Vj4Q5UmefnmSYT++ADHwA49PHssdJHwgBqcn33HCiHOyUBA+Rk7IVC6B5uFRSXYOLe4iWXLOV8kEXhI25H9zR7WW4Ms7cANHETX6GpuqKU8z1eoV92l9jk1H/FWoB5Sm7D+WPPGYUiWA0SzzrLE95J+2Og1Crgdzg+k+3NmkvWt40q8GuXrYFzGfisBNbdsjzbvWcdPqFkvx/sK1sw0YLbbnsoygg3gO3e1vUXgilgqKbbuNGc5zg2rtAfOGbkxW2S+w4kBkNAGYwB1KU8J9mUey8T2HROGptZ2huuzxbvrHNn1YJpLQvDdkpc6eKGu+UPJwWeksyM7GHm6r6YBF6LEfeOjzwePowa4+xfsg9AJG19m5LJGq8VmXH4gCaKzs1JZeeSO14IWJ/GrbTrIXd5zLnqnG/1CSHCOdSam+mXrusk7iXDVZEy9nSJRwmY7OueTMMCQdZHJGInstaTv7nvTw4ZKk68AltPQhGXyjl3G3rfy0qpfSBwFRBZcZRAexLeNyoVSHn+F0CgQU8uMEh3Q9xdWZ2w+nj/csOSRhTh6EdSeE7gzlK2MGUq8hXZ0RHwtnVwtrURxhiWSKgM9Rjv0MJWx7AqwCg9WeU0m3wA7rx4tKH1/6WJvqlN2tKBxwsxJfzKlgHqciNt15H9++ER0hU9Izi+iHgKhN448V9AR56ekLUtTfH1luZIUOJ8x2DgYYzg5yuv/xw6MQS5zTYpuscQy33JorWIpYOi5IJoLc5sgF6IfDweDf2SSSGmhJJzWyAoTCbE4ZzRcUv8 + postgres-password: AgAt6iXwa5o6U8H+LuOT9SYpqxoOa8QFRxbgjRUwkOjtYgEmwyW3jmTZFirr8Uw7bt6YGhyYR/evzSLgemx84JQrM0hLEzVmwzwsbTNiKZ9vVuZsaP25Lm7CTZ6tK1OLWlQP2Zu3xRrCDTwObYMUuucdaX10oY+vryyVMUJHg4yLN8M4SdFo5NPEWoEkoF7X/eoR7yb2aRkSEAwr/L1mBRHAhPZVDp5euaGXSbdLzwnO/esAdI4T+WnnAmqnaLg6oVDSgNx9Vwn6ztWjzW/d47YxulSF3Vt2byJA//6+YZpzanKA7zRWqWYYIgYkoEdymZA+zFGRQJPetuxPNg4oZYYgFcmbz6ukquUwAYF+XVTtyoq4/PLv3c5yZyLtzVioNNHyJGBvEQds5IM54Hpmtz8x/dqE10LHnDj+7qmZ+CDNAwXr+YiiPdbktL3BjhZpKKQ5HSlopzPv6H/CnGezLNXdiM7bScpVgreUlpqBS2JPVlOEFuHntOvRtk8DXYXvr7A2yLTeUYsNgJ5dB4ii6yHDUJWkQ4f98c/FmN5hEWsWHLVxaJG3Cz4tBOq2Arr56urkZ4vePssYIRVbUwynX3jB0205kmxiZJZHC+WODd1vsIV+ySxIQQ9elj48K0ZMd5rniejeBtzMtCtXhdFtfqRZjKkRHiSBXPByZj1QPZFjz85YA3Nugmg2G6tehpTp/WhkA7+R1wj5OIgklctIlb4SZPBml38sr3FN6m7PB065ZvSAJkeHjQvFyj2JGZrN1Pf54Zz6eL3ccfErbXOX/Lfh + template: + metadata: + name: ghostfolio-postgres + namespace: ghostfolio +--- +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + name: ghostfolio-valkey + namespace: ghostfolio + annotations: + argocd.argoproj.io/sync-wave: "-1" + argocd.argoproj.io/sync-options: Prune=false +spec: + encryptedData: + password: AgBeG/YIqHBE4hj7lWqxvhgbq868dlJ/VrHdAeYerV6Qr/nOtIcUvSpH59KsnR7REt0TxQuGkJHsh+GkhCFQC5cknddWkEu1jaFwXQC2Kt97okzNRR6tC4dYVbcMOrJMrCgSeROg402k4jai+GQeqOc/a6F1E4koDtIV5I+G/pa4ryWJ54L4YRQtHE3zwxMU0OCt5UxJ2/O5e1jOzWLLMVVzW4N0BWm21XKgnt8sywmsLEkVWZteIz8vqINL2gdZtLLVmREhk2SiuyW6wBbXaMEIpB/QcoqaxjFIEO6BM93DbiSuGXcbaMlMGuGq/Vef7mJEZSQGjpEPslBd8qhDKqxDNOg+y617MuByz3EQ3jf3WD6Sg8PbFcnoliUelwNKG0aYPysOZeqocywv3HCHIOAz5Yx8KnM3KqOu4aX+E6iUDrP77Fwlk+7e4oUpE9aErTwJohDcSayfqeGXKcHsXi8nrBotzbxZ09ETZvp/fVWrp2pjfXnAtBUm/pleLt4LKx3FaM4wqnQ4LK00WXcG4/h0Oa8nEMkn0iy6xFvUAKD72sy7dKs4JaT+Vw6rSD0i2JpjQYzY7Ddy2lHHTd0DnO++hzd5Tx9YTTDHQ7XOX2jXCiHBJASWb2NiO153fSi158wURLMz+9LMi/oC7uhFqubDSfE9SoRsCmPme3MKKR9XWj7jqw6CImQUQymQTYskyra6ElAoK67jH4klwO0zN/CXGo7VrrqdLmoT+JDIpCup0vIPKt7f/d3fKdY3lkSptHtTeS8FIpXcYbmYoJK8Msa6 + template: + metadata: + name: ghostfolio-valkey + namespace: ghostfolio diff --git a/bootstrap/ghostfolio-valkey.yaml b/bootstrap/ghostfolio-valkey.yaml new file mode 100644 index 0000000..2a3a374 --- /dev/null +++ b/bootstrap/ghostfolio-valkey.yaml @@ -0,0 +1,29 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ghostfolio-valkey + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + project: ghostfolio + sources: + - repoURL: https://valkey.io/valkey-helm/ + chart: valkey + targetRevision: 0.12.0 + helm: + releaseName: ghostfolio-valkey + valueFiles: [$values/apps/ghostfolio-valkey/values.yaml] + - repoURL: https://code.brunner.ninja/feedc0de/homelab-gitops.git + targetRevision: main + ref: values + destination: + server: https://kubernetes.default.svc + namespace: ghostfolio + syncPolicy: + automated: + enabled: true + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/bootstrap/ghostfolio.yaml b/bootstrap/ghostfolio.yaml new file mode 100644 index 0000000..2957008 --- /dev/null +++ b/bootstrap/ghostfolio.yaml @@ -0,0 +1,25 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ghostfolio + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "1" +spec: + project: ghostfolio + sources: + - repoURL: https://bythehugo.github.io/ghostfolio-helm/ + chart: ghostfolio + targetRevision: 0.5.4 + helm: + releaseName: ghostfolio + valueFiles: [$values/apps/ghostfolio/values.yaml] + - repoURL: https://code.brunner.ninja/feedc0de/homelab-gitops.git + targetRevision: main + ref: values + destination: + server: https://kubernetes.default.svc + namespace: ghostfolio + syncPolicy: + syncOptions: + - CreateNamespace=true diff --git a/bootstrap/kustomization.yaml b/bootstrap/kustomization.yaml index 19ff965..9270ef9 100644 --- a/bootstrap/kustomization.yaml +++ b/bootstrap/kustomization.yaml @@ -94,3 +94,9 @@ resources: - elastic.yaml - coredns-project.yaml - coredns.yaml +- ghostfolio-project.yaml +- ghostfolio-namespace.yaml +- ghostfolio-secrets.yaml +- ghostfolio-postgresql.yaml +- ghostfolio-valkey.yaml +- ghostfolio.yaml