16 Commits

Author SHA1 Message Date
gitea-actions
f80578c26a chore: bump version to v0.1.11
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 20s
2026-04-03 12:09:28 +00:00
6dd3b7c701 docs : README avec explication du mode maintenance
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:09:20 +02:00
gitea-actions
fb2691251a chore: bump version to v0.1.10
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 20s
2026-04-03 12:03:35 +00:00
94115a80f6 docs : retire ferme de 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:27 +02:00
gitea-actions
99d161921e chore: bump version to v0.1.9
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 17s
2026-04-03 11:49:41 +00:00
bf9f4aaa29 fix(docker) : retire volume ferme du docker-compose prod
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:49:36 +02:00
gitea-actions
7f797e307d chore: bump version to v0.1.8
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 19s
2026-04-03 11:33:56 +00:00
89465f5cd5 fix(docker) : créer les dossiers maintenance dans l'image prod
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:33:47 +02:00
gitea-actions
0e68d9dbe7 chore: bump version to v0.1.7
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 16s
2026-04-03 11:30:05 +00:00
19ac37fb3e chore : retire Ferme des applications managées
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:29:57 +02:00
gitea-actions
7e967a1649 chore: bump version to v0.1.6
All checks were successful
Auto Tag Develop / tag (push) Successful in 4s
Build & Push Docker Image / build (push) Successful in 15s
2026-04-03 11:19:34 +00:00
f5ab0335f9 Revert "feat : commande app:create-user pour créer des utilisateurs"
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
This reverts commit ad92a4c434.
2026-04-03 13:19:28 +02:00
gitea-actions
44e1e4a293 chore: bump version to v0.1.5
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:12:34 +00:00
ad92a4c434 feat : commande app:create-user pour créer des utilisateurs
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Permet de créer un user en prod sans SQL :
  php bin/console app:create-user <username> <password> --admin

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:12:28 +02:00
gitea-actions
be12175e17 chore: bump version to v0.1.4
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 35s
2026-04-03 11:09:26 +00:00
e8fc85c173 fix : correctifs de sécurité et robustesse post-review
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
- MeProvider : guard null user avec AccessDeniedHttpException
- MaintenanceToggleProcessor : vérification des opérations filesystem
- User : restreindre Get/GetCollection aux ROLE_ADMIN
- useAppVersion : corriger le path relatif '/version'

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:09:14 +02:00
11 changed files with 123 additions and 17 deletions

1
.env
View File

@@ -48,5 +48,4 @@ DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&ch
SIRH_MAINTENANCE_PATH=/var/www/maintenance/sirh/maintenance.on
LESSTIME_MAINTENANCE_PATH=/var/www/maintenance/lesstime/maintenance.on
INVENTORY_MAINTENANCE_PATH=/var/www/maintenance/inventory/maintenance.on
FERME_MAINTENANCE_PATH=/var/www/maintenance/ferme/maintenance.on
###< malio/maintenance ###

102
README.md
View File

@@ -1,6 +1,6 @@
# Central
Application de gestion du SI Malio — gestion des applications, bases de données, mode maintenance, déploiements.
Application de supervision du SI Malio. Permet de piloter le mode maintenance de toutes les applications sans se connecter a chaque projet.
## Installation
@@ -10,9 +10,107 @@ make install
make fixtures
```
## Accès
## Acces
- Frontend : http://localhost:8084
- API : http://localhost:8084/api
- Dev Nuxt (hot reload) : http://localhost:3003
- Login : `admin` / `admin`
## Applications gerees
| Application | Slug | Port prod | Fichier maintenance |
|-------------|------|-----------|---------------------|
| SIRH | `sirh` | 8080 | `/var/www/sirh/maintenance.on` |
| Lesstime | `lesstime` | 8081 | `/var/www/lesstime/maintenance.on` |
| Inventory | `inventory` | 8082 | `/var/www/inventory/maintenance.on` |
La configuration est dans `config/applications.yaml`.
## Comment fonctionne la maintenance
### Architecture
```
Central (container Docker, port 8084)
|
| Volumes Docker :
| /var/www/sirh (hote) --> /var/www/maintenance/sirh (container)
| /var/www/lesstime (hote) --> /var/www/maintenance/lesstime (container)
| /var/www/inventory (hote) --> /var/www/maintenance/inventory (container)
|
| Quand l'admin clique "Activer maintenance" sur Lesstime :
|
v
API : POST /api/applications/lesstime/maintenance { "maintenance": true }
|
v
MaintenanceToggleProcessor
--> touch /var/www/maintenance/lesstime/maintenance.on (dans le container)
--> via le volume Docker, cree /var/www/lesstime/maintenance.on sur l'hote
|
v
Nginx de l'hote (reverse proxy de Lesstime) :
if (-f /var/www/lesstime/maintenance.on) --> return 503
--> sert /var/www/lesstime/public/maintenance.html
```
### Cote Central (ce projet)
1. `config/applications.yaml` definit les apps et leur `maintenance_path` (variable d'env)
2. Les variables d'env (`SIRH_MAINTENANCE_PATH`, etc.) pointent vers `/var/www/maintenance/{slug}/maintenance.on`
3. Le `docker-compose.yml` prod monte les dossiers de deploy des apps vers `/var/www/maintenance/`
4. `MaintenanceToggleProcessor` cree ou supprime le fichier `maintenance.on`
5. `ManagedApplicationProvider` lit `file_exists()` pour afficher l'etat actuel
### Cote application cible (SIRH, Lesstime, Inventory)
Chaque application doit avoir :
1. **Un `maintenance.html`** dans `/var/www/{app}/public/` sur le serveur (extrait automatiquement par `deploy.sh`)
2. **Un nginx reverse proxy sur l'hote** qui verifie l'existence du fichier `maintenance.on` :
```nginx
server {
server_name app.malio-dev.fr;
root /var/www/{app}/public;
if (-f /var/www/{app}/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:{port};
# headers...
}
}
```
### Ajouter une nouvelle application
1. Ajouter l'entree dans `config/applications.yaml`
2. Ajouter la variable d'env `{APP}_MAINTENANCE_PATH` dans `.env` et `.env` prod
3. Ajouter le volume dans `docker-compose.yml` prod : `/var/www/{app}:/var/www/maintenance/{app}`
4. Configurer le nginx reverse proxy de l'hote pour la nouvelle app (voir ci-dessus)
5. S'assurer que `maintenance.html` existe dans `/var/www/{app}/public/`
## Stack
- **Backend** : PHP 8.4, Symfony 8.0, API Platform 4
- **Frontend** : Nuxt 4 (SPA), Vue 3, Tailwind CSS
- **Auth** : JWT HTTP-only cookie
- **Docker** : PHP-FPM + Nginx + Supervisor
## Deploiement
Voir `doc/deployment-docker.md` pour le guide complet.
```bash
cd /var/www/central
./deploy.sh # latest
./deploy.sh v0.1.5 # version specifique
```

View File

@@ -3,4 +3,3 @@ parameters:
- { name: 'SIRH', slug: 'sirh', maintenance_path: '%env(SIRH_MAINTENANCE_PATH)%' }
- { name: 'Lesstime', slug: 'lesstime', maintenance_path: '%env(LESSTIME_MAINTENANCE_PATH)%' }
- { name: 'Inventory', slug: 'inventory', maintenance_path: '%env(INVENTORY_MAINTENANCE_PATH)%' }
- { name: 'Ferme', slug: 'ferme', maintenance_path: '%env(FERME_MAINTENANCE_PATH)%' }

View File

@@ -1,2 +1,2 @@
parameters:
app.version: '0.1.3'
app.version: '0.1.11'

View File

@@ -88,7 +88,6 @@ services:
- /var/www/sirh:/var/www/maintenance/sirh
- /var/www/lesstime:/var/www/maintenance/lesstime
- /var/www/inventory:/var/www/maintenance/inventory
- /var/www/ferme:/var/www/maintenance/ferme
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
@@ -129,7 +128,6 @@ ENCRYPTION_KEY=<generer avec: openssl rand -hex 32>
SIRH_MAINTENANCE_PATH=/var/www/maintenance/sirh/maintenance.on
LESSTIME_MAINTENANCE_PATH=/var/www/maintenance/lesstime/maintenance.on
INVENTORY_MAINTENANCE_PATH=/var/www/maintenance/inventory/maintenance.on
FERME_MAINTENANCE_PATH=/var/www/maintenance/ferme/maintenance.on
```
### 5. Generer les cles JWT
@@ -155,7 +153,7 @@ Central pilote les fichiers `maintenance.on` des autres projets via des volumes
Verifier que les dossiers existent :
```bash
ls -ld /var/www/sirh /var/www/lesstime /var/www/inventory /var/www/ferme
ls -ld /var/www/sirh /var/www/lesstime /var/www/inventory
```
Si Central ne peut pas ecrire `maintenance.on`, il faudra ajuster les permissions sur ces dossiers pour que le processus du conteneur puisse creer/supprimer ce fichier.

View File

@@ -6,7 +6,7 @@ export function useAppVersion() {
if (version.value) {
return version.value
}
const response = await api.get<{ version: string }>('version', {}, {
const response = await api.get<{ version: string }>('/version', {}, {
toast: false
})
version.value = response.version

View File

@@ -73,7 +73,8 @@ 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
/var/www/maintenance/sirh /var/www/maintenance/lesstime /var/www/maintenance/inventory \
&& chown -R www-data:www-data /var/www/html/var /var/www/maintenance
WORKDIR /var/www/html
EXPOSE 80

View File

@@ -11,7 +11,6 @@ services:
- /var/www/sirh:/var/www/maintenance/sirh
- /var/www/lesstime:/var/www/maintenance/lesstime
- /var/www/inventory:/var/www/maintenance/inventory
- /var/www/ferme:/var/www/maintenance/ferme
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped

View File

@@ -28,9 +28,11 @@ use Symfony\Component\Serializer\Attribute\Groups;
normalizationContext: ['groups' => ['me:read']],
),
new Get(
security: "is_granted('ROLE_ADMIN')",
normalizationContext: ['groups' => ['user:list']],
),
new GetCollection(
security: "is_granted('ROLE_ADMIN')",
normalizationContext: ['groups' => ['user:list']],
),
new Post(security: "is_granted('ROLE_ADMIN')", processor: UserPasswordHasherProcessor::class),

View File

@@ -44,13 +44,17 @@ final readonly class MaintenanceToggleProcessor implements ProcessorInterface
if ($data->maintenance) {
$directory = dirname($maintenancePath);
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
if (!is_dir($directory) && !mkdir($directory, 0755, true)) {
throw new \RuntimeException(sprintf('Cannot create directory "%s".', $directory));
}
touch($maintenancePath);
if (!touch($maintenancePath)) {
throw new \RuntimeException(sprintf('Cannot create maintenance file at "%s".', $maintenancePath));
}
} elseif (file_exists($maintenancePath)) {
unlink($maintenancePath);
if (!unlink($maintenancePath)) {
throw new \RuntimeException(sprintf('Cannot remove maintenance file at "%s".', $maintenancePath));
}
}
$dto = new ManagedApplication();

View File

@@ -8,6 +8,7 @@ use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Entity\User;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* @implements ProviderInterface<User>
@@ -20,7 +21,12 @@ final readonly class MeProvider implements ProviderInterface
public function provide(Operation $operation, array $uriVariables = [], array $context = []): User
{
// @var User $user
return $this->security->getUser();
$user = $this->security->getUser();
if (!$user instanceof User) {
throw new AccessDeniedHttpException('User not authenticated.');
}
return $user;
}
}