From 4f1c03df0819e41a5e917f38ae347a0a274c64eb Mon Sep 17 00:00:00 2001 From: Alkatrazz Date: Mon, 15 Jun 2026 14:04:19 +0200 Subject: [PATCH] feat(ghostfolio): suivi de portefeuille sur k8s (PG s01 + Redis) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nouvel app ArgoCD ghostfolio (namespace ai) : suivi/analyse de portefeuille boursier. Base PostgreSQL dédiée sur storage-01, Redis in-cluster pour le cache. Co-Authored-By: Claude Opus 4.8 --- admin/k8s/ghostfolio.md | 121 +++++++++++++++++++++ ansible/group_vars/all/vault.yml | 109 ++++++++++--------- ansible/roles/postgresql/defaults/main.yml | 7 +- k8s/apps-of-apps/apps/ghostfolio.yaml | 24 ++++ k8s/apps/ghostfolio/deployment.yaml | 71 ++++++++++++ k8s/apps/ghostfolio/ingress.yaml | 14 +++ k8s/apps/ghostfolio/redis.yaml | 57 ++++++++++ k8s/apps/ghostfolio/service.yaml | 11 ++ 8 files changed, 360 insertions(+), 54 deletions(-) create mode 100644 admin/k8s/ghostfolio.md create mode 100644 k8s/apps-of-apps/apps/ghostfolio.yaml create mode 100644 k8s/apps/ghostfolio/deployment.yaml create mode 100644 k8s/apps/ghostfolio/ingress.yaml create mode 100644 k8s/apps/ghostfolio/redis.yaml create mode 100644 k8s/apps/ghostfolio/service.yaml diff --git a/admin/k8s/ghostfolio.md b/admin/k8s/ghostfolio.md new file mode 100644 index 0000000..43a2c27 --- /dev/null +++ b/admin/k8s/ghostfolio.md @@ -0,0 +1,121 @@ +# Ghostfolio — Suivi & analyse de portefeuille + +Ghostfolio est l'outil de suivi de patrimoine du lab : actions, ETF, crypto. Il fournit +le suivi de performance (TWR / ROI), les dividendes, l'allocation, l'analyse de risque +(« X-Ray »). Open-source (AGPL-3.0), self-hosté, données dans PostgreSQL sur storage-01. + +--- + +## Accès + +| URL | `http://ghostfolio.lab.local` | +|---|---| +| Namespace k8s | `ai` | +| Port interne | `3333` | +| Données de marché | Yahoo Finance (gratuit, sans clé API) par défaut | + +--- + +## Architecture + +``` +ghostfolio (pod) → PostgreSQL 192.168.10.1:5432 (base « ghostfolio », hors cluster) + → ghostfolio-redis:6379 (cache, in-cluster) +``` + +- **PostgreSQL** : base + utilisateur `ghostfolio` créés par le rôle Ansible `postgresql` + (mot de passe vault `vault_pg_ghostfolio_password`). Schéma géré par Ghostfolio (migrations + Prisma au démarrage) — pas de table à créer à la main. +- **Redis** : déployé dans le cluster (`redis:7-alpine`, `k8s/apps/ghostfolio/redis.yaml`), + mot de passe via le même secret. Pas de persistance (cache uniquement). +- **Pas de PVC** : Ghostfolio est sans état, tout vit dans PostgreSQL + Redis. + +--- + +## Déploiement (ArgoCD) + +Manifests dans `k8s/apps/ghostfolio/` : +- `deployment.yaml` — pod Ghostfolio (:3333) +- `redis.yaml` — Deployment + Service Redis (cache) +- `service.yaml` — ClusterIP :3333 +- `ingress.yaml` — IngressRoute Traefik + +Application ArgoCD : `k8s/apps-of-apps/apps/ghostfolio.yaml` + +> Image épinglée sur `:latest` (cohérent avec n8n). Pour figer une version, remplacer +> par `ghostfolio/ghostfolio:` dans `deployment.yaml`. + +--- + +## Mise en service (ordre des opérations) + +### 1. Créer la base PostgreSQL sur storage-01 + +Ajouter le secret au vault Ansible (depuis la racine du repo — la cible fait le `cd` seule) : + +```bash +make vault-edit # ajouter : vault_pg_ghostfolio_password: "" +``` + +La base et l'utilisateur `ghostfolio` sont déjà déclarés dans +`ansible/roles/postgresql/defaults/main.yml`. Appliquer : + +```bash +ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags postgresql +``` + +### 2. Créer le secret k8s (non commité) + +```bash +kubectl create secret generic ghostfolio-secret -n ai \ + --from-literal=database-url="postgresql://ghostfolio:PG_PASSWORD@192.168.10.1:5432/ghostfolio?connect_timeout=300&sslmode=prefer" \ + --from-literal=redis-password="$(openssl rand -hex 16)" \ + --from-literal=access-token-salt="$(openssl rand -hex 32)" \ + --from-literal=jwt-secret-key="$(openssl rand -hex 32)" +``` + +- `PG_PASSWORD` = la valeur mise dans `vault_pg_ghostfolio_password` à l'étape 1. +- `redis-password` peut être n'importe quelle valeur aléatoire (Redis est interne au cluster). +- `access-token-salt` / `jwt-secret-key` doivent rester stables : les régénérer invaliderait + les sessions et les comptes existants. + +### 3. Pousser sur Git → ArgoCD synchronise + +```bash +git add k8s/apps/ghostfolio/ k8s/apps-of-apps/apps/ghostfolio.yaml \ + ansible/roles/postgresql/defaults/main.yml admin/k8s/ghostfolio.md +git commit -m "feat(ghostfolio): suivi de portefeuille sur k8s (PG s01 + Redis)" +git push +``` + +ArgoCD crée l'app (~3 min). Au premier démarrage Ghostfolio applique ses migrations Prisma. + +### 4. Créer le compte admin + +À la première connexion sur `http://ghostfolio.lab.local`, le premier utilisateur créé +devient administrateur. Saisie des positions ensuite via l'UI ou import CSV. + +--- + +## Dépannage + +```bash +# Logs du pod +kubectl logs -n ai deploy/ghostfolio --tail=50 + +# Vérifier l'accès PostgreSQL depuis le pod +kubectl exec -n ai deploy/ghostfolio -- env | grep -i database # (masqué : secret) + +# Santé applicative +kubectl exec -n ai deploy/ghostfolio -- wget -qO- http://localhost:3333/api/v1/health + +# Redis joignable ? +kubectl get pods -n ai -l app=ghostfolio-redis +``` + +- **CrashLoop au 1er démarrage** : souvent la base inaccessible (mot de passe `database-url` + ≠ `vault_pg_ghostfolio_password`, ou rôle `postgresql` pas réappliqué). Vérifier nftables + (port 5432 ouvert pour `192.168.10.0/24`, déjà le cas) et `pg_hba.conf` (host scram-sha-256 + pour le LAN cluster, déjà le cas). +- **Quotes/prix vides** : Yahoo Finance peut throttler ; les prix se mettent à jour via le + scheduler interne. Optionnellement ajouter une clé d'un autre data provider plus tard. diff --git a/ansible/group_vars/all/vault.yml b/ansible/group_vars/all/vault.yml index a25d1fd..b6f9693 100644 --- a/ansible/group_vars/all/vault.yml +++ b/ansible/group_vars/all/vault.yml @@ -1,54 +1,57 @@ $ANSIBLE_VAULT;1.1;AES256 -37346135383630373332363964303533363431646638376431346432313363663433303531663361 -6136353130363466366136343038666561616434616666380a646130616638623338636535353733 -36653434383264623166353462306631353163383266653232333066346235323962303862636130 -3133353333356562660a653034306334343737656162356564383130643061663363303030336232 -65646237303366373939663263383534396132623334316464653933376134303638633264326533 -61383639373265333238373036643939326432643931373465353734373132306138333363386363 -39326262646561643131303662303733666262616461326633386130666135336364343462623030 -63613330653565616337663236636464623365646462346439323466383730316462343465373464 -30353566633863646438346134333332646263343465333464353737666631373462356366666433 -36306462323537313832613533306230333663343465326536353035376332663939333061393035 -36646539646530343830373338353333643635653330363161353737653062383065633931646462 -62353133643137646236376365306136626537323665616637353734656430333763616165343966 -35636364616666633733303238353366356435393631663930363036653931333436623664663432 -64636431323435393765303339383961303535326464663034373766616135393939643263316661 -37353232636663666663333230313530613538353161373731613539306330633661613736373363 -30653037643162353634323365336431656533336262313633636432343037636336623132616431 -33633631323830333739303337373264353264343533646663386265366232613636396230323561 -64373231396132663566303737386230346534366638663730346434363535356636393135663735 -63386163356435613665633739376561633135346230643965666231343763626439613532346439 -31643337656639663463383237316438666464303133356235643837363961636262666238396431 -31343832616138643330623065633564393862396663336662636238613764323438333762363261 -31333736313062303131303065633235376463663932633066306135623436626334323938353662 -62336434303535356436353035313438643263623638346431356238356134366664393237376235 -37373333376162616537373931663864306331343937396238366362323938663839323232306539 -66626265376465326661363839393339393834393435373839646466366564376663396138623936 -62666264653338613433383763666561313234363962316131303866393036613531643538376365 -63303737303634383032623030373039363961323064626130356134323265623063623031356333 -64626437623964613136356437376163386635343966313731326334613035303730323664376461 -63366166303162353661393536343764383335663161353732636562346562643764356264613831 -38636339303938666663656132343032333233626430666461313764626462316366663933633230 -38343362663634623637343933316163303135613833353966623966386431373238383836643930 -64303138373661313938623030353139346236646134393737393536393938643232356163396632 -63393334343834393936376330323932623935376436666163323738383830343163326330363330 -64666536333561643434393063613165336332653938353036626361633466363935326536343237 -30353838376432346565613934366664643730623534353430343765346432633065383932386132 -63613762316139303861333733643464393337396635376330333561356537303533323663306639 -35326132626431643136326238666365393834373735383865663463653436316165373362356532 -64323964336238343966373334666264313636393035613134646166656532303465626366353763 -31366539643962313764346432373632383564383933653430303963663635346362616463663965 -30303931393265396565386330323132636635653237343336376338333538663462623532336331 -66333031333465396439336330303238383164623734653930646164303466366235323938646665 -62376336336136613361373764306265393033363934653263363639633638666565323236366431 -65306634633866393165383564336234336561643037656536363763373330393136383166333033 -34663063646232303666613262636363346135633536643538306466663233623934396664333131 -62643535663430666331666538336563323539656163323564396463623833323034613130623634 -62313236313435623332646664393039393931303566363737363832623431396334323435646263 -37393466303235383531643836386339323337356637336562343162656333633435313664336262 -65646231633665373230656537396539663831393339633263653737616133663737616434396130 -32393131363833346130303730643838653862356235356338646465363837353664303063383961 -65346530356432643437646133353832343034386339646634306165393163623730633764326461 -33633962333735376238306330646266383134393663616437363162333534653764346533643635 -30353837623235616666323334366438653834623633313039393662613732366139396266323263 -6432 +32653862643366616262386263376562353838323765646264376536636364383136643736303964 +6464393530333332333965353764356133323737653734620a643762373836666538333366386165 +36316433313361626238303838333039633036666239376666313762363862313139386364613832 +6134623365613839300a356563663766383965306530326331653731313132336438663438303062 +38633639306436326431376566383361356532616239303430373632343263646137393636343236 +36633232663766663933653263366633613930343066643334376462306564333935336538323364 +35356333626165623366346662353066613736666466306439363434633233633463646533386132 +35666263653462323539653762653037646239373637313465663639626361633931633336346437 +39303933636134653461393463333637633638343163383333316636393832373462643438326338 +33303765656235396164323935343231363032396164633036656538663266373535353938353437 +35383838353734393237373930316463323230383435383636623835316164353661356232663962 +37383764383563393365353364373335356330336363316636633737383130343333663735313233 +31373739373265366136613438393433326532316165653462616435386234653635333566646166 +64346330356465366464363734383734626132633462386261643133616236616564353935656138 +33386636643131326561353663663738626137343935323934306335383530363337373130636130 +65333961393564636264376437376439303165366464343465383064343630326637313162393034 +63656534626264343265663536646437306334333438396331623931323236646361333137613965 +62343033336232656533636138396430613935386130646662346435363838623235396665376539 +62646237643538306636653637303633626162633330356138623835653466616538613162643164 +36636364323661623531616262646362643864343837633633343133343964653336363232346163 +36373235373735633462646138643234626530653638623661313538303063393138636262626137 +37356638303331393766663730396637306335633766353932636333383863316535633339336138 +39626634623637653832343633363038663437663435643966326436313639633363383761366430 +64386664376435366433373862663963363166346664373031363034373162386166366439633439 +38303562376536393161616236306235303762393234386234616232646661373035326234666564 +35386432396263363539353263376439613933396435343261356137353838386163363362636361 +36653139393361396461623031643634316336363065623433646237623965306466336338343338 +33353331383462333066666238613932613433383261653331643239393862343431323265326138 +36356333323965626433383361383964313730386266643739306134663564313333363038386633 +61613935306165626361613564636138626533353666383034363336353031393062316337616135 +61353236386130313964323638653535316365373632333638313736353536343935666661383538 +35363535336566343431326232626438646530303862303033316335343066396661623830616231 +39383739643062663963643630626430393563366237356232376261323131666339386463383038 +36663038623831336164343866626664326332306634383662653366373061626139383032623063 +38643466336562353766393764333265356232353837346466366165353635663866643564386134 +39313433313465636161353261613039363865386634656637323063636263383339396631623638 +63356232623139323961383966346232643632386635393565333834626635653234633932343866 +66383539396537353466313166313735343936386463386261306466643234636239306263633366 +39616630376536613633663466323366303233653263353236396436643034353562653965346431 +36623638396431373761663363653239626635663335326637323031323761343037623634326531 +38383836383231356633313331313539346437356464666464306537663039316661356363653532 +36663734353066373364326166613064353763336461393463303239616564656165666139643432 +63303239343261353036353539363838633238613132326466326437333133643330383334636164 +36366436663133643365383330333639663730363662613165613033663533396438343161646332 +39633166326431333234633039613733656131643634663265396465323465373635653364653739 +34636533663461323439663930663937333663356230343061623339646130666434343361363233 +63613366663037613832653835663363626134653835633835323837626565663835616430643332 +35386236646465333836313338353439666338646130363064656632623433326632316437663238 +31613330653837313463303435366265396337626563343833613361633836663061646538613630 +61666330376130663337333937303134346163376561313138333163646463323131626330626462 +31653436666666363661393333353366396339643235663162613231626236356662376661613162 +64396535336634643661393830326630663964343036363566336533626565393763633238653935 +34356561393838636565336139386265316537616138353566393762373832626637356562633739 +34363461306662396365333331373339323537653365646539346338353066356235623435373662 +34313431346230663133306339323561333463393064383036363637656561333536623462616436 +36653730616536313265 diff --git a/ansible/roles/postgresql/defaults/main.yml b/ansible/roles/postgresql/defaults/main.yml index 8923acc..8eadc17 100644 --- a/ansible/roles/postgresql/defaults/main.yml +++ b/ansible/roles/postgresql/defaults/main.yml @@ -11,12 +11,14 @@ postgresql_shared_buffers: "256MB" # Bases et utilisateurs à créer # Mots de passe dans le vault : vault_pg_hermes_password, vault_pg_litellm_password, -# vault_pg_openwebui_password, vault_pg_n8n_password +# vault_pg_openwebui_password, vault_pg_n8n_password, +# vault_pg_ghostfolio_password postgresql_databases: - name: hermes - name: litellm - name: openwebui - name: n8n + - name: ghostfolio postgresql_users: - name: hermes @@ -31,3 +33,6 @@ postgresql_users: - name: n8n password: "{{ vault_pg_n8n_password }}" db: n8n + - name: ghostfolio + password: "{{ vault_pg_ghostfolio_password }}" + db: ghostfolio diff --git a/k8s/apps-of-apps/apps/ghostfolio.yaml b/k8s/apps-of-apps/apps/ghostfolio.yaml new file mode 100644 index 0000000..c9dfc96 --- /dev/null +++ b/k8s/apps-of-apps/apps/ghostfolio.yaml @@ -0,0 +1,24 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ghostfolio + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: git@github.com:Alkatrazz24/Funk-lab.git + targetRevision: main + path: k8s/apps/ghostfolio + directory: + recurse: true + destination: + server: https://kubernetes.default.svc + namespace: ai + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/k8s/apps/ghostfolio/deployment.yaml b/k8s/apps/ghostfolio/deployment.yaml new file mode 100644 index 0000000..cb721e1 --- /dev/null +++ b/k8s/apps/ghostfolio/deployment.yaml @@ -0,0 +1,71 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ghostfolio + namespace: ai +spec: + replicas: 1 + selector: + matchLabels: + app: ghostfolio + template: + metadata: + labels: + app: ghostfolio + spec: + containers: + - name: ghostfolio + image: ghostfolio/ghostfolio:latest + ports: + - containerPort: 3333 + env: + # PostgreSQL sur storage-01 (hors cluster) — chaîne complète dans le secret + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: ghostfolio-secret + key: database-url + # Redis (in-cluster) + - name: REDIS_HOST + value: "ghostfolio-redis" + - name: REDIS_PORT + value: "6379" + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: ghostfolio-secret + key: redis-password + # Secrets applicatifs Ghostfolio + - name: ACCESS_TOKEN_SALT + valueFrom: + secretKeyRef: + name: ghostfolio-secret + key: access-token-salt + - name: JWT_SECRET_KEY + valueFrom: + secretKeyRef: + name: ghostfolio-secret + key: jwt-secret-key + - name: PORT + value: "3333" + - name: TZ + value: "Europe/Paris" + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 1000m + memory: 1Gi + readinessProbe: + httpGet: + path: /api/v1/health + port: 3333 + initialDelaySeconds: 30 + periodSeconds: 15 + livenessProbe: + httpGet: + path: /api/v1/health + port: 3333 + initialDelaySeconds: 60 + periodSeconds: 30 diff --git a/k8s/apps/ghostfolio/ingress.yaml b/k8s/apps/ghostfolio/ingress.yaml new file mode 100644 index 0000000..0c79d03 --- /dev/null +++ b/k8s/apps/ghostfolio/ingress.yaml @@ -0,0 +1,14 @@ +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: ghostfolio + namespace: ai +spec: + entryPoints: + - web + routes: + - match: Host(`ghostfolio.lab.local`) + kind: Rule + services: + - name: ghostfolio + port: 3333 diff --git a/k8s/apps/ghostfolio/redis.yaml b/k8s/apps/ghostfolio/redis.yaml new file mode 100644 index 0000000..b67f5f2 --- /dev/null +++ b/k8s/apps/ghostfolio/redis.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ghostfolio-redis + namespace: ai +spec: + replicas: 1 + selector: + matchLabels: + app: ghostfolio-redis + template: + metadata: + labels: + app: ghostfolio-redis + spec: + containers: + - name: redis + image: redis:7-alpine + args: + - "--requirepass" + - "$(REDIS_PASSWORD)" + - "--maxmemory" + - "128mb" + - "--maxmemory-policy" + - "allkeys-lru" + ports: + - containerPort: 6379 + env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: ghostfolio-secret + key: redis-password + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 250m + memory: 192Mi + readinessProbe: + exec: + command: ["sh", "-c", "redis-cli -a \"$REDIS_PASSWORD\" ping | grep -q PONG"] + initialDelaySeconds: 5 + periodSeconds: 10 +--- +apiVersion: v1 +kind: Service +metadata: + name: ghostfolio-redis + namespace: ai +spec: + selector: + app: ghostfolio-redis + ports: + - port: 6379 + targetPort: 6379 diff --git a/k8s/apps/ghostfolio/service.yaml b/k8s/apps/ghostfolio/service.yaml new file mode 100644 index 0000000..69c24aa --- /dev/null +++ b/k8s/apps/ghostfolio/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: ghostfolio + namespace: ai +spec: + selector: + app: ghostfolio + ports: + - port: 3333 + targetPort: 3333