From 21e050ce29fea8ea3bc417462e95c5b5ae93bd95 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Wed, 1 Apr 2026 11:00:10 +0200 Subject: [PATCH] feat : add Docker prodcution deployment --- .claude/skills/ticket-executor/LEARNINGS.md | 61 ++++ .claude/skills/ticket-executor/SKILL.md | 78 +++++ .dockerignore | 23 ++ .gitea/workflows/build-docker.yml | 30 ++ LOG/xdebug.log | 0 deploy/docker/.env.example | 22 ++ deploy/docker/Dockerfile.prod | 82 +++++ deploy/docker/deploy.sh | 28 ++ deploy/docker/docker-compose.prod.yml | 13 + deploy/docker/nginx.conf | 53 ++++ deploy/docker/supervisord.conf | 28 ++ deploy/nginx/lesstime-docker.conf | 14 + doc/deployment-docker.md | 335 ++++++++++++++++++++ 13 files changed, 767 insertions(+) create mode 100644 .claude/skills/ticket-executor/LEARNINGS.md create mode 100644 .claude/skills/ticket-executor/SKILL.md create mode 100644 .dockerignore create mode 100644 .gitea/workflows/build-docker.yml create mode 100644 LOG/xdebug.log create mode 100644 deploy/docker/.env.example create mode 100644 deploy/docker/Dockerfile.prod create mode 100755 deploy/docker/deploy.sh create mode 100644 deploy/docker/docker-compose.prod.yml create mode 100644 deploy/docker/nginx.conf create mode 100644 deploy/docker/supervisord.conf create mode 100644 deploy/nginx/lesstime-docker.conf create mode 100644 doc/deployment-docker.md diff --git a/.claude/skills/ticket-executor/LEARNINGS.md b/.claude/skills/ticket-executor/LEARNINGS.md new file mode 100644 index 0000000..a5f24ac --- /dev/null +++ b/.claude/skills/ticket-executor/LEARNINGS.md @@ -0,0 +1,61 @@ +# Ticket Executor - Learnings + +## Session 2026-03-17 (26 tickets) + +### T-001 — Secrets .env +- **Pattern**: Replace secrets with `change_me_in_env_local` placeholder, move real values to `.env.local` +- **Gotcha**: `.env.local` must contain ALL overridden secrets + +### T-002 — Security API Gitea +- **Pattern**: Ajouter `security: "is_granted('ROLE_USER')"` sur les opérations ApiResource +- **Learning**: Vérifier d'abord les ressources déjà sécurisées pour ne pas dupliquer + +### T-003 — SVG Upload +- **Pattern**: Double protection - bloquer à l'upload (retirer du MIME allowlist) + defense-in-depth (Content-Disposition: attachment au download) +- **Learning**: Toujours vérifier upload ET download controllers + +### T-004 — MCP create-task / Repos numérotation +- **Gotcha critique**: PostgreSQL n'autorise PAS `FOR UPDATE` avec des fonctions d'agrégation (`MAX`) +- **Fix**: Utiliser `pg_advisory_xact_lock()` au lieu de `FOR UPDATE` pour les queries avec agrégation +- **Pattern**: Offset les lock keys (+1000000) pour éviter collisions entre Task et ClientTicket + +### T-005 — Filter ROLE_CLIENT projects +- **Pattern**: Créer une Doctrine Extension (`QueryCollectionExtensionInterface` + `QueryItemExtensionInterface`) pour filtrer par relation +- **Learning**: Symfony autoconfigure enregistre l'extension automatiquement + +### T-006 — Block client doc upload +- **Pattern**: Vérifier le rôle dans le Processor AVANT de résoudre l'IRI de la tâche +- **Learning**: Le portail client envoie un `clientTicket` IRI (pas de `task` IRI), donc le check sur `taskIri` non-vide suffit + +### T-007 — MCP role checks +- **Pattern**: Injecter `Security` dans chaque Tool, vérifier au début de `__invoke()` +- **Learning**: 22 tools à modifier - bien séparer ROLE_ADMIN (users/clients) vs ROLE_USER (le reste) + +### T-009 — Password hashing +- **Pattern**: Champ `plainPassword` non-persisté, writable uniquement, hashé dans le Processor +- **Learning**: Modifier aussi le frontend (DTO + composant) quand on renomme un champ API + +### T-010 — Rate limiting +- **Gotcha**: `login_throttling` nécessite `symfony/rate-limiter` installé, pas juste dans composer.json +- **Learning**: Toujours vérifier que les packages sont installés, pas juste déclarés + +### T-012 — Harmoniser repos numérotation +- **Pattern**: Aligner les contrats (retourner le max, pas le next) et mettre le +1 côté appelant +- **Learning**: Vérifier TOUS les appelants d'une méthode renommée + +### T-015 — useAvatarService +- **Learning**: Quand on migre vers `useApi()`, ajouter la détection FormData pour ne pas écraser le Content-Type multipart + +### T-020 — i18n +- **Pattern**: Ajouter `useI18n()` dans le setup script avant de pouvoir utiliser `t()` dans le JS +- **Learning**: Les templates peuvent utiliser `$t()` directement sans import + +### T-022 — Retirer twig-bundle +- **Pattern**: Retirer de composer.json + bundles.php + supprimer config YAML + templates +- **Learning**: API Platform ne requiert PAS twig, c'est juste suggéré pour Swagger UI + +## Meta-learnings +- **Parallélisation**: Les tickets touchant des fichiers indépendants peuvent tourner en parallèle sans problème +- **MCP status**: Toujours mettre "En cours" AVANT de commencer, "Terminé" APRÈS validation +- **PostgreSQL gotchas**: Tester les queries SQL avec agrégation + locking sur PostgreSQL, pas MySQL +- **Agents**: Les agents simples (1-3 fichiers) terminent en ~30s, les complexes (22 fichiers) en ~8min diff --git a/.claude/skills/ticket-executor/SKILL.md b/.claude/skills/ticket-executor/SKILL.md new file mode 100644 index 0000000..619ccbe --- /dev/null +++ b/.claude/skills/ticket-executor/SKILL.md @@ -0,0 +1,78 @@ +--- +name: ticket-executor +description: Execute Lesstime project tickets systematically - updates MCP statuses, follows project conventions, and logs learnings for self-improvement +--- + +# Ticket Executor Skill + +## Purpose +Execute Lesstime project tickets end-to-end: read the ticket, implement the fix, update MCP status, and log learnings. + +## Workflow + +### 1. Receive Ticket +- Get ticket ID, title, description, tags (Backend/Frontend), priority, and current status +- Understand the scope from the title and description + +### 2. Set Status to "En cours" (ID: 2) +- Use MCP `update-task` with `statusId: 2` before starting work +- MCP endpoint: `http://project.malio-dev.fr/_mcp` +- Auth: `Bearer 7e8b410a5b79b5c0432951dcee3a3a81e0731e86d9f70d8784ec079a2b759c64` + +### 3. Analyze & Implement +Based on tag: +- **Backend**: Check `src/Entity/`, `src/State/`, `src/Controller/`, `src/Security/`, `config/` +- **Frontend**: Check `frontend/components/`, `frontend/composables/`, `frontend/pages/`, `frontend/services/` + +Conventions to follow: +- PHP: `declare(strict_types=1)`, Symfony + PSR-12, API Platform patterns +- Frontend: TypeScript strict, `useApi()` composable, 4 spaces indent +- See CLAUDE.md for full conventions + +### 4. Verify +- For Backend: `make php-cs-fixer-allow-risky` if PHP changed +- For Frontend: check TypeScript types, no `any` +- Read modified files to confirm correctness + +### 5. Set Status to "Terminé" (ID: 5) +- Use MCP `update-task` with `statusId: 5` after successful implementation + +### 6. Log Learnings +Append to `.claude/skills/ticket-executor/LEARNINGS.md`: +- What worked well +- Patterns discovered +- Gotchas encountered +- Time-saving shortcuts found + +## MCP Session Management +The MCP HTTP transport requires a session. To call tools: +```bash +# Initialize session (get Mcp-Session-Id from response header) +curl -si -X POST http://project.malio-dev.fr/_mcp \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude","version":"1.0"}}}' + +# Call tool (use Mcp-Session-Id from init response) +curl -s -X POST http://project.malio-dev.fr/_mcp \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -H "Mcp-Session-Id: " \ + -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"update-task","arguments":{"id":,"statusId":}}}' +``` + +## Status IDs +- 1 = A faire +- 2 = En cours +- 3 = Bloqué +- 4 = En attente de validation +- 5 = Terminé + +## Learnings Integration +Before each ticket, read `LEARNINGS.md` to apply previous insights. +After each ticket, append new learnings. This creates a feedback loop that improves execution quality over time. + +## Parallel Execution Rules +- Independent tickets (no shared files) can run in parallel via worktree agents +- Tickets modifying the same files must run sequentially +- Always verify no merge conflicts after parallel execution diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..dbb6194 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,23 @@ +.git +.gitea +.env.local +.env.test +docker/ +deploy/docker/docker-compose.prod.yml +deploy/docker/deploy.sh +deploy/docker/.env.example +frontend/node_modules +frontend/.nuxt +frontend/.output +var/ +vendor/ +LOG/ +docs/ +tests/ +*.sql +*.xlsx +*.png +*.md +!composer.lock +!symfony.lock +!frontend/package-lock.json diff --git a/.gitea/workflows/build-docker.yml b/.gitea/workflows/build-docker.yml new file mode 100644 index 0000000..892501b --- /dev/null +++ b/.gitea/workflows/build-docker.yml @@ -0,0 +1,30 @@ +name: Build & Push Docker Image + +on: + push: + tags: + - "v*" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Login to Gitea Registry + run: | + echo "${{ secrets.REGISTRY_TOKEN }}" | docker login gitea.malio.fr -u "${{ gitea.repository_owner }}" --password-stdin + + - name: Build Docker image + run: | + docker build \ + -f deploy/docker/Dockerfile.prod \ + -t gitea.malio.fr/malio/lesstime:${{ gitea.ref_name }} \ + -t gitea.malio.fr/malio/lesstime:latest \ + . + + - name: Push Docker image + run: | + docker push gitea.malio.fr/malio/lesstime:${{ gitea.ref_name }} + docker push gitea.malio.fr/malio/lesstime:latest diff --git a/LOG/xdebug.log b/LOG/xdebug.log new file mode 100644 index 0000000..e69de29 diff --git a/deploy/docker/.env.example b/deploy/docker/.env.example new file mode 100644 index 0000000..d0caa7c --- /dev/null +++ b/deploy/docker/.env.example @@ -0,0 +1,22 @@ +# Symfony +APP_ENV=prod +APP_DEBUG=0 +APP_SECRET=change-me + +# Database (use host.docker.internal to reach bare-metal PostgreSQL) +DATABASE_URL="postgresql://lesstime_user:password@host.docker.internal:5432/lesstime_prod?serverVersion=16&charset=utf8" + +# JWT +JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem +JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem +JWT_PASSPHRASE=change-me +JWT_COOKIE_SECURE=1 +JWT_COOKIE_SAMESITE=lax +JWT_TOKEN_TTL=86400 +JWT_COOKIE_TTL=86400 + +# CORS +CORS_ALLOW_ORIGIN='^https?://project\.malio-dev\.fr$' + +# App +DEFAULT_URI=https://project.malio-dev.fr diff --git a/deploy/docker/Dockerfile.prod b/deploy/docker/Dockerfile.prod new file mode 100644 index 0000000..bcf6808 --- /dev/null +++ b/deploy/docker/Dockerfile.prod @@ -0,0 +1,82 @@ +# --- Stage 1: Build backend --- +FROM php:8.4-cli AS backend-build + +RUN apt-get update && apt-get install -y \ + libicu-dev libpq-dev libpng-dev libzip-dev libxml2-dev \ + unzip curl git \ + && docker-php-ext-install -j$(nproc) intl pdo_pgsql zip gd opcache \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +WORKDIR /app +COPY composer.json composer.lock symfony.lock ./ +RUN APP_ENV=prod APP_DEBUG=0 composer install --no-dev --no-scripts --no-interaction + +COPY bin bin/ +COPY config config/ +COPY migrations migrations/ +COPY public public/ +COPY src src/ +COPY templates templates/ + +RUN composer dump-autoload --optimize --no-dev + +# --- Stage 2: Build frontend --- +FROM node:lts-alpine AS frontend-build + +WORKDIR /app/frontend +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci + +COPY frontend/ ./ +ENV CI=1 \ + NUXT_TELEMETRY_DISABLED=1 \ + NUXT_PUBLIC_API_BASE=/api \ + NUXT_PUBLIC_APP_BASE=/ +RUN npm run generate + +# --- Stage 3: Production image --- +FROM php:8.4-fpm AS production + +RUN apt-get update && apt-get install -y \ + libicu-dev libpq-dev libpng-dev libzip-dev libxml2-dev \ + nginx supervisor \ + && docker-php-ext-install -j$(nproc) intl pdo_pgsql zip gd opcache \ + && rm -rf /var/lib/apt/lists/* + +# PHP production config +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +# PHP-FPM: forward worker output to stderr for docker logs +RUN echo "catch_workers_output = yes" >> /usr/local/etc/php-fpm.d/www.conf \ + && echo "decorate_workers_output = no" >> /usr/local/etc/php-fpm.d/www.conf + +# Nginx: log to stdout/stderr +RUN ln -sf /dev/stdout /var/log/nginx/access.log \ + && ln -sf /dev/stderr /var/log/nginx/error.log + +# Remove default nginx site +RUN rm -f /etc/nginx/sites-enabled/default + +# Configs +COPY deploy/docker/supervisord.conf /etc/supervisor/conf.d/app.conf +COPY deploy/docker/nginx.conf /etc/nginx/sites-enabled/lesstime.conf + +# Backend from stage 1 +COPY --from=backend-build /app /var/www/html + +# Frontend from stage 2 +COPY --from=frontend-build /app/frontend/.output/public /var/www/html/frontend/.output/public + +# Symfony needs a .env file to boot (variables are overridden by env_file in docker-compose) +RUN echo "APP_ENV=prod" > /var/www/html/.env + +# Permissions +RUN mkdir -p /var/www/html/var /var/www/html/var/uploads \ + && chown -R www-data:www-data /var/www/html/var + +WORKDIR /var/www/html +EXPOSE 80 + +CMD ["supervisord", "-n", "-c", "/etc/supervisor/conf.d/app.conf"] diff --git a/deploy/docker/deploy.sh b/deploy/docker/deploy.sh new file mode 100755 index 0000000..e46e7da --- /dev/null +++ b/deploy/docker/deploy.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +TAG="${1:-latest}" +export LESSTIME_IMAGE_TAG="$TAG" + +echo "==> Deploying lesstime:${TAG}..." + +echo "==> Pulling image..." +docker compose pull + +echo "==> Starting container..." +docker compose up -d + +echo "==> Waiting for container to be ready..." +sleep 3 + +echo "==> Running migrations..." +docker compose exec -T -u www-data app php bin/console doctrine:migrations:migrate --no-interaction + +echo "==> Clearing cache..." +docker compose exec -T -u www-data app php bin/console cache:clear --env=prod +docker compose exec -T -u www-data app php bin/console cache:warmup --env=prod + +VERSION=$(docker compose exec -T app cat config/version.yaml | grep 'app.version' | awk -F"'" '{print $2}') +echo "==> Deployed v${VERSION}" diff --git a/deploy/docker/docker-compose.prod.yml b/deploy/docker/docker-compose.prod.yml new file mode 100644 index 0000000..bf1d86f --- /dev/null +++ b/deploy/docker/docker-compose.prod.yml @@ -0,0 +1,13 @@ +services: + app: + image: gitea.malio.fr/malio/lesstime:${LESSTIME_IMAGE_TAG:-latest} + container_name: lesstime-app + env_file: .env + ports: + - "8080:80" + volumes: + - ./config/jwt:/var/www/html/config/jwt:ro + - ./uploads:/var/www/html/var/uploads + extra_hosts: + - "host.docker.internal:host-gateway" + restart: unless-stopped diff --git a/deploy/docker/nginx.conf b/deploy/docker/nginx.conf new file mode 100644 index 0000000..e058faf --- /dev/null +++ b/deploy/docker/nginx.conf @@ -0,0 +1,53 @@ +server { + listen 80; + server_name _; + + root /var/www/html/frontend/.output/public; + index index.html; + + client_max_body_size 55m; + + access_log /dev/stdout; + error_log /dev/stderr; + + location ^~ /api/ { + root /var/www/html/public; + try_files $uri /index.php?$query_string; + } + + location ^~ /bundles/ { + root /var/www/html/public; + try_files $uri =404; + } + + location = /api/login_check { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME /var/www/html/public/index.php; + fastcgi_param DOCUMENT_ROOT /var/www/html/public; + fastcgi_param SCRIPT_NAME /index.php; + fastcgi_param PATH_INFO /login_check; + fastcgi_param REQUEST_URI /login_check; + fastcgi_pass 127.0.0.1:9000; + } + + location ^~ /_mcp { + root /var/www/html/public; + try_files $uri /index.php?$query_string; + } + + location ~ ^/index\.php(/|$) { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME /var/www/html/public/index.php; + fastcgi_param DOCUMENT_ROOT /var/www/html/public; + fastcgi_pass 127.0.0.1:9000; + internal; + } + + location ~ \.php$ { + return 404; + } + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/deploy/docker/supervisord.conf b/deploy/docker/supervisord.conf new file mode 100644 index 0000000..dafb36a --- /dev/null +++ b/deploy/docker/supervisord.conf @@ -0,0 +1,28 @@ +[supervisord] +nodaemon=true +user=root +logfile=/dev/null +logfile_maxbytes=0 +pidfile=/var/run/supervisord.pid + +[program:php-fpm] +command=php-fpm -F +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +stopasgroup=true +stopsignal=QUIT + +[program:nginx] +command=nginx -g "daemon off;" +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +stopasgroup=true +stopsignal=QUIT diff --git a/deploy/nginx/lesstime-docker.conf b/deploy/nginx/lesstime-docker.conf new file mode 100644 index 0000000..b34e7cb --- /dev/null +++ b/deploy/nginx/lesstime-docker.conf @@ -0,0 +1,14 @@ +server { + listen 80; + listen [::]:80; + server_name project.malio-dev.fr; + + location / { + proxy_pass http://127.0.0.1:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + client_max_body_size 55m; + } +} diff --git a/doc/deployment-docker.md b/doc/deployment-docker.md new file mode 100644 index 0000000..3cf6556 --- /dev/null +++ b/doc/deployment-docker.md @@ -0,0 +1,335 @@ +# Deploiement Docker — Lesstime + +## Pre-requis + +### Docker + +```bash +# Ubuntu +sudo apt update +sudo apt install -y ca-certificates curl gnupg +sudo install -m 0755 -d /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg +echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null +sudo apt update +sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin +sudo usermod -aG docker $USER +``` + +Se deconnecter/reconnecter pour que le groupe `docker` prenne effet. + +### Nginx + +```bash +sudo apt install -y nginx +sudo systemctl enable nginx +sudo systemctl start nginx +``` + +### PostgreSQL + +PostgreSQL tourne dans un conteneur Docker separe (voir le repo `infra-postgres`). +Il doit etre installe et accessible avant de deployer Lesstime. + +Creer la base de donnees pour Lesstime : + +```bash +cd /var/www/postgres +docker compose exec postgres psql -U admin +``` + +```sql +-- Si le user n'existe pas encore +CREATE USER malio WITH PASSWORD 'motdepasse'; + +-- Creer la base +CREATE DATABASE lesstime_prod OWNER malio; +\q +``` + +--- + +## Premiere installation (nouvelle machine) + +Guide complet pour mettre en ligne Lesstime sur une machine vierge. Inclut les pre-requis, la BDD et l'app. + +### 1. Installer les pre-requis + +Installer Docker, Nginx et PostgreSQL (voir section Pre-requis ci-dessus). + +### 2. Creer le dossier de deploiement + +```bash +sudo mkdir -p /var/www/lesstime +sudo chown -R $(whoami):$(whoami) /var/www/lesstime +cd /var/www/lesstime +``` + +### 3. Se connecter au registry Docker de Gitea + +```bash +docker login gitea.malio.fr +``` + +- **Username** : le nom d'utilisateur du compte organisation Gitea `MALIO` +- **Password** : le token REGISTRY_TOKEN dispo dans le bitwarden + +Le login est sauvegarde dans `~/.docker/config.json`, pas besoin de le refaire a chaque deploiement. + +### 4. Creer les fichiers de deploiement + +Creer `docker-compose.yml` : + +```yaml +services: + app: + image: gitea.malio.fr/malio/lesstime:${LESSTIME_IMAGE_TAG:-latest} + container_name: lesstime-app + env_file: .env + ports: + - "8080:80" + volumes: + - ./config/jwt:/var/www/html/config/jwt:ro + - ./uploads:/var/www/html/var/uploads + extra_hosts: + - "host.docker.internal:host-gateway" + restart: unless-stopped +``` + +Creer `deploy.sh` : + +```bash +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +TAG="${1:-latest}" +export LESSTIME_IMAGE_TAG="$TAG" + +echo "==> Deploying lesstime:${TAG}..." + +echo "==> Pulling image..." +docker compose pull + +echo "==> Starting container..." +docker compose up -d + +echo "==> Waiting for container to be ready..." +sleep 3 + +echo "==> Running migrations..." +docker compose exec -T -u www-data app php bin/console doctrine:migrations:migrate --no-interaction + +echo "==> Clearing cache..." +docker compose exec -T -u www-data app php bin/console cache:clear --env=prod +docker compose exec -T -u www-data app php bin/console cache:warmup --env=prod + +VERSION=$(docker compose exec -T app cat config/version.yaml | grep 'app.version' | awk -F"'" '{print $2}') +echo "==> Deployed v${VERSION}" +``` + +Rendre executable : + +```bash +chmod +x deploy.sh +``` + +### 5. Configurer l'environnement + +Creer `.env` avec les variables suivantes : + +```env +# Symfony +APP_ENV=prod +APP_DEBUG=0 +APP_SECRET= + +# Database (host.docker.internal = la machine hote, ou le PG tourne en Docker) +DATABASE_URL="postgresql://malio:password@host.docker.internal:5432/lesstime_prod?serverVersion=16&charset=utf8" + +# JWT +JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem +JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem +JWT_PASSPHRASE= +JWT_COOKIE_SECURE=1 +JWT_COOKIE_SAMESITE=lax +JWT_TOKEN_TTL=86400 +JWT_COOKIE_TTL=86400 + +# CORS +CORS_ALLOW_ORIGIN='^https?://project\.malio-dev\.fr$' + +# App +DEFAULT_URI=https://project.malio-dev.fr +``` + +### 6. Generer les cles JWT + +```bash +mkdir -p config/jwt +openssl genpkey -algorithm RSA -out config/jwt/private.pem -pkeyopt rsa_keygen_bits:4096 +openssl rsa -pubout -in config/jwt/private.pem -out config/jwt/public.pem +``` + +Rendre les cles lisibles par le conteneur (www-data = uid 33) : + +```bash +sudo chown 33:33 config/jwt/private.pem config/jwt/public.pem +sudo chmod 644 config/jwt/private.pem config/jwt/public.pem +``` + +### 7. Creer le dossier uploads + +```bash +mkdir -p uploads +``` + +### 8. Configurer Nginx systeme + +Creer `/etc/nginx/sites-available/lesstime.conf` : + +```nginx +server { + listen 80; + server_name project.malio-dev.fr; + + client_max_body_size 55m; + + location / { + proxy_pass http://127.0.0.1:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +``` + +Activer le site : + +```bash +sudo ln -sf /etc/nginx/sites-available/lesstime.conf /etc/nginx/sites-enabled/lesstime.conf +sudo nginx -t && sudo systemctl reload nginx +``` + +### 9. Deployer + +```bash +./deploy.sh +``` + +### 10. Importer les donnees (optionnel) + +Si tu as un dump SQL a importer : + +```bash +# Depuis ton PC, envoyer le dump vers le serveur +scp lesstime.sql user@serveur:/tmp/lesstime.sql + +# Sur le serveur, vider la base puis importer +cd /var/www/postgres +docker compose exec -T postgres psql -U malio lesstime_prod -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" +docker compose exec -T postgres psql -U malio lesstime_prod < /tmp/lesstime.sql + +# Creer les tables manquantes (si le dump a des erreurs de syntaxe) +cd /var/www/lesstime +docker compose exec -u www-data app php bin/console doctrine:schema:update --force --env=prod + +# Nettoyer +rm /tmp/lesstime.sql +``` + +### Structure finale du dossier + +``` +/var/www/lesstime/ +├── docker-compose.yml +├── deploy.sh +├── .env +├── config/jwt/ +│ ├── private.pem +│ └── public.pem +└── uploads/ +``` + +--- + +## Deployer une nouvelle version + +Quand l'app est deja installee, deployer une mise a jour : + +```bash +cd /var/www/lesstime +./deploy.sh # deploie la derniere version (latest) +./deploy.sh v0.3.13 # deploie une version specifique +``` + +C'est tout. Le script pull l'image, redemarre le conteneur, lance les migrations et vide le cache. + +--- + +## Rollback + +### Image seule (pas de changement de schema BDD) + +```bash +./deploy.sh v0.3.12 +``` + +### Avec rollback de migration + +```bash +# 1. Rollback schema (pendant que la version actuelle tourne encore) +docker compose exec -T -u www-data app php bin/console doctrine:migrations:migrate prev --no-interaction +# 2. Deployer l'ancienne version +./deploy.sh v0.3.12 +``` + +--- + +## CI/CD + +Le workflow `.gitea/workflows/build-docker.yml` se declenche automatiquement sur push de tag `v*` : +1. Build l'image multi-stage +2. Push vers `gitea.malio.fr/malio/lesstime:` et `:latest` + +Combine avec `auto-tag-develop.yml`, chaque push sur `develop` cree automatiquement un tag → build → image disponible. + +--- + +## Voir les logs + +```bash +cd /var/www/lesstime +docker compose logs -f # tous les logs +docker compose logs -f --tail=100 # 100 dernieres lignes +``` + +Logs Symfony : + +```bash +docker compose exec app cat var/log/prod.log +``` + +--- + +## Migration depuis l'ancien deploiement (bare-metal) + +Si l'application tourne deja en bare metal : + +1. Installer Docker (voir pre-requis) +2. Creer le dossier `/var/www/lesstime-docker/` (ne pas ecraser l'ancien) +3. Copier les fichiers existants : + ```bash + cp /var/www/lesstime/.env /var/www/lesstime-docker/.env + cp -a /var/www/lesstime/config/jwt /var/www/lesstime-docker/config/jwt + cp -a /var/www/lesstime/var/uploads /var/www/lesstime-docker/uploads + ``` +4. Creer `docker-compose.yml` et `deploy.sh` dans `/var/www/lesstime-docker/` (voir etape 4 ci-dessus) +5. Editer `/var/www/lesstime-docker/.env` : changer `DATABASE_URL` pour utiliser `host.docker.internal` au lieu de `127.0.0.1` +6. Se connecter au registry Gitea (voir etape 3 ci-dessus) +7. Mettre a jour Nginx systeme avec la conf reverse proxy (voir etape 8 ci-dessus) +8. Arreter l'ancien PHP-FPM : `sudo systemctl stop php8.4-fpm` +9. Deployer : `cd /var/www/lesstime-docker && ./deploy.sh` +10. Verifier que tout marche, puis renommer le dossier : `mv /var/www/lesstime-docker /var/www/lesstime`