From c2f7255da0b8764d47961ef9b8a0e2926249d69e Mon Sep 17 00:00:00 2001 From: ALI YESILKAYA Date: Sun, 28 Jun 2026 12:39:35 +0200 Subject: [PATCH] =?UTF-8?q?feat(openalice):=20d=C3=A9ploiement=20in-cluste?= =?UTF-8?q?r=20de=20l'agent=20IA=20financier=20(back-end=20finance=20d'Asa?= =?UTF-8?q?)=20(#61)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Déploie TraderAlice/OpenAlice (AGPL-3.0) dans le namespace ai comme back-end de connaissances financières pour Asa, consommé via le connecteur MCP Ask. - CI build-openalice.yml : rebuild depuis le Dockerfile amont (ref épinglée v0.60.0-beta.1) → ghcr.io/alkatrazz24/funk-openalice, bump manifest sur main - k8s/apps/openalice : Deployment (restricted + fsGroup, Recreate), PVC 5Gi NFS /data, Service web:47331 + mcpask:3003, IngressRoute openalice.lab.local - ArgoCD app + doc admin/ia/openalice.md (RAG) + MAJ CLAUDE.md - Cerveau hybride : LiteLLM/Qwen3-8B local (gratuit) + Claude basculable ; données TraderHub (sans clé) + FMP gratuit. Lecture/recherche seule — aucun broker, zéro trading. Onboarding premier boot (Web UI) imposé : credentials scellés dans /data par design. Co-authored-by: Claude Opus 4.8 --- .github/workflows/build-openalice.yml | 73 ++++++++++++++++++++++++ CLAUDE.md | 4 +- admin/ia/openalice.md | 49 ++++++++++++++++ k8s/apps-of-apps/apps/openalice.yaml | 24 ++++++++ k8s/apps/openalice/README.md | 67 ++++++++++++++++++++++ k8s/apps/openalice/deployment.yaml | 81 +++++++++++++++++++++++++++ k8s/apps/openalice/ingress.yaml | 14 +++++ k8s/apps/openalice/pvc.yaml | 14 +++++ k8s/apps/openalice/service.yaml | 16 ++++++ 9 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-openalice.yml create mode 100644 admin/ia/openalice.md create mode 100644 k8s/apps-of-apps/apps/openalice.yaml create mode 100644 k8s/apps/openalice/README.md create mode 100644 k8s/apps/openalice/deployment.yaml create mode 100644 k8s/apps/openalice/ingress.yaml create mode 100644 k8s/apps/openalice/pvc.yaml create mode 100644 k8s/apps/openalice/service.yaml diff --git a/.github/workflows/build-openalice.yml b/.github/workflows/build-openalice.yml new file mode 100644 index 0000000..ca81d57 --- /dev/null +++ b/.github/workflows/build-openalice.yml @@ -0,0 +1,73 @@ +name: build-openalice + +# Construit l'image OpenAlice (TraderAlice/OpenAlice — AGPL-3.0) depuis son dépôt amont +# à une révision ÉPINGLÉE, la pousse sur notre GHCR, et (sur main) bumpe le manifest k8s. +# +# OpenAlice n'est pas vendu sous forme d'image publique : on la rebuild depuis son Dockerfile. +# La source amont n'est PAS vendorisée dans ce repo (AGPL, gros monorepo) — on la clone à la +# volée à la `OPENALICE_REF` ci-dessous. Bump de version = éditer OPENALICE_REF puis dispatch. +on: + push: + branches: ["main", "feat/**", "claude/**"] + paths: + - ".github/workflows/build-openalice.yml" + workflow_dispatch: + +env: + IMAGE: ghcr.io/alkatrazz24/funk-openalice + OPENALICE_REPO: TraderAlice/OpenAlice + OPENALICE_REF: v0.60.0-beta.1 # révision amont épinglée + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write # pour committer le bump du manifest sur main + packages: write + steps: + - name: Checkout (ce repo — workflow + manifests) + uses: actions/checkout@v4 + + - name: Clone OpenAlice amont (ref épinglée) + run: | + git clone --depth 1 --branch "${OPENALICE_REF}" \ + "https://github.com/${OPENALICE_REPO}.git" openalice-src + + - name: Calcule le tag (= ref amont) + la liste des tags + id: vars + run: | + TAG="${OPENALICE_REF}" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + if [ "${{ github.ref }}" = "refs/heads/main" ]; then + echo "tags=${IMAGE}:${TAG},${IMAGE}:latest" >> "$GITHUB_OUTPUT" + else + echo "tags=${IMAGE}:${TAG}" >> "$GITHUB_OUTPUT" + fi + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build & push + uses: docker/build-push-action@v6 + with: + context: openalice-src + push: true + tags: ${{ steps.vars.outputs.tags }} + + - name: Met à jour le manifest avec le tag amont (main uniquement) + if: github.ref == 'refs/heads/main' + run: | + sed -i "s|image: ${IMAGE}:.*|image: ${IMAGE}:${{ steps.vars.outputs.tag }}|" \ + k8s/apps/openalice/deployment.yaml + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add k8s/apps/openalice/deployment.yaml + if ! git diff --staged --quiet; then + git commit -m "ci(openalice): image → ${{ steps.vars.outputs.tag }} [skip ci]" + git pull --rebase origin main + git push + fi diff --git a/CLAUDE.md b/CLAUDE.md index bb55679..7e95a5b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -162,6 +162,7 @@ k8s/ │ ├── stt.yaml │ ├── searxng.yaml │ ├── ghostfolio.yaml +│ ├── openalice.yaml │ ├── monitoring.yaml │ └── nfs-provisioner.yaml ├── apps/ # Manifests raw k8s par application @@ -170,6 +171,7 @@ k8s/ │ ├── stt/ # namespace: ai — STT-server (assistant vocal Asa, stt.lab.local) │ ├── searxng/ # namespace: ai — méta-moteur recherche web (outil web_search d'Asa) │ ├── ghostfolio/ # namespace: ai — suivi de portefeuille (ghostfolio.lab.local) +│ ├── openalice/ # namespace: ai — agent IA financier (openalice.lab.local, back-end finance d'Asa) │ └── {external-services}/ # ExternalName/Endpoints pointant vers storage-01 ou gpu-01 └── infra/ # Helm releases (HelmRelease + values.yaml) ├── monitoring/ # kube-prometheus-stack 85.0.2 @@ -184,7 +186,7 @@ k8s/ 2. Créer `k8s/apps-of-apps/apps/.yaml` (ArgoCD Application CRD) — copier `n8n.yaml` comme modèle 3. `git commit` + `git push` → ArgoCD prend en charge automatiquement -Namespaces actifs : `argocd`, `infra` (Traefik, MetalLB, NFS-provisioner, registry in-cluster), `monitoring` (Prometheus/Grafana), `ai` (n8n, open-webui, stt, searxng, ghostfolio), `sacrifice` (jeu FPS — déployé hors de ce repo et hors ArgoCD, cf. `admin/ops/etat-cluster.md`). +Namespaces actifs : `argocd`, `infra` (Traefik, MetalLB, NFS-provisioner, registry in-cluster), `monitoring` (Prometheus/Grafana), `ai` (n8n, open-webui, stt, searxng, ghostfolio, openalice), `sacrifice` (jeu FPS — déployé hors de ce repo et hors ArgoCD, cf. `admin/ops/etat-cluster.md`). > Les services du namespace `ai` sont exposés via des **IngressRoute Traefik** (CRD), pas des `Ingress` standards. diff --git a/admin/ia/openalice.md b/admin/ia/openalice.md new file mode 100644 index 0000000..71c387c --- /dev/null +++ b/admin/ia/openalice.md @@ -0,0 +1,49 @@ +# OpenAlice — agent IA financier + +**Quoi** : [TraderAlice/OpenAlice](https://github.com/TraderAlice/OpenAlice) (AGPL-3.0), agent +autonome de recherche financière multi-actifs (actions, crypto, commodités, forex, macro). +Déployé in-cluster comme **back-end financier d'Asa**. + +**Où** : namespace `ai`, `openalice.lab.local`, manifests `k8s/apps/openalice/`, app ArgoCD +`k8s/apps-of-apps/apps/openalice.yaml`. Image rebuild par CI depuis le Dockerfile amont +(`.github/workflows/build-openalice.yml`, ref épinglée `OPENALICE_REF`) → +`ghcr.io/alkatrazz24/funk-openalice`. + +**Mode d'usage** : lecture/recherche **uniquement** — aucun broker (UTA) connecté, donc +**pas de trading réel**. Données via **TraderHub** (hébergé, sans clé) + clé **FMP** gratuite +optionnelle pour les cours live (les K-lines/quotes ne sont pas sur le hub). + +## Architecture + +- **Process Alice** : runtime agent + Web UI (`:47331`) + serveur MCP interne + **connecteur + MCP Ask** (`:3003`). +- **MCP Ask** : entrée pour agents externes — `askWithSession(message, sessionId)` → + Alice répond (elle reste autonome, pas un simple fournisseur d'outils). C'est ce qu'Asa + consomme via `http://openalice:3003` in-cluster (outil `ask_alice`). +- **Cerveau** : pointé sur **LiteLLM `storage-01:4000` → Qwen3-8B** (OpenAI-compatible, + gratuit) ; **Claude** ajoutable en credential pour basculer (hybride). OpenAlice supporte + aussi l'override `ANTHROPIC_BASE_URL`. + +## Particularités + +- **Credentials scellés** : OpenAlice chiffre ses credentials au repos dans `/data` + (`sealing.js`). → **impossible de seeder une clé en clair via ConfigMap**. La config IA + + clés vendor + activation MCP Ask se font **au premier boot via la Web UI** (persisté/scellé + dans le PVC). +- **Token admin** (256 bits) généré au 1er boot : `kubectl -n ai logs deploy/openalice | grep -i token`. +- **PodSecurity restricted** (ns `ai`) : uid 1000 + `fsGroup: 1000` pour que le NFS soit + inscriptible. Sondes **TCP** (la Web UI 401 derrière le gate admin). +- **Ressources** : req 512Mi / lim 1.5Gi (nœuds 8 Go, OOM actif) — runtime Node + CLIs agent + embarqués (claude-code, codex) ; surveiller la conso réelle après déploiement. + +## Onboarding (premier boot) + +Voir checklist détaillée dans `k8s/apps/openalice/README.md` : +1. Secret `ghcr-pull` dans `ai` (si absent). +2. Web UI → AI Provider (LiteLLM OpenAI-compat + Claude option), Market Data (FMP), + Connectors → **activer MCP Ask :3003**. + +## Liens + +- Présentation/MCP Ask : `docs/` du dépôt amont (`mcp-ask-connector.md`). +- Intégration Asa : `admin/ia/stt.md` (outil `ask_alice`), `k8s/apps/stt/`. diff --git a/k8s/apps-of-apps/apps/openalice.yaml b/k8s/apps-of-apps/apps/openalice.yaml new file mode 100644 index 0000000..be29574 --- /dev/null +++ b/k8s/apps-of-apps/apps/openalice.yaml @@ -0,0 +1,24 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: openalice + 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/openalice + 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/openalice/README.md b/k8s/apps/openalice/README.md new file mode 100644 index 0000000..9586e94 --- /dev/null +++ b/k8s/apps/openalice/README.md @@ -0,0 +1,67 @@ +# OpenAlice — agent IA financier (in-cluster) + +[TraderAlice/OpenAlice](https://github.com/TraderAlice/OpenAlice) — *"one-person Wall Street"* : +agent autonome de recherche financière (actions, crypto, commodités, forex, macro). **AGPL-3.0**. +Déployé in-cluster (namespace `ai`, `openalice.lab.local`) comme **back-end de connaissances +financières d'Asa** : Asa lui parle via le connecteur **MCP Ask** (cf. PR outil `ask_alice`). + +> ⚠️ **Logiciel beta, expérimental.** Ici on l'utilise en **lecture/recherche uniquement** : +> **aucun broker (UTA) connecté → zéro trading réel.** Données de marché via **TraderHub** +> (hébergé, sans clé) + clé **FMP** gratuite optionnelle pour les cours live. + +## Image + +Pas d'image publique : la CI (`.github/workflows/build-openalice.yml`) rebuild depuis le +Dockerfile amont à une **ref épinglée** (`OPENALICE_REF`) → `ghcr.io/alkatrazz24/funk-openalice`. +Changer de version = éditer `OPENALICE_REF` puis relancer le workflow (`workflow_dispatch`). + +## Architecture du déploiement + +| Composant | Détail | +|---|---| +| Deployment | 1 réplica, `Recreate` (volume RWO), PodSecurity *restricted* (uid 1000, `fsGroup` 1000) | +| PVC `openalice-data` | 5Gi NFS → `/data` (auth CLIs, sessions, **config + credentials scellés**, caches) | +| Service `openalice` | `web:47331` (Web UI) + `mcpask:3003` (MCP Ask, in-cluster, pour Asa) | +| IngressRoute | `openalice.lab.local` → 47331 | + +Le cerveau d'Alice **n'est pas** une clé Claude par défaut : on le pointe sur **LiteLLM +(`storage-01:4000`) → Qwen3-8B** (gratuit), avec Claude basculable (hybride). Tout se règle +au **premier boot** (voir ci-dessous) car OpenAlice **scelle ses credentials dans `/data`** +(impossible à seeder en clair via ConfigMap — par design). + +## Pré-requis manuels (une fois) + +### 1. Secret de pull GHCR (si absent du ns `ai`) + +```bash +kubectl -n ai create secret docker-registry ghcr-pull \ + --docker-server=ghcr.io \ + --docker-username=Alkatrazz24 \ + --docker-password= +``` + +### 2. Premier boot — onboarding via la Web UI + +OpenAlice génère un **token admin** (256 bits) au premier démarrage. Le récupérer : + +```bash +kubectl -n ai logs deploy/openalice | grep -i token +``` + +Ouvrir **http://openalice.lab.local**, coller le token, puis configurer : + +1. **Settings › AI Provider** — ajouter un credential **OpenAI-compatible** : + - baseUrl `http://192.168.10.1:4000/v1` · modèle `hermes-default` (ou `qwen3-8b`) · apiKey `lm-studio` + - (option **hybride**) ajouter aussi un credential **Anthropic** (Claude) avec ta clé pour basculer. +2. **Settings › Market Data** — laisser **TraderHub** activé (par défaut). Ajouter la clé + **FMP** (palier gratuit, [financialmodelingprep.com](https://site.financialmodelingprep.com)) + pour les cours/K-lines live (hors hub). +3. **Connectors › MCP Ask** — **activer**, port **3003**. C'est l'entrée qu'Asa consomme + (`askWithSession`) via `http://openalice:3003` in-cluster. + +Tout ceci est **persisté + scellé** dans le volume → survit aux redémarrages et rebuilds d'image. + +## Asa (STT-server) + +L'outil `ask_alice(question)` de la boucle agentique d'Asa délègue à Alice via MCP Ask +(`http://openalice:3003`). Voir `admin/ia/openalice.md` et le déploiement `stt`. diff --git a/k8s/apps/openalice/deployment.yaml b/k8s/apps/openalice/deployment.yaml new file mode 100644 index 0000000..835d97e --- /dev/null +++ b/k8s/apps/openalice/deployment.yaml @@ -0,0 +1,81 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: openalice + namespace: ai +spec: + replicas: 1 + # OpenAlice écrit dans un volume RWO (NFS) — pas de rolling avec 2 pods sur le même volume. + strategy: + type: Recreate + selector: + matchLabels: + app: openalice + template: + metadata: + labels: + app: openalice + spec: + # Image privée sur ghcr → secret de pull créé manuellement dans le ns ai + # (cf. README : kubectl create secret docker-registry ghcr-pull ...) + imagePullSecrets: + - name: ghcr-pull + # Conformité PodSecurity "restricted" (namespace ai). fsGroup 1000 → le volume NFS + # (HOME des CLIs + /data) est inscriptible par l'uid non-root de l'image. + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault + containers: + - name: openalice + # Tag amont géré par la CI (build-openalice.yml) : bumpé en à chaque build main. + image: ghcr.io/alkatrazz24/funk-openalice:v0.60.0-beta.1 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + ports: + - name: web + containerPort: 47331 + # MCP Ask connector — consommé in-cluster par Asa (STT-server). À ACTIVER une fois + # au premier boot (Web UI › Connectors) : { mcpAsk: { enabled: true, port: 3003 } }. + - name: mcpask + containerPort: 3003 + env: + # OpenAlice stocke ses credentials (provider IA, clés vendor) SCELLÉS dans /data, + # configurés au premier boot via la Web UI — voir README. Rien de secret en env ici. + - name: TZ + value: "Europe/Paris" + # Derrière Traefik (entrypoint web) : on fait confiance au proxy in-cluster pour + # les en-têtes (IP réelle / host). LAN privé, HTTP. + - name: OPENALICE_TRUSTED_PROXIES + value: "10.42.0.0/16" + # Web UI Hono : le portail 47331 répond mais peut 401 derrière le gate admin → + # sonde TCP plutôt qu'HTTP. Démarrage lent (runtime Node + CLIs agent embarqués). + readinessProbe: + tcpSocket: + port: 47331 + initialDelaySeconds: 20 + periodSeconds: 15 + livenessProbe: + tcpSocket: + port: 47331 + initialDelaySeconds: 60 + periodSeconds: 30 + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: "1" + memory: 1536Mi + volumeMounts: + - name: data + mountPath: /data + volumes: + - name: data + persistentVolumeClaim: + claimName: openalice-data diff --git a/k8s/apps/openalice/ingress.yaml b/k8s/apps/openalice/ingress.yaml new file mode 100644 index 0000000..229f927 --- /dev/null +++ b/k8s/apps/openalice/ingress.yaml @@ -0,0 +1,14 @@ +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: openalice + namespace: ai +spec: + entryPoints: + - web + routes: + - match: Host(`openalice.lab.local`) + kind: Rule + services: + - name: openalice + port: 47331 diff --git a/k8s/apps/openalice/pvc.yaml b/k8s/apps/openalice/pvc.yaml new file mode 100644 index 0000000..926a954 --- /dev/null +++ b/k8s/apps/openalice/pvc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: openalice-data + namespace: ai +spec: + # /data : auth des CLIs agent, sessions, config (credentials SCELLÉES au repos), + # caches, archive news, historique trading. Persiste config + onboarding premier boot. + accessModes: + - ReadWriteOnce + storageClassName: nfs + resources: + requests: + storage: 5Gi diff --git a/k8s/apps/openalice/service.yaml b/k8s/apps/openalice/service.yaml new file mode 100644 index 0000000..40c0298 --- /dev/null +++ b/k8s/apps/openalice/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: openalice + namespace: ai +spec: + selector: + app: openalice + ports: + - name: web + port: 47331 + targetPort: 47331 + # MCP Ask — consommé par le STT-server (Asa) : http://openalice:3003 (in-cluster). + - name: mcpask + port: 3003 + targetPort: 3003