Compare commits

...

27 Commits

Author SHA1 Message Date
gitea-actions
916f4ae101 chore: bump version to v0.3.26
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 22s
2026-04-03 12:04:40 +00:00
45d389c67f docs : guide de configuration du mode maintenance en prod
All checks were successful
Auto Tag Develop / tag (push) Successful in 8s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:03:57 +02:00
gitea-actions
7f12332cf6 chore: bump version to v0.3.25
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Build & Push Docker Image / build (push) Successful in 22s
2026-04-03 12:03:43 +00:00
fe30f03b9f docs : ajout maintenance mode dans la doc de deploiement
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:03:30 +02:00
gitea-actions
fc472d5dad chore: bump version to v0.3.24
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 18s
2026-04-03 11:56:09 +00:00
a0a2f27eac fix(infra) : extraire maintenance.html du container au deploy
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:56:02 +02:00
gitea-actions
bd7adec2f0 chore: bump version to v0.3.23
All checks were successful
Build & Push Docker Image / build (push) Successful in 19s
Auto Tag Develop / tag (push) Successful in 5s
2026-04-03 11:54:49 +00:00
9b6386c4ae fix(infra) : root nginx-proxy vers public/ pour maintenance.html
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:54:42 +02:00
gitea-actions
9da1ae7ca1 chore: bump version to v0.3.22
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 18s
2026-04-03 11:50:10 +00:00
bc8bed3339 feat(infra) : ajout maintenance mode dans nginx-proxy
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:49:50 +02:00
gitea-actions
3fee678bd2 chore: bump version to v0.3.21
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 22s
2026-04-03 11:10:14 +00:00
be720178c2 feat(infra) : add maintenance mode during deployments
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Nginx returns a 503 page when maintenance.on exists. The deploy script
automatically enables/disables maintenance mode around the update.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:09:39 +02:00
gitea-actions
eec0294f3e chore: bump version to v0.3.20
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 49s
2026-04-03 07:39:34 +00:00
59a1c7956c fix(auth) : allow Enter key to submit login form
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 09:38:17 +02:00
gitea-actions
e86949a1d7 chore: bump version to v0.3.19
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 20s
2026-04-02 12:12:10 +00:00
Matthieu
7ca62bfc46 chore(infra) : remove release artefact pipeline and baremetal deploy
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Keep only Docker-based deployment workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:11:58 +02:00
gitea-actions
b60e4ae670 chore: bump version to v0.3.18
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 1m7s
Build Release Artefact / build (push) Successful in 1m51s
2026-04-02 10:11:41 +00:00
ace52f8fc5 fix(mcp) : add mcp-sessions dir in prod Dockerfile + add time tracking rule doc
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 22:59:43 +02:00
1ae9535516 refactor : reorganize infra files into infra/dev and infra/prod
Consolidate Docker, Nginx, and deploy configs from 5 scattered directories
(docker/, deploy/docker/, deploy/nginx/, script/) into a single infra/ tree
with dev/ and prod/ subdirectories. Update all references in docker-compose,
Makefile, CI workflows, Dockerfiles, and documentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 22:36:10 +02:00
gitea-actions
b50cfb5049 chore: bump version to v0.3.17
All checks were successful
Auto Tag Develop / tag (push) Successful in 4s
Build & Push Docker Image / build (push) Successful in 19s
Build Release Artefact / build (push) Successful in 2m5s
2026-04-01 10:01:14 +00:00
Matthieu
a5227b9936 fix : use sudo docker and port 8081 in deploy scripts
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 12:01:05 +02:00
gitea-actions
0d298db797 chore: bump version to v0.3.16
All checks were successful
Auto Tag Develop / tag (push) Successful in 4s
Build & Push Docker Image / build (push) Successful in 16s
Build Release Artefact / build (push) Successful in 2m2s
2026-04-01 09:24:34 +00:00
Matthieu
cbe71a1f32 fix : use malio-dev registry namespace instead of malio
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 11:24:26 +02:00
gitea-actions
a8fa8fd7e0 chore: bump version to v0.3.15
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 58s
Build Release Artefact / build (push) Successful in 2m13s
2026-04-01 09:15:52 +00:00
Matthieu
4aa2abd396 fix : remove COPY templates from Dockerfile.prod (dir does not exist)
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 11:15:43 +02:00
gitea-actions
fa3326e99c chore: bump version to v0.3.14
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 6s
Build Release Artefact / build (push) Successful in 1m54s
2026-04-01 09:07:03 +00:00
Matthieu
21e050ce29 feat : add Docker prodcution deployment
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
2026-04-01 11:00:10 +02:00
33 changed files with 1147 additions and 230 deletions

View File

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

View File

@@ -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 <token>" \
-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 <token>" \
-H "Content-Type: application/json" \
-H "Mcp-Session-Id: <session-id>" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"update-task","arguments":{"id":<taskId>,"statusId":<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

24
.dockerignore Normal file
View File

@@ -0,0 +1,24 @@
.git
.gitea
.env.local
.env.test
infra/dev/
infra/prod/docker-compose.yml
infra/prod/deploy.sh
infra/prod/deploy-release.sh
infra/prod/.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

View File

@@ -60,7 +60,7 @@ JWT_COOKIE_TTL=86400
# Base de donnees (Doctrine / PostgreSQL)
# ===========================================================================
# Les variables POSTGRES_* sont definies dans docker/.env.docker
# Les variables POSTGRES_* sont definies dans infra/dev/.env.docker
# et injectees automatiquement par Docker Compose.
# DATABASE_URL est construite a partir de ces variables.
DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/${POSTGRES_DB}?serverVersion=16&charset=utf8"
@@ -74,10 +74,10 @@ DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:${POSTGRES_P
ENCRYPTION_KEY=change_me_in_env_local
# ===========================================================================
# Docker (docker/.env.docker)
# Docker (infra/dev/.env.docker)
#
# Ces variables sont lues par Docker Compose. Voir docker/.env.docker
# pour les valeurs par defaut. Creez docker/.env.docker.local pour
# Ces variables sont lues par Docker Compose. Voir infra/dev/.env.docker
# pour les valeurs par defaut. Creez infra/dev/.env.docker.local pour
# surcharger localement.
# ===========================================================================

View File

@@ -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 infra/prod/Dockerfile \
-t gitea.malio.fr/malio-dev/lesstime:${{ gitea.ref_name }} \
-t gitea.malio.fr/malio-dev/lesstime:latest \
.
- name: Push Docker image
run: |
docker push gitea.malio.fr/malio-dev/lesstime:${{ gitea.ref_name }}
docker push gitea.malio.fr/malio-dev/lesstime:latest

View File

@@ -1,65 +0,0 @@
name: Build Release Artefact
on:
push:
tags:
- "v*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.4"
extensions: mbstring, intl, pdo_pgsql, xml, curl, zip, gd
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "lts/*"
- name: Install backend deps (prod)
env:
APP_ENV: prod
APP_DEBUG: "0"
run: composer install --no-dev --optimize-autoloader --no-interaction --no-scripts
- name: Build frontend (static)
run: |
cd frontend
npm ci
CI=1 NUXT_TELEMETRY_DISABLED=1 NUXT_PUBLIC_API_BASE=/api NUXT_PUBLIC_APP_BASE=/ npm run generate
test -f .output/public/index.html
- name: Build artefact
shell: bash
run: |
set -euo pipefail
mkdir -p release
tar -czf "release/lesstime-${GITHUB_REF_NAME}.tar.gz" \
.env \
bin \
config \
migrations \
public \
src \
vendor \
composer.json \
composer.lock \
symfony.lock \
frontend/.output
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: release/lesstime-${{ github.ref_name }}.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}

2
.gitignore vendored
View File

@@ -28,5 +28,5 @@
###< ide ###
###> docker local ###
docker/.env.docker.local
infra/dev/.env.docker.local
###< docker local ###

View File

@@ -125,7 +125,7 @@ Exemples : `feat : add login page`, `fix(auth) : prevent null token crash`
- Container PHP : `php-lesstime-fpm`
- Container Nginx : `nginx-lesstime`
- Container DB : PostgreSQL sur port **5435** (interne et externe)
- Config Docker : `docker/.env.docker` (override local : `docker/.env.docker.local`)
- Config Docker : `infra/dev/.env.docker` (override local : `infra/dev/.env.docker.local`)
- Après modif nginx : `docker restart nginx-lesstime`
## Fixtures

0
LOG/xdebug.log Normal file
View File

View File

@@ -156,7 +156,7 @@ docker/ # Dockerfiles et config Nginx
| `nginx-lesstime` | 8082 | Nginx reverse proxy |
| PostgreSQL | 5435 | Base de données |
Configuration : `docker/.env.docker` (override local : `docker/.env.docker.local`)
Configuration : `infra/dev/.env.docker` (override local : `infra/dev/.env.docker.local`)
## API

View File

@@ -1,2 +1,2 @@
parameters:
app.version: '0.3.13'
app.version: '0.3.26'

View File

@@ -1,50 +0,0 @@
server {
listen 80;
listen [::]:80;
server_name project.malio-dev.fr;
root /var/www/lesstime/frontend/.output/public;
index index.html;
client_max_body_size 55m;
location ^~ /api/ {
root /var/www/lesstime/public;
try_files $uri /index.php?$query_string;
}
location ^~ /bundles/ {
root /var/www/lesstime/public;
try_files $uri =404;
}
location = /api/login_check {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/lesstime/public/index.php;
fastcgi_param DOCUMENT_ROOT /var/www/lesstime/public;
fastcgi_param SCRIPT_NAME /index.php;
fastcgi_param PATH_INFO /login_check;
fastcgi_param REQUEST_URI /login_check;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}
location ^~ /_mcp {
root /var/www/lesstime/public;
try_files $uri /index.php?$query_string;
}
location ~ ^/index\.php(/|$) {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/lesstime/public/index.php;
fastcgi_param DOCUMENT_ROOT /var/www/lesstime/public;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}
location ~ \.php$ {
return 404;
}
location / {
try_files $uri $uri/ /index.html;
}
}

364
doc/deployment-docker.md Normal file
View File

@@ -0,0 +1,364 @@
# 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-dev/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 "==> Enabling maintenance mode..."
touch maintenance.on
echo "==> Pulling image..."
sudo docker compose pull
echo "==> Starting container..."
sudo docker compose up -d
echo "==> Waiting for container to be ready..."
sleep 3
echo "==> Extracting maintenance page..."
mkdir -p public
sudo docker compose cp app:/var/www/html/public/maintenance.html public/maintenance.html
echo "==> Running migrations..."
sudo docker compose exec -T -u www-data app php bin/console doctrine:migrations:migrate --no-interaction
echo "==> Clearing cache..."
sudo docker compose exec -T -u www-data app php bin/console cache:clear --env=prod
sudo docker compose exec -T -u www-data app php bin/console cache:warmup --env=prod
echo "==> Disabling maintenance mode..."
rm -f maintenance.on
VERSION=$(sudo 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=<generer avec: openssl rand -hex 32>
# 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=<generer avec: openssl rand -hex 32>
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;
listen [::]:80;
server_name project.malio-dev.fr;
root /var/www/lesstime/public;
# Maintenance mode
if (-f /var/www/lesstime/maintenance.on) {
return 503;
}
error_page 503 @maintenance;
location @maintenance {
rewrite ^(.*)$ /maintenance.html break;
}
location = /maintenance.html {
internal;
}
location / {
proxy_pass http://127.0.0.1:8081;
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;
}
}
```
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
├── public/
│ └── maintenance.html # extrait automatiquement par deploy.sh
└── 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-dev/lesstime:<tag>` 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`

View File

@@ -0,0 +1,153 @@
# Configuration du mode maintenance (nginx hote)
Guide pour activer le support du mode maintenance pilote par Central.
Ces etapes sont a faire **une seule fois** par application sur le serveur de production.
Le principe : le nginx de l'hote (reverse proxy) verifie si un fichier `maintenance.on` existe dans le dossier de deploy. Si oui, il sert une page `maintenance.html` au lieu de proxifier vers le container Docker.
Central pilote la creation/suppression de ce fichier via ses volumes Docker.
## Ce qui a ete fait pour Lesstime
### 1. Deployer pour extraire la page maintenance
```bash
cd /var/www/lesstime
sudo ./deploy.sh
```
Le `deploy.sh` extrait automatiquement `maintenance.html` du container vers `public/` :
```
mkdir -p public
sudo docker compose cp app:/var/www/html/public/maintenance.html public/maintenance.html
```
### 2. Mettre a jour la conf nginx de l'hote
Remplacer le contenu de `/etc/nginx/sites-available/lesstime.conf` :
```nginx
server {
listen 80;
listen [::]:80;
server_name project.malio-dev.fr;
root /var/www/lesstime/public;
# Maintenance mode
if (-f /var/www/lesstime/maintenance.on) {
return 503;
}
error_page 503 @maintenance;
location @maintenance {
rewrite ^(.*)$ /maintenance.html break;
}
location = /maintenance.html {
internal;
}
location / {
proxy_pass http://127.0.0.1:8081;
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;
}
}
```
### 3. Recharger nginx
```bash
sudo nginx -t && sudo systemctl reload nginx
```
### 4. Verifier
- Depuis Central, activer la maintenance sur Lesstime
- Ouvrir `http://project.malio-dev.fr` → doit afficher la page "Maintenance en cours"
- Desactiver la maintenance depuis Central → le site revient
---
## A faire pour Inventory
Meme procedure :
### 1. Deployer pour extraire la page maintenance
```bash
cd /var/www/inventory
sudo ./deploy.sh
```
> Si le `deploy.sh` ne contient pas encore l'extraction, mettre a jour le fichier depuis le repo (`infra/prod/deploy.sh`) ou executer manuellement :
> ```bash
> mkdir -p public
> sudo docker compose cp app:/var/www/html/public/maintenance.html public/maintenance.html
> ```
### 2. Mettre a jour la conf nginx de l'hote
Remplacer le contenu de `/etc/nginx/sites-available/inventory.conf` :
```nginx
server {
listen 80;
listen [::]:80;
server_name inventory.malio-dev.fr;
root /var/www/inventory/public;
# Maintenance mode
if (-f /var/www/inventory/maintenance.on) {
return 503;
}
error_page 503 @maintenance;
location @maintenance {
rewrite ^(.*)$ /maintenance.html break;
}
location = /maintenance.html {
internal;
}
location / {
proxy_pass http://127.0.0.1:8082;
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;
}
}
```
### 3. Recharger nginx
```bash
sudo nginx -t && sudo systemctl reload nginx
```
---
## Fonctionnement
```
Central (container)
└── touch /var/www/maintenance/lesstime/maintenance.on
│ (volume Docker : /var/www/lesstime → /var/www/maintenance/lesstime)
/var/www/lesstime/maintenance.on (hote)
nginx hote : if (-f /var/www/lesstime/maintenance.on) → 503
maintenance.html servie depuis /var/www/lesstime/public/
```

View File

@@ -2,7 +2,7 @@ services:
php:
container_name: php-${DOCKER_APP_NAME}-fpm
build:
context: ./docker/php
context: ./infra/dev
dockerfile: Dockerfile
args:
DOCKER_PHP_VERSION: ${DOCKER_PHP_VERSION}
@@ -21,8 +21,8 @@ services:
- ~/.cache:/var/www/.cache # Pour la cache de composer
- ~/.config:/var/www/.config # Pour la config de yarn
- ~/.composer:/var/www/.composer # Pour la config de composer
- ./docker/php/config/php.ini:/usr/local/etc/php/php.ini
- ./docker/php/config/docker-php-ext-xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
- ./infra/dev/php.ini:/usr/local/etc/php/php.ini
- ./infra/dev/xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
- ./LOG:/var/www/html/LOG
- uploads_data:/var/www/html/var/uploads
extra_hosts:
@@ -41,7 +41,7 @@ services:
- "8082:80"
volumes:
- ./:/var/www/html:ro
- ./docker/nginx/conf.d:/etc/nginx/conf.d:ro
- ./infra/dev/nginx.conf:/etc/nginx/conf.d/lesstime.conf:ro
restart: unless-stopped
db:
image: postgres:16-alpine

View File

@@ -0,0 +1,87 @@
# Règle Claude : Time Tracking automatique via Lesstime
> Ajouter ce contenu dans le CLAUDE.md de chaque projet ou dans `~/.claude/CLAUDE.md` pour l'appliquer globalement.
---
## Time Tracking obligatoire
Claude DOIT créer une time entry dans Lesstime au démarrage de chaque tâche de développement, ou sur demande explicite de l'utilisateur ("lance le chrono", "start timer", "track le temps").
### Déclencheurs
1. **Début d'une tâche de dev** : feature, bugfix, refactoring, infra, review
2. **Demande explicite** : "lance le chrono", "start timer", "track le temps"
3. **Depuis un ticket Lesstime** : lier directement au taskId du ticket référencé
### Méthode
Créer la time entry via **curl** sur l'API REST Lesstime :
1. **Login** : `POST http://project.malio-dev.fr/api/login_check`
- Body : `{"username":"admin","password":"admin"}`
- Réponse : 204 avec cookie `Set-Cookie: BEARER=<jwt>`
2. **Créer le timer** : `POST http://project.malio-dev.fr/api/time_entries`
- Headers : `Cookie: BEARER=<jwt>`, `Content-Type: application/ld+json`, `Accept: application/ld+json`
- Body :
```json
{
"user": "/api/users/5",
"startedAt": "<ISO8601 avec timezone>",
"title": "<description courte de la tâche>",
"project": "/api/projects/<projectId>",
"tags": ["/api/task_tags/<tagId>"],
"task": "/api/tasks/<taskId>"
}
```
3. **Stopper le timer** : `PATCH http://project.malio-dev.fr/api/time_entries/<id>`
- Headers : `Cookie: BEARER=<jwt>`, `Content-Type: application/merge-patch+json`, `Accept: application/ld+json`
- Body : `{"stoppedAt": "<ISO8601>"}`
### Paramètres obligatoires
- **user** : TOUJOURS `/api/users/5` (Matthieu)
- **startedAt** : ISO 8601 avec timezone (ex: `2026-04-01T14:30:00+02:00`)
- **title** : description courte de la tâche en cours
- **project** : selon le projet (voir mapping ci-dessous)
### Tags (choisir selon le type de travail)
| Tag | ID | IRI |
|-----|----|-----|
| Backend | 3 | `/api/task_tags/3` |
| Frontend | 2 | `/api/task_tags/2` |
| IA | 7 | `/api/task_tags/7` |
| Infra | 5 | `/api/task_tags/5` |
| UI/UX | 4 | `/api/task_tags/4` |
| Maintenance | 6 | `/api/task_tags/6` |
| RDV | 1 | `/api/task_tags/1` |
| Réunion | 8 | `/api/task_tags/8` |
| Formation | 10 | `/api/task_tags/10` |
| Gestion projet | 9 | `/api/task_tags/9` |
### Mapping projets
| Projet | ID | IRI |
|--------|----|-----|
| Lesstime | 5 | `/api/projects/5` |
| Inventory | 7 | `/api/projects/7` |
| SIRH | 12 | `/api/projects/12` |
| Infrastructure | 13 | `/api/projects/13` |
| Malio UI | 11 | `/api/projects/11` |
| ERP Liot | 6 | `/api/projects/6` |
| Ferme | 8 | `/api/projects/8` |
| ADMIN | 16 | `/api/projects/16` |
| Maintenance-LIOT | 17 | `/api/projects/17` |
| Qualiopi | 14 | `/api/projects/14` |
| Vaultwarden | 18 | `/api/projects/18` |
### Règles
- **Un seul timer actif à la fois** (contrainte DB) — stopper l'actif avant d'en créer un nouveau
- **Toujours stopper le timer** en fin de tâche ou sur demande
- **Informer l'utilisateur** quand un timer est lancé/stoppé (numéro, titre, projet, tags)
- **Lier au ticket Lesstime** si un ticket est référencé (champ `task`)
- **Choisir les tags intelligemment** selon le type de travail effectué

View File

@@ -61,7 +61,7 @@ ENCRYPTION_KEY=<random-hex-32>
## 4. Installer le script de deploy
```bash
sudo cp script/deploy-release.sh /usr/local/bin/deploy-lesstime
sudo cp infra/prod/deploy-release.sh /usr/local/bin/deploy-lesstime
sudo chmod +x /usr/local/bin/deploy-lesstime
```
@@ -89,7 +89,7 @@ sudo -u www-data php bin/console lexik:jwt:generate-keypair --skip-if-exists --e
## 7. Configurer Nginx
```bash
sudo cp deploy/nginx/lesstime.conf /etc/nginx/sites-available/lesstime
sudo cp infra/prod/nginx-baremetal.conf /etc/nginx/sites-available/lesstime
sudo ln -sf /etc/nginx/sites-available/lesstime /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```

View File

@@ -33,8 +33,8 @@
<MalioButton
label="Se connecter"
button-class="w-full"
type="submit"
:disabled="isSubmitting"
@click="handleSubmit"
/>
<p class="font-bold">v{{ version }}</p>
</form>

22
infra/prod/.env.example Normal file
View File

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

82
infra/prod/Dockerfile Normal file
View File

@@ -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/
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 infra/prod/supervisord.conf /etc/supervisor/conf.d/app.conf
COPY infra/prod/nginx.conf /etc/nginx/sites-enabled/lesstime.conf
COPY infra/prod/maintenance.html /var/www/html/public/maintenance.html
# 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 /var/www/html/var/mcp-sessions \
&& 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"]

38
infra/prod/deploy.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
TAG="${1:-latest}"
export LESSTIME_IMAGE_TAG="$TAG"
echo "==> Deploying lesstime:${TAG}..."
echo "==> Enabling maintenance mode..."
touch maintenance.on
echo "==> Pulling image..."
sudo docker compose pull
echo "==> Starting container..."
sudo docker compose up -d
echo "==> Waiting for container to be ready..."
sleep 3
echo "==> Extracting maintenance page..."
mkdir -p public
sudo docker compose cp app:/var/www/html/public/maintenance.html public/maintenance.html
echo "==> Running migrations..."
sudo docker compose exec -T -u www-data app php bin/console doctrine:migrations:migrate --no-interaction
echo "==> Clearing cache..."
sudo docker compose exec -T -u www-data app php bin/console cache:clear --env=prod
sudo docker compose exec -T -u www-data app php bin/console cache:warmup --env=prod
echo "==> Disabling maintenance mode..."
rm -f maintenance.on
VERSION=$(sudo docker compose exec -T app cat config/version.yaml | grep 'app.version' | awk -F"'" '{print $2}')
echo "==> Deployed v${VERSION}"

View File

@@ -0,0 +1,13 @@
services:
app:
image: gitea.malio.fr/malio-dev/lesstime:${LESSTIME_IMAGE_TAG:-latest}
container_name: lesstime-app
env_file: .env
ports:
- "8081: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

View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Maintenance en cours</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f3f4f6;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
.container {
background: white;
border-radius: 12px;
box-shadow: 0 4px 24px rgba(0,0,0,0.10);
padding: 48px 40px;
max-width: 480px;
text-align: center;
}
.icon {
font-size: 48px;
margin-bottom: 16px;
}
h1 {
color: #1f2937;
font-size: 24px;
margin: 0 0 16px;
}
p {
color: #6b7280;
font-size: 16px;
line-height: 1.6;
margin: 0;
}
</style>
</head>
<body>
<div class="container">
<div class="icon">&#128736;</div>
<h1>Maintenance en cours</h1>
<p>L'application est temporairement indisponible pour mise à jour. Elle sera de retour dans quelques instants.</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,31 @@
server {
listen 80;
listen [::]:80;
server_name project.malio-dev.fr;
root /var/www/lesstime/public;
# Maintenance mode
if (-f /var/www/lesstime/maintenance.on) {
return 503;
}
error_page 503 @maintenance;
location @maintenance {
rewrite ^(.*)$ /maintenance.html break;
}
location = /maintenance.html {
internal;
}
location / {
proxy_pass http://127.0.0.1:8081;
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;
}
}

70
infra/prod/nginx.conf Normal file
View File

@@ -0,0 +1,70 @@
server {
listen 80;
server_name _;
# Maintenance mode
if (-f /var/www/html/maintenance.on) {
return 503;
}
error_page 503 @maintenance;
location @maintenance {
root /var/www/html/public;
rewrite ^(.*)$ /maintenance.html break;
}
location = /maintenance.html {
root /var/www/html/public;
internal;
}
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;
}
}

View File

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

View File

@@ -1,6 +1,6 @@
# Permet d'utiliser un .env.docker.local pour override
ENV_DEFAULT = docker/.env.docker
ENV_LOCAL = docker/.env.docker.local
ENV_DEFAULT = infra/dev/.env.docker
ENV_LOCAL = infra/dev/.env.docker.local
ENV_FILE := $(if $(wildcard $(ENV_LOCAL)),$(ENV_LOCAL),$(ENV_DEFAULT))
# Permet d'avoir les variables du fichier .env.docker.local
@@ -23,13 +23,11 @@ FILES =
#========================================================================================
env-init:
@mkdir -p docker
@cp --update=none $(ENV_DEFAULT) $(ENV_LOCAL)
# Lance le container
start: env-init
@echo "**** START CONTAINERS ****"
@cp --update=none docker/.env.docker docker/.env.docker.local
CURRENT_UID=$(shell id -u) CURRENT_GID=$(shell id -g) $(DOCKER_COMPOSE) up -d
# Éteint le container

View File

@@ -1,96 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Usage: ./script/deploy-release.sh v0.1.0
# Requires: curl, tar, (optional) rsync
#
# Auth token: set RELEASE_TOKEN env var or create /etc/lesstime-release-token
umask 002
TAG="${1:-}"
if [ -z "$TAG" ]; then
echo "Usage: $0 v0.1.0" >&2
exit 1
fi
REPO_OWNER="MALIO-DEV"
REPO_NAME="Lesstime"
GITEA_API="https://gitea.malio.fr/api/v1"
DEPLOY_DIR="/var/www/lesstime"
if [ -f /etc/lesstime-release-token ] && [ -z "${RELEASE_TOKEN:-}" ]; then
RELEASE_TOKEN="$(cat /etc/lesstime-release-token)"
fi
tmp_dir="$(mktemp -d)"
cleanup() {
rm -rf "$tmp_dir"
}
trap cleanup EXIT
release_json="$tmp_dir/release.json"
curl_opts=(-sS)
if [ -n "${RELEASE_TOKEN:-}" ]; then
curl_opts+=(-H "Authorization: token ${RELEASE_TOKEN}")
fi
curl "${curl_opts[@]}" \
"${GITEA_API}/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/${TAG}" \
-o "$release_json"
asset_url="$(python3 - "$release_json" <<'PY'
import json, sys
data = json.load(open(sys.argv[1], 'r'))
assets = data.get("assets", [])
for a in assets:
name = a.get("name", "")
if name.startswith("lesstime-") and name.endswith(".tar.gz"):
print(a.get("browser_download_url", ""))
break
PY
)"
if [ -z "$asset_url" ]; then
echo "Release asset not found for tag ${TAG}" >&2
exit 1
fi
archive="$tmp_dir/artefact.tar.gz"
curl "${curl_opts[@]}" -L "$asset_url" -o "$archive"
tar -xzf "$archive" -C "$tmp_dir"
if command -v rsync >/dev/null 2>&1; then
rsync -a --delete --no-perms --no-owner --no-group \
--exclude ".env" \
--exclude ".env.local" \
--exclude "config/jwt" \
--exclude "var" \
"$tmp_dir"/ "$DEPLOY_DIR"/
else
cp -a "$tmp_dir"/. "$DEPLOY_DIR"/
fi
# Ensure Nginx can traverse the deploy path.
chmod o+rx "$(dirname "$DEPLOY_DIR")" "$DEPLOY_DIR" 2>/dev/null || true
# Create frontend/dist symlink if needed (nginx serves from frontend/dist)
if [ -d "${DEPLOY_DIR}/frontend/.output/public" ] && [ ! -L "${DEPLOY_DIR}/frontend/dist" ]; then
ln -sfn "${DEPLOY_DIR}/frontend/.output/public" "${DEPLOY_DIR}/frontend/dist"
fi
echo "Release ${TAG} deployed to ${DEPLOY_DIR}"
# Ensure var/log exists and is writable by PHP (www-data)
mkdir -p "${DEPLOY_DIR}/var/log"
chown www-data:www-data "${DEPLOY_DIR}/var/log"
chmod 775 "${DEPLOY_DIR}/var/log"
if [ -f "${DEPLOY_DIR}/.env.local" ]; then
echo "Clearing cache..."
php "${DEPLOY_DIR}/bin/console" cache:clear --env=prod --no-debug
echo "Running migrations (if any)..."
php "${DEPLOY_DIR}/bin/console" doctrine:migrations:migrate --no-interaction --env=prod
else
echo "Skip post-deploy: ${DEPLOY_DIR}/.env.local not found" >&2
fi