feat(ghostfolio): suivi de portefeuille sur k8s (PG s01 + Redis)

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 <noreply@anthropic.com>
This commit is contained in:
Alkatrazz 2026-06-15 14:04:19 +02:00
parent 0aa4c3650d
commit 4f1c03df08
8 changed files with 360 additions and 54 deletions

121
admin/k8s/ghostfolio.md Normal file
View file

@ -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:<tag>` 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: "<mot de passe fort>"
```
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.

View file

@ -1,54 +1,57 @@
$ANSIBLE_VAULT;1.1;AES256 $ANSIBLE_VAULT;1.1;AES256
37346135383630373332363964303533363431646638376431346432313363663433303531663361 32653862643366616262386263376562353838323765646264376536636364383136643736303964
6136353130363466366136343038666561616434616666380a646130616638623338636535353733 6464393530333332333965353764356133323737653734620a643762373836666538333366386165
36653434383264623166353462306631353163383266653232333066346235323962303862636130 36316433313361626238303838333039633036666239376666313762363862313139386364613832
3133353333356562660a653034306334343737656162356564383130643061663363303030336232 6134623365613839300a356563663766383965306530326331653731313132336438663438303062
65646237303366373939663263383534396132623334316464653933376134303638633264326533 38633639306436326431376566383361356532616239303430373632343263646137393636343236
61383639373265333238373036643939326432643931373465353734373132306138333363386363 36633232663766663933653263366633613930343066643334376462306564333935336538323364
39326262646561643131303662303733666262616461326633386130666135336364343462623030 35356333626165623366346662353066613736666466306439363434633233633463646533386132
63613330653565616337663236636464623365646462346439323466383730316462343465373464 35666263653462323539653762653037646239373637313465663639626361633931633336346437
30353566633863646438346134333332646263343465333464353737666631373462356366666433 39303933636134653461393463333637633638343163383333316636393832373462643438326338
36306462323537313832613533306230333663343465326536353035376332663939333061393035 33303765656235396164323935343231363032396164633036656538663266373535353938353437
36646539646530343830373338353333643635653330363161353737653062383065633931646462 35383838353734393237373930316463323230383435383636623835316164353661356232663962
62353133643137646236376365306136626537323665616637353734656430333763616165343966 37383764383563393365353364373335356330336363316636633737383130343333663735313233
35636364616666633733303238353366356435393631663930363036653931333436623664663432 31373739373265366136613438393433326532316165653462616435386234653635333566646166
64636431323435393765303339383961303535326464663034373766616135393939643263316661 64346330356465366464363734383734626132633462386261643133616236616564353935656138
37353232636663666663333230313530613538353161373731613539306330633661613736373363 33386636643131326561353663663738626137343935323934306335383530363337373130636130
30653037643162353634323365336431656533336262313633636432343037636336623132616431 65333961393564636264376437376439303165366464343465383064343630326637313162393034
33633631323830333739303337373264353264343533646663386265366232613636396230323561 63656534626264343265663536646437306334333438396331623931323236646361333137613965
64373231396132663566303737386230346534366638663730346434363535356636393135663735 62343033336232656533636138396430613935386130646662346435363838623235396665376539
63386163356435613665633739376561633135346230643965666231343763626439613532346439 62646237643538306636653637303633626162633330356138623835653466616538613162643164
31643337656639663463383237316438666464303133356235643837363961636262666238396431 36636364323661623531616262646362643864343837633633343133343964653336363232346163
31343832616138643330623065633564393862396663336662636238613764323438333762363261 36373235373735633462646138643234626530653638623661313538303063393138636262626137
31333736313062303131303065633235376463663932633066306135623436626334323938353662 37356638303331393766663730396637306335633766353932636333383863316535633339336138
62336434303535356436353035313438643263623638346431356238356134366664393237376235 39626634623637653832343633363038663437663435643966326436313639633363383761366430
37373333376162616537373931663864306331343937396238366362323938663839323232306539 64386664376435366433373862663963363166346664373031363034373162386166366439633439
66626265376465326661363839393339393834393435373839646466366564376663396138623936 38303562376536393161616236306235303762393234386234616232646661373035326234666564
62666264653338613433383763666561313234363962316131303866393036613531643538376365 35386432396263363539353263376439613933396435343261356137353838386163363362636361
63303737303634383032623030373039363961323064626130356134323265623063623031356333 36653139393361396461623031643634316336363065623433646237623965306466336338343338
64626437623964613136356437376163386635343966313731326334613035303730323664376461 33353331383462333066666238613932613433383261653331643239393862343431323265326138
63366166303162353661393536343764383335663161353732636562346562643764356264613831 36356333323965626433383361383964313730386266643739306134663564313333363038386633
38636339303938666663656132343032333233626430666461313764626462316366663933633230 61613935306165626361613564636138626533353666383034363336353031393062316337616135
38343362663634623637343933316163303135613833353966623966386431373238383836643930 61353236386130313964323638653535316365373632333638313736353536343935666661383538
64303138373661313938623030353139346236646134393737393536393938643232356163396632 35363535336566343431326232626438646530303862303033316335343066396661623830616231
63393334343834393936376330323932623935376436666163323738383830343163326330363330 39383739643062663963643630626430393563366237356232376261323131666339386463383038
64666536333561643434393063613165336332653938353036626361633466363935326536343237 36663038623831336164343866626664326332306634383662653366373061626139383032623063
30353838376432346565613934366664643730623534353430343765346432633065383932386132 38643466336562353766393764333265356232353837346466366165353635663866643564386134
63613762316139303861333733643464393337396635376330333561356537303533323663306639 39313433313465636161353261613039363865386634656637323063636263383339396631623638
35326132626431643136326238666365393834373735383865663463653436316165373362356532 63356232623139323961383966346232643632386635393565333834626635653234633932343866
64323964336238343966373334666264313636393035613134646166656532303465626366353763 66383539396537353466313166313735343936386463386261306466643234636239306263633366
31366539643962313764346432373632383564383933653430303963663635346362616463663965 39616630376536613633663466323366303233653263353236396436643034353562653965346431
30303931393265396565386330323132636635653237343336376338333538663462623532336331 36623638396431373761663363653239626635663335326637323031323761343037623634326531
66333031333465396439336330303238383164623734653930646164303466366235323938646665 38383836383231356633313331313539346437356464666464306537663039316661356363653532
62376336336136613361373764306265393033363934653263363639633638666565323236366431 36663734353066373364326166613064353763336461393463303239616564656165666139643432
65306634633866393165383564336234336561643037656536363763373330393136383166333033 63303239343261353036353539363838633238613132326466326437333133643330383334636164
34663063646232303666613262636363346135633536643538306466663233623934396664333131 36366436663133643365383330333639663730363662613165613033663533396438343161646332
62643535663430666331666538336563323539656163323564396463623833323034613130623634 39633166326431333234633039613733656131643634663265396465323465373635653364653739
62313236313435623332646664393039393931303566363737363832623431396334323435646263 34636533663461323439663930663937333663356230343061623339646130666434343361363233
37393466303235383531643836386339323337356637336562343162656333633435313664336262 63613366663037613832653835663363626134653835633835323837626565663835616430643332
65646231633665373230656537396539663831393339633263653737616133663737616434396130 35386236646465333836313338353439666338646130363064656632623433326632316437663238
32393131363833346130303730643838653862356235356338646465363837353664303063383961 31613330653837313463303435366265396337626563343833613361633836663061646538613630
65346530356432643437646133353832343034386339646634306165393163623730633764326461 61666330376130663337333937303134346163376561313138333163646463323131626330626462
33633962333735376238306330646266383134393663616437363162333534653764346533643635 31653436666666363661393333353366396339643235663162613231626236356662376661613162
30353837623235616666323334366438653834623633313039393662613732366139396266323263 64396535336634643661393830326630663964343036363566336533626565393763633238653935
6432 34356561393838636565336139386265316537616138353566393762373832626637356562633739
34363461306662396365333331373339323537653365646539346338353066356235623435373662
34313431346230663133306339323561333463393064383036363637656561333536623462616436
36653730616536313265

View file

@ -11,12 +11,14 @@ postgresql_shared_buffers: "256MB"
# Bases et utilisateurs à créer # Bases et utilisateurs à créer
# Mots de passe dans le vault : vault_pg_hermes_password, vault_pg_litellm_password, # 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: postgresql_databases:
- name: hermes - name: hermes
- name: litellm - name: litellm
- name: openwebui - name: openwebui
- name: n8n - name: n8n
- name: ghostfolio
postgresql_users: postgresql_users:
- name: hermes - name: hermes
@ -31,3 +33,6 @@ postgresql_users:
- name: n8n - name: n8n
password: "{{ vault_pg_n8n_password }}" password: "{{ vault_pg_n8n_password }}"
db: n8n db: n8n
- name: ghostfolio
password: "{{ vault_pg_ghostfolio_password }}"
db: ghostfolio

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: ghostfolio
namespace: ai
spec:
selector:
app: ghostfolio
ports:
- port: 3333
targetPort: 3333