Compare commits

..

27 Commits

Author SHA1 Message Date
Gato 7ae0d9f87f docs(watchtower): le PAT de pull doit couvrir Bonsai, plus seulement Luz
Le commentaire disait qu'un PAT read:package sur l'org Luz suffisait — c'était
vrai tant que Luz était seul à publier des images. La stack bonsai tire désormais
bonsai-api du même registre avec les mêmes identifiants : un PAT limité à Luz la
laisserait bloquée sur son ancienne image, sans rien signaler.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 14:03:04 +02:00
Gato 30887c066b feat(bonsai): la prod tire l'image publiée par la CI au lieu de la recompiler
Le service api clonait le dépôt et compilait le JAR sur le serveur à chaque
« docker compose build » (Dockerfile.api + secret ssh_key + CACHEBUST). La prod
rebuildait donc ce que la CI avait déjà buildé, sans garantie que les deux
partent du même commit — et aucune image publiée n'atteignait jamais la prod.

Elle tire maintenant …/bonsai-api:main et porte le label watchtower.enable : le
redéploiement suit tout seul chaque merge sur main, comme pour Luz. Le webapp,
lui, continue d'être buildé depuis les sources — d'où le secret ssh_key conservé.

Prérequis : de quoi tirer les images privées de l'org Bonsai. Le PAT de Watchtower
n'a aujourd'hui le scope read:package que sur Luz ; sans extension à Bonsai, le
pull échoue en silence (/v1/update répond 200, la prod ne bouge pas).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 14:02:24 +02:00
Gato 23ce5c86e1 Merge pull request 'La prod suit à nouveau les images publiées : les stacks passent au tag :main' (#1) from fix/stacks-luz-suivent-le-tag-main into main
Reviewed-on: #1
2026-07-26 22:02:44 +02:00
Gato 5558c206a0 fix(luz): les stacks suivent le tag :main, publié par la CI
La prod servait encore a70e0e4 (25/07) alors que main en etait a 79 commits
plus loin : les stacks luz et dev-luz epinglaient « :develop », un tag que la
CI de l'app ne pousse plus depuis qu'elle ne se declenche que sur main
(images.yml publie sha-<court>, main et latest).

Rien ne le signalait : Watchtower repondait bien 200 a /v1/update, ne trouvait
aucune image plus recente pour « :develop » et ne recreait donc rien. Seule la
verification post-deploiement de la CI a fini par le dire.

Les quatre images (backend + webapp, prod et dev-luz) visent desormais « :main ».
Les en-tetes des deux stacks rappellent que ce tag doit rester celui que publie
images.yml, changer l'un imposant de changer l'autre.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 21:59:37 +02:00
Gato 9a37ed4b8f fix(watchtower): authentifie le pull des images privées Gitea (reqPackageAccess)
Watchtower déclenchait bien /v1/update (200) mais échouait à tirer
luz-backend/luz-webapp :develop (« unauthorized: reqPackageAccess »),
d'où Updated=0 silencieux : les conteneurs n'étaient jamais recréés et
la prod restait figée sur une ancienne image malgré une CI verte.

Le montage de /root/.docker/config.json était inopérant (le docker login
du CI a lieu sur le runner, pas sur l'hôte). On le remplace par des
identifiants de pull explicites REPO_USER/REPO_PASS (PAT read:package,
via REGISTRY_USER/REGISTRY_PASS du .env). .env.example documenté.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 07:07:07 +02:00
Gato 2b2a924916 refactor(luz): renomme le container webapp « luz » → « luz-webapp »
Aligne le nom du front sur celui du backend (luz-backend) et renomme les
identifiants Traefik associés (routeur/service luz → luz-webapp). La règle
Host(`luz.goutailler-olivier.com`) est inchangée : le site reste servi.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 07:40:18 +02:00
Gato 44173a540b fix(luz): prod suit le tag :develop (pas de branche main sur l'app)
Le tag :latest n'est jamais produit (l'app ne pousse que develop) → « manifest
unknown » au pull. La prod tire désormais git.goutailler-olivier.com/luz/luz-*:develop,
comme dev-luz, en conservant sa base persistante et son absence de profil dev.
Labels Watchtower conservés : la prod se recrée automatiquement à chaque push develop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 07:23:53 +02:00
Gato 8efa725753 feat(luz): prod tire les images du registre Gitea + auto-déploiement Watchtower
Remplace les images locales « localhost/luz-*:latest » (ex-deploy.sh SSH, supprimé)
par « git.goutailler-olivier.com/luz/luz-*:latest » et ajoute le label
com.centurylinklabs.watchtower.enable=true sur backend et webapp. La prod suit
désormais le tag :latest (publié par la CI sur push main), séparée de dev-luz
(:develop). Met à jour le bandeau de tête en conséquence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 07:08:58 +02:00
Gato d90b7bc34b feat(dev-luz): déploiement continu depuis le registre via Watchtower
Les images pointent désormais sur git.goutailler-olivier.com/luz/luz-*:develop
(publiées par la CI) au lieu de localhost/luz-*:dev transférées en SSH. Les
services backend/webapp portent le label watchtower.enable=true : Watchtower
tire la nouvelle image et recrée le conteneur dès que la CI la pousse.

Prérequis serveur : docker login sur le registre (root) pour tirer les images
privées de l'org luz.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 06:26:27 +02:00
Gato 7d3c6f164e chore(dev-luz): renommer l'environnement de test luz-dev → dev-luz 2026-07-05 10:49:33 +02:00
Gato 7ed2d0b238 fix: nom dev-luz
Signed-off-by: Gato <cedric@goutailler-olivier.fr>
2026-07-04 06:33:09 +02:00
Gato ba13758058 feat(luz): stacks dev + prod avec backend et BDD (routing /api Traefik)
Ajoute l'environnement de test luz-dev/ (webapp + backend /api + PostgreSQL,
profil Spring dev avec seed) et complète le stack prod luz/ avec db + backend
sur luz.goutailler-olivier.com/api. Images chargées en direct par SSH via
Luz/script/deploy.sh (sans registre), d'où les tags localhost/luz-*.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 05:23:04 +02:00
Cédric Olivier 42d006ee6e Clean repo and add deployment script for Bonsai 2026-06-20 07:33:40 +02:00
Gato bba8216cea feat(bonsai): invalidation sélective du cache via CACHEBUST
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 06:52:10 +02:00
Gato e19aeadc63 fix: ssh
Signed-off-by: Gato <cedric@goutailler-olivier.fr>
2026-06-15 06:47:56 +02:00
Gato cdefbe3a81 tech: ajout repo avec build main
Signed-off-by: Gato <cedric@goutailler-olivier.fr>
2026-06-15 06:42:46 +02:00
Gato f65ffea670 chore(olhar): remplace UPLOADS_PATH par DATA_PATH dans .env.example
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 09:05:59 +02:00
Gato 2e45a8cc10 chore(olhar): ajout PHOTOS_STORAGE_PATH dans .env.example
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 08:55:12 +02:00
Gato 5798440a9b chore(olhar): ajout UPLOADS_PATH dans .env.example
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 08:43:04 +02:00
Gato 0352ffd641 fix(olhar): deploy.sh utilise le compose du repo cloné
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 08:40:45 +02:00
Gato 3ef9311729 fix(olhar): deploy depuis develop car main n'a pas encore la structure monorepo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 08:25:44 +02:00
Gato 9a477e8b7f feat(olhar): script de déploiement depuis les sources
Clone ou met à jour Gato/Olhar (main), build les deux images
Docker localement, puis relance le stack via docker compose.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 08:19:21 +02:00
Gato c65ffaa15a feat(olhar): compose unifié db + api + pwa, suppression olhar-api
- olhar/docker-compose.yml : fusion des services db (postgres:16) +
  api (olhar-api:latest) + pwa (olhar-pwa:latest) dans un seul stack
- olhar/.env.example : POSTGRES_PASSWORD + JWT_SECRET
- olhar-api/ : dossier supprimé (contenu intégré dans olhar/)
- Données migrées vers ~/Applications/data/olhar/ (au lieu de olhar-api/)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 07:06:02 +02:00
Gato 7f6d6a7aaf fix(olhar-api): monter le dossier uploads pour persister les photos
Sans volume, les photos uploadées étaient perdues à chaque redémarrage du container.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 21:05:35 +02:00
Gato bca776f118 chore: ajouter les fichiers .env.example et extraire les secrets hardcodés
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 08:54:17 +02:00
Gato 85350cfe86 feat: ajout du service olhar-api
Docker Compose calqué sur bonsai-api : PostgreSQL 16 + Spring Boot exposé
sur olhar.goutailler-olivier.com/api via Traefik, avec Watchtower activé.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 08:03:28 +02:00
Gato 2c420e3fce Update doc 2026-06-07 07:33:40 +02:00
22 changed files with 584 additions and 108 deletions
+1
View File
@@ -1,2 +1,3 @@
traefik-letsencrypt/ traefik-letsencrypt/
.env .env
**/.env
-54
View File
@@ -1,54 +0,0 @@
name: bonsai-api-stack
services:
db:
image: postgres:16-alpine
container_name: bonsai-api-db
restart: unless-stopped
environment:
POSTGRES_DB: bonsai
POSTGRES_USER: bonsai
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
TZ: Europe/Paris
healthcheck:
test: ["CMD-SHELL", "pg_isready -U bonsai -d bonsai"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- ~/Applications/data/bonsai-api/db_data:/var/lib/postgresql/data
networks:
- bonsai-api-net
api:
image: git.goutailler-olivier.com/bonsai/bonsai-api:latest
container_name: bonsai-api
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATASOURCE_URL: jdbc:postgresql://db:5432/bonsai
DATASOURCE_USERNAME: bonsai
DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
KEYCLOAK_JWKS_URI: https://auth.goutailler-olivier.com/realms/bonsai/protocol/openid-connect/certs
CORS_ALLOWED_ORIGIN_PROD: https://bonsai.goutailler-olivier.com
TZ: Europe/Paris
networks:
- bonsai-api-net
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.bonsai-api.rule=Host(`bonsai.goutailler-olivier.com`) && PathPrefix(`/api`)
- traefik.http.routers.bonsai-api.entrypoints=websecure
- traefik.http.routers.bonsai-api.tls.certresolver=le
- traefik.http.services.bonsai-api.loadbalancer.server.port=8080
- traefik.docker.network=proxy
- com.centurylinklabs.watchtower.enable=true
networks:
bonsai-api-net:
driver: bridge
proxy:
external: true
name: proxy
-27
View File
@@ -1,27 +0,0 @@
name: bonsai-webapp
services:
bonsai-webapp:
image: git.goutailler-olivier.com/bonsai/bonsai-webapp:latest
container_name: bonsai-webapp
restart: unless-stopped
environment:
TZ: Europe/Paris
networks:
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.bonsai-webapp.rule=Host(`bonsai.goutailler-olivier.com`)
- traefik.http.routers.bonsai-webapp.entrypoints=websecure
- traefik.http.routers.bonsai-webapp.tls.certresolver=le
- traefik.http.services.bonsai-webapp.loadbalancer.server.port=80
- traefik.docker.network=proxy
- com.centurylinklabs.watchtower.enable=true
networks:
proxy:
external: true
name: proxy
+8
View File
@@ -0,0 +1,8 @@
# Copier ce fichier en .env et adapter les valeurs avant de démarrer.
# cp .env.example .env
# Mot de passe PostgreSQL partagé entre le container db et l'API Spring Boot.
POSTGRES_PASSWORD=changeme
# Chemin absolu vers la clé SSH privée ayant accès au dépôt Gitea.
SSH_KEY_PATH=/home/gato/.ssh/id_ed25519
+19
View File
@@ -0,0 +1,19 @@
# syntax=docker/dockerfile:1
FROM node:22-alpine AS builder
RUN apk add --no-cache git openssh
ARG CACHEBUST
RUN --mount=type=secret,id=ssh_key \
--mount=type=cache,target=/root/.npm \
mkdir -p /root/.ssh && \
cp /run/secrets/ssh_key /root/.ssh/id_ed25519 && \
chmod 600 /root/.ssh/id_ed25519 && \
ssh-keyscan -p 2222 git.goutailler-olivier.com >> /root/.ssh/known_hosts && \
git clone --depth 1 --branch main \
ssh://git@git.goutailler-olivier.com:2222/Gato/Bonsai-webapp.git /src && \
cd /src && npm ci --cache /root/.npm && npm run build
FROM nginx:alpine
COPY --from=builder /src/dist/Bonsai-webapp/browser /usr/share/nginx/html
COPY --from=builder /src/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
+68
View File
@@ -0,0 +1,68 @@
# Bonsai — Stack complète
Stack Docker Compose unifiée pour l'application Bonsai : base de données, API et webapp.
Les deux composants ne se déploient pas de la même façon :
- **`api`** — image **tirée du registre Gitea** (`git.goutailler-olivier.com/bonsai/bonsai-api:main`), publiée par la CI du dépôt `Bonsai-api` (`.gitea/workflows/images.yml`) à chaque merge sur `main`. Le redéploiement est automatique : le conteneur porte le label `com.centurylinklabs.watchtower.enable=true`, donc Watchtower (`../watchtower/`) le recrée dès qu'une nouvelle image est publiée. Rien à builder sur le serveur.
- **`webapp`** — toujours **buildé depuis les sources** de la branche `main` du dépôt `Bonsai-webapp`. La clé SSH est transmise au build via un secret BuildKit — elle n'est jamais gravée dans les couches de l'image.
## Services
| Service | Image | Provenance |
|---------|-------|------------|
| `db` | `postgres:16-alpine` | — |
| `api` | `git.goutailler-olivier.com/bonsai/bonsai-api:main` | CI de `Bonsai-api` (merge sur `main`) |
| `webapp` | `node:22-alpine``nginx:alpine` | build local depuis `Bonsai-webapp` (branche `main`) |
## Prérequis
- Docker avec BuildKit activé (Docker 23+)
- Une clé SSH ayant accès au dépôt Gitea (`git.goutailler-olivier.com:2222`) — pour le build du `webapp`
- Réseau Docker externe `proxy` (Traefik)
- De quoi tirer les images privées de l'org **Bonsai** : soit un `docker login git.goutailler-olivier.com` fait une fois en root sur le serveur, soit les `REPO_USER`/`REPO_PASS` de la stack Watchtower, dont le PAT doit porter le scope `read:package` sur l'org Bonsai. Sans cela, Watchtower échoue silencieusement à tirer l'image (`unauthorized: reqPackageAccess`) et la prod reste sur l'ancienne version, alors que l'appel à `/v1/update` répond bien 200.
## Démarrage
```bash
# 1. Configurer les variables d'environnement
cp .env.example .env
# Éditer .env : définir POSTGRES_PASSWORD et SSH_KEY_PATH
# 2. Tirer l'image de l'API + builder le webapp
docker compose pull
docker compose build
# 3. Démarrer la stack
docker compose up -d
```
## Mise à jour
L'API se met à jour **toute seule** à chaque merge sur `main` de `Bonsai-api` (CI → registre → Watchtower). Aucune action ici.
Pour forcer un redéploiement, ou pour rebuilder le webapp depuis le dernier commit de sa branche `main` :
```bash
./deploy.sh
```
`CACHEBUST` (posé par le script) invalide uniquement l'étape clone+build du webapp. Les couches précédentes (installation des outils, image de base) restent en cache — le build reste rapide.
### Revenir à une version précédente de l'API
Chaque publication pose aussi un tag immuable `sha-<court>`. Pour épingler un commit :
```yaml
image: git.goutailler-olivier.com/bonsai/bonsai-api:sha-ccc20bf
```
puis `docker compose up -d api`. Watchtower ne touche pas à un tag figé qui ne bouge plus — repasser sur `:main` pour réactiver le suivi automatique.
## Arrêt
```bash
docker compose down
```
Les données PostgreSQL sont persistées dans `~/Applications/data/bonsai/db_data` et les fichiers uploadés dans `~/Applications/data/bonsai/uploads`.
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
#
# Déploiement manuel de la stack Bonsai.
#
# L'API n'est plus buildée ici : son image est publiée par la CI du dépôt
# Bonsai-api à chaque merge sur main, et Watchtower recrée le conteneur tout seul.
# Ce script sert donc au premier démarrage, à un changement de la stack ou à un
# redéploiement forcé — « pull » va chercher la dernière image de l'API, « build »
# ne concerne plus que le webapp (toujours compilé depuis les sources).
docker compose pull
CACHEBUST=$(date +%s) docker compose build
docker compose up -d
+102
View File
@@ -0,0 +1,102 @@
name: bonsai
services:
db:
image: postgres:16-alpine
container_name: bonsai-db
restart: unless-stopped
environment:
POSTGRES_DB: bonsai
POSTGRES_USER: bonsai
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
TZ: Europe/Paris
healthcheck:
test: ["CMD-SHELL", "pg_isready -U bonsai -d bonsai"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- ~/Applications/data/bonsai/db_data:/var/lib/postgresql/data
networks:
- bonsai-net
# Image tirée du registre Gitea, publiée par la CI du dépôt Bonsai-api
# (.gitea/workflows/images.yml) à chaque push sur main. Le tag « main » suit la
# tête de cette branche ; « sha-<court> » permet d'épingler un commit précis en
# cas de rollback.
#
# Avant, ce service clonait le dépôt et compilait le JAR sur le serveur à chaque
# « docker compose build » (Dockerfile.api + secret ssh_key + CACHEBUST) : la prod
# rebuildait ce que la CI avait déjà buildé, et rien ne garantissait que les deux
# partaient du même commit. Le redéploiement est désormais automatique — le label
# watchtower.enable ci-dessous fait recréer ce conteneur dès que la CI publie une
# nouvelle image.
#
# Prérequis : le serveur doit pouvoir tirer les images privées de l'org Bonsai —
# soit via un « docker login git.goutailler-olivier.com » (root), soit via les
# REPO_USER/REPO_PASS de la stack Watchtower, dont le PAT doit avoir le scope
# read:package sur l'org Bonsai (et pas seulement sur Luz).
api:
image: git.goutailler-olivier.com/bonsai/bonsai-api:main
container_name: bonsai-api
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATASOURCE_URL: jdbc:postgresql://db:5432/bonsai
DATASOURCE_USERNAME: bonsai
DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
KEYCLOAK_JWKS_URI: https://auth.goutailler-olivier.com/realms/bonsai/protocol/openid-connect/certs
CORS_ALLOWED_ORIGIN_PROD: https://bonsai.goutailler-olivier.com
SERVER_SERVLET_CONTEXT_PATH: /api
TZ: Europe/Paris
volumes:
- ~/Applications/data/bonsai/uploads:/app/uploads
networks:
- bonsai-net
- proxy
labels:
- com.centurylinklabs.watchtower.enable=true
- traefik.enable=true
- traefik.http.routers.bonsai-api.rule=Host(`bonsai.goutailler-olivier.com`) && PathPrefix(`/api`)
- traefik.http.routers.bonsai-api.entrypoints=websecure
- traefik.http.routers.bonsai-api.tls.certresolver=le
- traefik.http.services.bonsai-api.loadbalancer.server.port=8080
- traefik.docker.network=proxy
webapp:
build:
context: .
dockerfile: Dockerfile.webapp
secrets:
- ssh_key
args:
CACHEBUST: ${CACHEBUST:-stable}
container_name: bonsai-webapp
restart: unless-stopped
depends_on:
- api
environment:
TZ: Europe/Paris
networks:
- bonsai-net
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.bonsai-webapp.rule=Host(`bonsai.goutailler-olivier.com`)
- traefik.http.routers.bonsai-webapp.entrypoints=websecure
- traefik.http.routers.bonsai-webapp.tls.certresolver=le
- traefik.http.services.bonsai-webapp.loadbalancer.server.port=80
- traefik.docker.network=proxy
networks:
bonsai-net:
driver: bridge
proxy:
external: true
name: proxy
secrets:
ssh_key:
file: ${SSH_KEY_PATH}
+14
View File
@@ -0,0 +1,14 @@
# Secrets du stack de test Luz — copier en .env (git-ignoré) avant de démarrer.
# cp .env.example .env
#
# Note : le script Luz/script/deploy.sh génère automatiquement ce .env avec des
# valeurs fortes au premier déploiement. Ce fichier reste utile pour un démarrage
# manuel (docker compose up -d) directement sur le serveur.
# Mot de passe PostgreSQL (partagé entre le container db et le backend).
# openssl rand -hex 16
DB_PASSWORD=changeme
# Secret de signature des JWT. NE JAMAIS réutiliser le défaut de application.yml.
# openssl rand -base64 32
JWT_SECRET=changeme-use-a-long-random-string
+102
View File
@@ -0,0 +1,102 @@
# Stack de TEST Luz — dev-luz.goutailler-olivier.com
#
# Frontend : https://dev-luz.goutailler-olivier.com → webapp (nginx :80)
# Backend : https://dev-luz.goutailler-olivier.com/api → backend Spring (:8080)
#
# Déploiement AUTOMATIQUE : la CI Gitea (client/app/.gitea/workflows/images.yml)
# builde et pousse les images « …/luz/luz-*:main » à chaque push sur main,
# puis appelle l'API HTTP de Watchtower. Watchtower (../watchtower/) ne surveille
# que les conteneurs portant le label watchtower.enable=true (posé ci-dessous) :
# il tire la nouvelle image et recrée le conteneur. Plus de transfert SSH ni de
# « deploy.sh » pour cette stack de test.
#
# ⚠️ La CI ne publie plus « :develop » (elle ne se déclenche que sur main) : une
# stack restée sur ce tag ne reçoit plus jamais de mise à jour, en silence. Le tag
# suivi ici doit toujours être celui que publie images.yml — cf. ../luz/.
#
# Prérequis : le serveur (root) doit être « docker login git.goutailler-olivier.com »
# une fois, sinon Watchtower ne peut pas tirer les images privées de l'org luz.
#
# Profil Spring « dev » : la base est réinitialisée (TRUNCATE + seed.sql) à chaque
# démarrage du backend. Données de test fixes, idéales pour l'app mobile.
#
# Prérequis serveur : Traefik v3 + réseau externe « proxy » (voir ../traefik/),
# et un enregistrement DNS A « dev-luz » pointant sur le serveur (Let's Encrypt HTTP-01).
name: dev-luz
services:
db:
image: postgres:17
container_name: dev-luz-db
restart: unless-stopped
environment:
POSTGRES_DB: luz
POSTGRES_USER: luz
POSTGRES_PASSWORD: ${DB_PASSWORD}
TZ: Europe/Paris
healthcheck:
test: ["CMD-SHELL", "pg_isready -U luz -d luz"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- dev_luz_db:/var/lib/postgresql/data
networks:
- dev-luz-net
backend:
image: git.goutailler-olivier.com/luz/luz-backend:main
container_name: dev-luz-backend
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DB_HOST: db
DB_PORT: 5432
DB_NAME: luz
DB_USER: luz
DB_PASSWORD: ${DB_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
SPRING_PROFILES_ACTIVE: dev # TRUNCATE + rejeu de seed.sql à chaque démarrage
TZ: Europe/Paris
networks:
- dev-luz-net
- proxy
labels:
- com.centurylinklabs.watchtower.enable=true
- traefik.enable=true
- traefik.http.routers.dev-luz-backend.rule=Host(`dev-luz.goutailler-olivier.com`) && PathPrefix(`/api`)
- traefik.http.routers.dev-luz-backend.entrypoints=websecure
- traefik.http.routers.dev-luz-backend.tls.certresolver=le
- traefik.http.services.dev-luz-backend.loadbalancer.server.port=8080
- traefik.docker.network=proxy
webapp:
image: git.goutailler-olivier.com/luz/luz-webapp:main
container_name: dev-luz-webapp
restart: unless-stopped
depends_on:
- backend
environment:
TZ: Europe/Paris
networks:
- proxy
labels:
- com.centurylinklabs.watchtower.enable=true
- traefik.enable=true
- traefik.http.routers.dev-luz-webapp.rule=Host(`dev-luz.goutailler-olivier.com`)
- traefik.http.routers.dev-luz-webapp.entrypoints=websecure
- traefik.http.routers.dev-luz-webapp.tls.certresolver=le
- traefik.http.services.dev-luz-webapp.loadbalancer.server.port=80
- traefik.docker.network=proxy
volumes:
dev_luz_db:
networks:
dev-luz-net:
driver: bridge
proxy:
external: true
name: proxy
+9
View File
@@ -0,0 +1,9 @@
# Copier ce fichier en .env et renseigner les valeurs avant de démarrer.
# cp env.example .env
# Mot de passe PostgreSQL pour la base de données Gitea.
GITEA_POSTGRES_PASSWORD=changeme
# Token d'enregistrement du runner Gitea Actions.
# À récupérer dans Gitea : Site Administration > Actions > Runners > Create new runner
GITEA_RUNNER_REGISTRATION_TOKEN=your_runner_token_here
+7 -1
View File
@@ -7,7 +7,13 @@ docker compose -f gitea-compose.yml pull gitea
docker compose -f gitea-compose.yml up -d docker compose -f gitea-compose.yml up -d
docker exec gitea gitea --version docker exec gitea gitea --version
``` ```
Build le runner
```bash
docker compose up -d --build act_runner
```
Relancer le runner Relancer le runner
```bash ```bash
docker compose -f gitea-compose.yml restart act_runner docker compose restart act_runner
``` ```
+3 -3
View File
@@ -8,7 +8,7 @@ services:
environment: environment:
POSTGRES_DB: gitea POSTGRES_DB: gitea
POSTGRES_USER: gitea POSTGRES_USER: gitea
POSTGRES_PASSWORD: change_me POSTGRES_PASSWORD: ${GITEA_POSTGRES_PASSWORD}
TZ: Europe/Paris TZ: Europe/Paris
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
@@ -47,7 +47,7 @@ services:
GITEA__database__HOST: db:5432 GITEA__database__HOST: db:5432
GITEA__database__NAME: gitea GITEA__database__NAME: gitea
GITEA__database__USER: gitea GITEA__database__USER: gitea
GITEA__database__PASSWD: change_me GITEA__database__PASSWD: ${GITEA_POSTGRES_PASSWORD}
TZ: Europe/Paris TZ: Europe/Paris
volumes: volumes:
@@ -88,7 +88,7 @@ services:
- gitea - gitea
environment: environment:
GITEA_INSTANCE_URL: http://gitea:3000 GITEA_INSTANCE_URL: http://gitea:3000
GITEA_RUNNER_REGISTRATION_TOKEN: VBV30irSexdiyvHyzlX0HAmb1iV3XGiDm37raumU GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN}
GITEA_RUNNER_NAME: docker-runner GITEA_RUNNER_NAME: docker-runner
GITEA_RUNNER_LABELS: ubuntu-latest:host GITEA_RUNNER_LABELS: ubuntu-latest:host
CONFIG_FILE: /config.yaml CONFIG_FILE: /config.yaml
+18
View File
@@ -0,0 +1,18 @@
# Secrets du stack de PRODUCTION Luz — copier en .env (git-ignoré) avant de démarrer.
# cp .env.example .env
#
# Note : le script Luz/script/deploy.sh prod génère automatiquement ce .env avec
# des valeurs fortes au premier déploiement. Ce fichier reste utile pour un
# démarrage manuel sur le serveur.
# Mot de passe PostgreSQL (partagé entre le container db et le backend).
# openssl rand -hex 16
DB_PASSWORD=changeme
# Secret de signature des JWT. NE JAMAIS réutiliser le défaut de application.yml.
# openssl rand -base64 32
JWT_SECRET=changeme-use-a-long-random-string
# (Optionnel) Racine des données persistantes. La BDD vit dans ${DATA_PATH}/luz/db_data.
# Défaut : ~/Applications/data
#DATA_PATH=/home/gato/Applications/data
+89 -11
View File
@@ -1,27 +1,105 @@
# Stack de PRODUCTION Luz — luz.goutailler-olivier.com
#
# Frontend : https://luz.goutailler-olivier.com → webapp (nginx :80)
# Backend : https://luz.goutailler-olivier.com/api → backend Spring (:8080)
#
# Images tirées du registre Gitea (git.goutailler-olivier.com/luz/luz-*:main),
# publiées par la CI (client/app/.gitea/workflows/images.yml) à chaque push « main »
# — c'est-à-dire à chaque mise en production. Prod et dev-luz partagent donc la MÊME
# image ; ce qui les sépare : ici la base est PERSISTANTE et sans profil « dev » (voir
# plus bas), côté dev-luz elle est réinitialisée à chaque démarrage. Le redéploiement
# est automatique : les conteneurs portent le label watchtower.enable=true, donc chaque
# push main fait recréer AUSSI cette prod par Watchtower (../watchtower/), en même
# temps que dev-luz. Plus de transfert SSH ni de deploy.sh.
#
# ⚠️ Le tag suivi ici doit rester celui que la CI publie. L'app a d'abord publié
# « :develop » (elle n'avait pas de branche main), puis est passée à « :main ». Ces
# stacks étant restées sur « :develop », plus rien n'a poussé ce tag : Watchtower
# répondait 200 à /v1/update mais ne trouvait aucune image plus récente, et la prod
# est restée figée sur le dernier build develop (a70e0e4, 25/07) pendant que main
# avançait de 79 commits — sans la moindre erreur visible côté CI hors la
# vérification post-déploiement. Changer le tag de publication dans images.yml
# impose donc de changer ce tag ici, dans le même mouvement.
#
# Prérequis : le serveur (root) doit être « docker login git.goutailler-olivier.com »
# une fois, sinon Watchtower ne peut pas tirer les images privées de l'org luz.
#
# PAS de profil « dev » : Flyway applique les migrations, la base est PERSISTANTE
# (bind sous ${DATA_PATH}/luz/db_data), les données ne sont jamais réinitialisées.
#
# Prérequis serveur : Traefik v3 + réseau externe « proxy » (voir ../traefik/),
# DNS A « luz » → serveur.
name: luz name: luz
services: services:
luz: db:
image: git.goutailler-olivier.com/gato/luz:latest image: postgres:17
container_name: luz container_name: luz-db
restart: unless-stopped restart: unless-stopped
environment:
POSTGRES_DB: luz
POSTGRES_USER: luz
POSTGRES_PASSWORD: ${DB_PASSWORD}
TZ: Europe/Paris
healthcheck:
test: ["CMD-SHELL", "pg_isready -U luz -d luz"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- ${DATA_PATH:-~/Applications/data}/luz/db_data:/var/lib/postgresql/data
networks:
- luz-net
backend:
image: git.goutailler-olivier.com/luz/luz-backend:main
container_name: luz-backend
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DB_HOST: db
DB_PORT: 5432
DB_NAME: luz
DB_USER: luz
DB_PASSWORD: ${DB_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
TZ: Europe/Paris
networks:
- luz-net
- proxy
labels:
- com.centurylinklabs.watchtower.enable=true
- traefik.enable=true
- traefik.http.routers.luz-backend.rule=Host(`luz.goutailler-olivier.com`) && PathPrefix(`/api`)
- traefik.http.routers.luz-backend.entrypoints=websecure
- traefik.http.routers.luz-backend.tls.certresolver=le
- traefik.http.services.luz-backend.loadbalancer.server.port=8080
- traefik.docker.network=proxy
webapp:
image: git.goutailler-olivier.com/luz/luz-webapp:main
container_name: luz-webapp
restart: unless-stopped
depends_on:
- backend
environment: environment:
TZ: Europe/Paris TZ: Europe/Paris
networks: networks:
- proxy - proxy
labels: labels:
- traefik.enable=true
- traefik.http.routers.luz.rule=Host(`luz.goutailler-olivier.com`)
- traefik.http.routers.luz.entrypoints=websecure
- traefik.http.routers.luz.tls.certresolver=le
- traefik.http.services.luz.loadbalancer.server.port=80
- traefik.docker.network=proxy
- com.centurylinklabs.watchtower.enable=true - com.centurylinklabs.watchtower.enable=true
- traefik.enable=true
- traefik.http.routers.luz-webapp.rule=Host(`luz.goutailler-olivier.com`)
- traefik.http.routers.luz-webapp.entrypoints=websecure
- traefik.http.routers.luz-webapp.tls.certresolver=le
- traefik.http.services.luz-webapp.loadbalancer.server.port=80
- traefik.docker.network=proxy
networks: networks:
luz-net:
driver: bridge
proxy: proxy:
external: true external: true
name: proxy name: proxy
+14
View File
@@ -0,0 +1,14 @@
# Copier ce fichier en .env et renseigner les valeurs avant de démarrer.
# cp env.example .env
# Mot de passe PostgreSQL partagé entre le container db et Nextcloud.
POSTGRES_PASSWORD=changeme
# Compte administrateur Nextcloud — créé uniquement au premier démarrage.
# Pour le modifier après initialisation, passer par l'interface d'administration.
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=changeme
# Identifiants pgAdmin (interface web d'administration PostgreSQL).
PGADMIN_DEFAULT_EMAIL=admin@example.com
PGADMIN_DEFAULT_PASSWORD=changeme
+6 -6
View File
@@ -13,7 +13,7 @@ services:
environment: environment:
POSTGRES_DB: nextcloud POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: changeme POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
TZ: Europe/Paris TZ: Europe/Paris
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
@@ -36,10 +36,10 @@ services:
POSTGRES_HOST: db POSTGRES_HOST: db
POSTGRES_DB: nextcloud POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: changeme POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
NEXTCLOUD_ADMIN_USER: admin NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
NEXTCLOUD_ADMIN_PASSWORD: adminpass NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: cloud.goutailler-olivier.com NEXTCLOUD_TRUSTED_DOMAINS: cloud.goutailler-olivier.com
NEXTCLOUD_OVERWRITEHOST: cloud.goutailler-olivier.com NEXTCLOUD_OVERWRITEHOST: cloud.goutailler-olivier.com
@@ -77,8 +77,8 @@ services:
container_name: nextcloud-pgadmin container_name: nextcloud-pgadmin
restart: unless-stopped restart: unless-stopped
environment: environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
PGADMIN_DEFAULT_PASSWORD: adminpass PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
PGADMIN_CONFIG_SERVER_MODE: 'False' PGADMIN_CONFIG_SERVER_MODE: 'False'
TZ: Europe/Paris TZ: Europe/Paris
volumes: volumes:
+14
View File
@@ -0,0 +1,14 @@
# Copier en .env et définir des valeurs sécurisées avant de démarrer.
# cp .env.example .env
# Mot de passe PostgreSQL partagé entre le container db et l'API Spring Boot.
POSTGRES_PASSWORD=changeme
# Secret JWT utilisé pour signer les tokens d'authentification.
# Générer une valeur forte : openssl rand -hex 32
JWT_SECRET=changeme-in-production-use-a-long-random-string-at-least-256-bits
# Dossier racine pour les données persistantes (BDD + uploads).
# La BDD sera dans ${DATA_PATH}/olhar/db_data
# Les photos dans ${DATA_PATH}/olhar/uploads
DATA_PATH=/home/gato/Applications/data
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_URL="ssh://git@git.goutailler-olivier.com:2222/Gato/Olhar.git"
BUILD_DIR="${HOME}/Applications/build/Olhar"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# ── 1. Récupérer les sources ──────────────────────────────────────────────────
if [ -d "${BUILD_DIR}/.git" ]; then
echo "▶ Mise à jour du dépôt (develop)..."
git -C "${BUILD_DIR}" fetch origin develop
git -C "${BUILD_DIR}" reset --hard origin/develop
else
echo "▶ Clonage du dépôt..."
git clone --branch develop --depth 1 "${REPO_URL}" "${BUILD_DIR}"
fi
# ── 2. Build & démarrage du stack ────────────────────────────────────────────
echo "▶ Build des images et démarrage du stack..."
docker compose \
-f "${BUILD_DIR}/docker-compose.yml" \
--env-file "${SCRIPT_DIR}/.env" \
up --build -d
echo "✓ Stack démarré."
+52 -5
View File
@@ -1,17 +1,62 @@
name: olhar name: olhar
services: services:
oalhar: db:
image: git.goutailler-olivier.com/gato/olhar-pwa:latest image: postgres:16-alpine
container_name: olhar container_name: olhar-db
restart: unless-stopped restart: unless-stopped
environment:
POSTGRES_DB: olhar
POSTGRES_USER: olhar
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
TZ: Europe/Paris
healthcheck:
test: ["CMD-SHELL", "pg_isready -U olhar -d olhar"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- ~/Applications/data/olhar/db_data:/var/lib/postgresql/data
networks:
- olhar-net
api:
image: git.goutailler-olivier.com/gato/olhar-api:latest
container_name: olhar-api
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATASOURCE_URL: jdbc:postgresql://db:5432/olhar
DATASOURCE_USERNAME: olhar
DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
CORS_ALLOWED_ORIGIN_PROD: https://olhar.goutailler-olivier.com
PHOTOS_STORAGE_PATH: /app/uploads
TZ: Europe/Paris
volumes:
- ~/Applications/data/olhar/uploads:/app/uploads
networks:
- olhar-net
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.olhar-api.rule=Host(`olhar.goutailler-olivier.com`) && PathPrefix(`/api`)
- traefik.http.routers.olhar-api.entrypoints=websecure
- traefik.http.routers.olhar-api.tls.certresolver=le
- traefik.http.services.olhar-api.loadbalancer.server.port=8080
- traefik.docker.network=proxy
- com.centurylinklabs.watchtower.enable=true
pwa:
image: git.goutailler-olivier.com/gato/olhar-pwa:latest
container_name: olhar-pwa
restart: unless-stopped
environment: environment:
TZ: Europe/Paris TZ: Europe/Paris
networks: networks:
- proxy - proxy
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.http.routers.olhar.rule=Host(`olhar.goutailler-olivier.com`) - traefik.http.routers.olhar.rule=Host(`olhar.goutailler-olivier.com`)
@@ -22,6 +67,8 @@ services:
- com.centurylinklabs.watchtower.enable=true - com.centurylinklabs.watchtower.enable=true
networks: networks:
olhar-net:
driver: bridge
proxy: proxy:
external: true external: true
name: proxy name: proxy
+8
View File
@@ -1 +1,9 @@
# Jeton d'authentification de l'API HTTP de Watchtower (déclenchement du /v1/update
# par la CI). Doit être identique au secret WATCHTOWER_TOKEN côté Gitea Actions.
WATCHTOWER_TOKEN=votre_token_secret_ici WATCHTOWER_TOKEN=votre_token_secret_ici
# Identifiants de pull du registre Gitea privé (git.goutailler-olivier.com).
# PAT en LECTURE seule (scope read:package) d'un compte membre de l'org Luz.
# Utilisés par Watchtower (REPO_USER/REPO_PASS) pour tirer luz-backend/luz-webapp.
REGISTRY_USER=nom_utilisateur_gitea
REGISTRY_PASS=pat_read_package_ici
+12 -1
View File
@@ -7,12 +7,23 @@ services:
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- /root/.docker/config.json:/root/.docker/config.json:ro
environment: environment:
WATCHTOWER_HTTP_API_UPDATE: "true" WATCHTOWER_HTTP_API_UPDATE: "true"
WATCHTOWER_HTTP_API_TOKEN: ${WATCHTOWER_TOKEN} WATCHTOWER_HTTP_API_TOKEN: ${WATCHTOWER_TOKEN}
WATCHTOWER_LABEL_ENABLE: "true" WATCHTOWER_LABEL_ENABLE: "true"
WATCHTOWER_CLEANUP: "true" WATCHTOWER_CLEANUP: "true"
# Identifiants de pull du registre Gitea privé (git.goutailler-olivier.com).
# Sans eux, Watchtower échoue à tirer luz-backend/luz-webapp :main
# (« unauthorized: reqPackageAccess ») et ne recrée jamais les conteneurs
# — le déploiement reste bloqué en silence (Updated=0, mais /v1/update → 200).
# Un PAT en LECTURE seule (scope read:package) suffit — mais il lui faut
# accès aux DEUX organisations qui publient des images ici : Luz et, depuis
# que la stack bonsai tire bonsai-api du registre, Bonsai. Un PAT limité à
# Luz laisserait la prod Bonsai bloquée sur son ancienne image, en silence.
# Remplace l'ancien montage de /root/.docker/config.json, qui côté hôte
# ne contenait aucune auth (le docker login du CI a lieu sur le runner).
REPO_USER: ${REGISTRY_USER}
REPO_PASS: ${REGISTRY_PASS}
TZ: Europe/Paris TZ: Europe/Paris
networks: networks:
- proxy - proxy