Skip to content

Commit 1a32937

Browse files
authored
Merge pull request #10 from RustyRory/feat/9-cicd
feat(mvp): fixes #9 - lint test deploy auto
2 parents 816695c + ef13c10 commit 1a32937

9 files changed

Lines changed: 6029 additions & 857 deletions

File tree

.github/workflows/ci.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- '**'
7+
pull_request:
8+
9+
jobs:
10+
ci:
11+
name: Lint & Test
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Setup Node.js
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: 20
21+
cache: npm
22+
cache-dependency-path: vps-monitor-app/package-lock.json
23+
24+
- name: Install dependencies
25+
run: npm ci
26+
working-directory: vps-monitor-app
27+
28+
- name: Lint
29+
run: npm run lint
30+
working-directory: vps-monitor-app
31+
32+
- name: Test
33+
run: npm test
34+
working-directory: vps-monitor-app

.github/workflows/staging.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Deploy staging
2+
3+
on:
4+
push:
5+
branches:
6+
- staging
7+
8+
jobs:
9+
deploy:
10+
name: Deploy to VPS
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: SSH deploy
15+
uses: appleboy/ssh-action@v1
16+
with:
17+
host: ${{ secrets.VPS_HOST }}
18+
username: ${{ secrets.VPS_USER }}
19+
key: ${{ secrets.VPS_SSH_KEY }}
20+
script: |
21+
cd /var/www/VPS-monitor
22+
git pull origin staging
23+
cd /var/www
24+
docker-compose build vps-monitor
25+
docker-compose up -d vps-monitor

docs/workflow.md

Lines changed: 157 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,161 @@ vps-monitor:
7070
7171
Lancer : `docker-compose up -d vps-monitor`
7272

73-
### Étape 6 — CI CD
73+
### Étape 6 — CI/CD : lint, tests et déploiement automatique sur staging
7474

75+
#### Objectif
7576

77+
À chaque push sur la branche `staging`, GitHub Actions doit :
78+
1. Vérifier la qualité du code (lint)
79+
2. Lancer les tests
80+
3. Déployer automatiquement sur le VPS via SSH
81+
82+
#### 6.1 — Mettre en place ESLint
83+
84+
Installer ESLint dans `vps-monitor-app/` :
85+
86+
```bash
87+
npm install --save-dev eslint @eslint/js
88+
```
89+
90+
Créer `vps-monitor-app/eslint.config.js` :
91+
92+
```js
93+
import js from '@eslint/js';
94+
95+
export default [
96+
js.configs.recommended,
97+
{
98+
rules: {
99+
'no-unused-vars': 'warn',
100+
'no-console': 'off',
101+
},
102+
},
103+
];
104+
```
105+
106+
Ajouter le script dans `package.json` :
107+
108+
```json
109+
"lint": "eslint api/ public/"
110+
```
111+
112+
#### 6.2 — Mettre en place les tests
113+
114+
Installer les dépendances de test :
115+
116+
```bash
117+
npm install --save-dev jest supertest
118+
```
119+
120+
Créer `vps-monitor-app/api/server.test.js` :
121+
122+
```js
123+
const request = require('supertest');
124+
const app = require('./server');
125+
126+
describe('GET /api/status', () => {
127+
it('répond 200 avec la structure attendue', async () => {
128+
const res = await request(app).get('/api/status');
129+
expect(res.statusCode).toBe(200);
130+
expect(res.body).toHaveProperty('containers');
131+
expect(res.body).toHaveProperty('websites');
132+
expect(res.body).toHaveProperty('globalStatus');
133+
});
134+
});
135+
```
136+
137+
> Pour que `supertest` fonctionne, `server.js` doit exporter `app` sans appeler `listen` directement — séparer l'export de app et le démarrage du serveur.
138+
139+
Ajouter le script dans `package.json` :
140+
141+
```json
142+
"test": "jest"
143+
```
144+
145+
#### 6.3 — Créer le workflow GitHub Actions
146+
147+
Créer `.github/workflows/staging.yml` à la racine du repo :
148+
149+
```yaml
150+
name: CI + Deploy staging
151+
152+
on:
153+
push:
154+
branches:
155+
- staging
156+
157+
jobs:
158+
ci:
159+
name: Lint & Test
160+
runs-on: ubuntu-latest
161+
162+
steps:
163+
- uses: actions/checkout@v4
164+
165+
- name: Setup Node.js
166+
uses: actions/setup-node@v4
167+
with:
168+
node-version: 20
169+
cache: npm
170+
cache-dependency-path: vps-monitor-app/package-lock.json
171+
172+
- name: Install dependencies
173+
run: npm ci
174+
working-directory: vps-monitor-app
175+
176+
- name: Lint
177+
run: npm run lint
178+
working-directory: vps-monitor-app
179+
180+
- name: Test
181+
run: npm test
182+
working-directory: vps-monitor-app
183+
184+
deploy:
185+
name: Deploy to VPS
186+
runs-on: ubuntu-latest
187+
needs: ci
188+
189+
steps:
190+
- name: SSH deploy
191+
uses: appleboy/ssh-action@v1
192+
with:
193+
host: ${{ secrets.VPS_HOST }}
194+
username: ${{ secrets.VPS_USER }}
195+
key: ${{ secrets.VPS_SSH_KEY }}
196+
script: |
197+
cd /var/www/VPS-monitor
198+
git pull origin staging
199+
cd /var/www
200+
docker-compose build vps-monitor
201+
docker-compose up -d vps-monitor
202+
```
203+
204+
#### 6.4 — Configurer les secrets GitHub
205+
206+
Dans le repo GitHub → Settings → Secrets and variables → Actions, ajouter :
207+
208+
| Secret | Valeur |
209+
|---|---|
210+
| `VPS_HOST` | IP ou domaine du VPS |
211+
| `VPS_USER` | `rusty` |
212+
| `VPS_SSH_KEY` | Clé SSH privée (contenu de `~/.ssh/id_rsa`) |
213+
214+
#### 6.5 — Créer la branche staging
215+
216+
```bash
217+
git checkout -b staging
218+
git push origin staging
219+
```
220+
221+
Tout push sur `staging` déclenchera le pipeline. La branche `main` reste la branche stable.
76222

77223
---
78224

79225
## Phase 2 — Monitoring HTTP des applications web
80226

81-
### Étape 6 — Créer le service de vérification HTTP
227+
### Étape 7 — Créer le service de vérification HTTP
82228

83229
- Créer `api/services/http.js` : liste des URLs à vérifier, fonction qui effectue un GET sur chacune avec timeout, retourne nom + URL + code HTTP + statut (OK / DOWN)
84230

@@ -92,13 +238,13 @@ URLs à monitorer :
92238
| College La Boussole | `/collegelaboussole/` |
93239
| Cinemap | `/cinemap/` |
94240

95-
### Étape 7 — Étendre l'endpoint `/api/status`
241+
### Étape 8 — Étendre l'endpoint `/api/status`
96242

97243
- Appeler `http.js` en parallèle avec `docker.js`
98244
- Ajouter le champ `websites` dans la réponse JSON
99245
- Calculer `globalStatus` : `"OK"` si tout est up, `"DEGRADED"` ou `"KO"` sinon
100246

101-
### Étape 8 — Mettre à jour le frontend
247+
### Étape 9 — Mettre à jour le frontend
102248

103249
- Ajouter une section "Applications web" dans `index.html`
104250
- Mettre à jour `app.js` pour afficher les statuts HTTP avec indicateurs visuels (vert/rouge)
@@ -108,20 +254,20 @@ URLs à monitorer :
108254

109255
## Phase 3 — Actions sur les containers + Sécurité
110256

111-
### Étape 9 — Ajouter les actions Docker
257+
### Étape 10 — Ajouter les actions Docker
112258

113259
- Ajouter `POST /api/container/restart` dans `server.js` : reçoit `{ "name": "container-name" }`, appelle dockerode pour redémarrer le container
114260
- Ajouter (optionnel) `POST /api/container/stop` et `POST /api/container/start`
115261
- Tester chaque action manuellement
116262

117-
### Étape 10 — Ajouter l'authentification
263+
### Étape 11 — Ajouter l'authentification
118264

119265
- Choisir une méthode simple : variable d'environnement pour login/password, middleware Express qui vérifie un header ou cookie de session
120266
- Protéger toutes les routes `/api/*` et la page principale
121267
- Stocker les credentials dans une variable d'environnement (ne pas hardcoder)
122268
- Mettre à jour le `docker-compose.yml` pour passer les variables d'env
123269

124-
### Étape 11 — Mettre à jour le frontend pour les actions
270+
### Étape 12 — Mettre à jour le frontend pour les actions
125271

126272
- Ajouter un bouton "Restart" sur chaque carte container
127273
- Envoyer le POST correspondant au clic, rafraîchir le statut après réponse
@@ -131,7 +277,7 @@ URLs à monitorer :
131277

132278
## Phase 4 — Intégration Nginx + Évolutions
133279

134-
### Étape 12 — Remplacer la homepage Nginx
280+
### Étape 13 — Remplacer la homepage Nginx
135281

136282
Modifier la configuration Nginx existante pour rediriger `/` vers le container :
137283

@@ -146,30 +292,20 @@ location / {
146292

147293
Recharger Nginx : `nginx -t && nginx -s reload`
148294

149-
### Étape 13 — Tests de non-régression
295+
### Étape 14 — Tests de non-régression
150296

151297
- Vérifier que toutes les applications existantes sont toujours accessibles via leurs routes (`/saintbarthvolley/`, etc.)
152298
- Vérifier que la homepage affiche bien le dashboard
153299
- Simuler un container arrêté et vérifier l'affichage (statut rouge)
154300
- Simuler un service HTTP down et vérifier l'affichage
155301

156-
### Étape 14 — Évolutions futures (optionnel)
302+
### Étape 15 — Évolutions futures (optionnel)
157303

158304
À planifier selon les besoins :
159305

160-
- Alertes Discord / email quand un service tombe
306+
- Alertes email quand un service tombe
161307
- Historique d'uptime (stockage en fichier ou SQLite)
162308
- Graphiques CPU / RAM via `dockerode.stats`
163309
- Logs en temps réel via WebSocket
164310
- Support multi-serveurs
165311

166-
---
167-
168-
## Récapitulatif des livrables par phase
169-
170-
| Phase | Livrable |
171-
|---|---|
172-
| 1 | Backend Node.js + Frontend HTML + Dockerfile + intégration docker-compose |
173-
| 2 | Service HTTP checks + endpoint `/api/status` complet + UI mise à jour |
174-
| 3 | Actions restart/stop/start + authentification login/password |
175-
| 4 | Config Nginx + tests + documentation finale |

vps-monitor-app/api/server.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ app.get('/api/status', async (req, res) => {
2424
}
2525
});
2626

27-
app.listen(PORT, () => {
28-
console.log(`vps-monitor listening on port ${URI}:${PORT}`);
29-
});
27+
module.exports = app;
28+
29+
if (require.main === module) {
30+
app.listen(PORT, () => {
31+
console.log(`vps-monitor listening on port ${URI}:${PORT}`);
32+
});
33+
}

vps-monitor-app/api/server.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const request = require('supertest');
2+
3+
jest.mock('./services/docker', () => ({
4+
getContainers: jest.fn().mockResolvedValue([
5+
{ name: 'app1', status: 'running', image: 'img', ports: ['3000'], uptime: 'Up 1 hour' },
6+
]),
7+
}));
8+
9+
const app = require('./server');
10+
11+
describe('GET /api/status', () => {
12+
it('répond 200 avec la structure attendue', async () => {
13+
const res = await request(app).get('/api/status');
14+
expect(res.statusCode).toBe(200);
15+
expect(res.body).toHaveProperty('containers');
16+
expect(res.body).toHaveProperty('websites');
17+
expect(res.body).toHaveProperty('globalStatus');
18+
});
19+
20+
it('globalStatus est OK si tous les containers tournent', async () => {
21+
const res = await request(app).get('/api/status');
22+
expect(res.body.globalStatus).toBe('OK');
23+
});
24+
});

vps-monitor-app/api/services/docker.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ async function getContainers() {
66
const containers = await docker.listContainers({ all: true });
77

88
return containers.map((c) => {
9-
const uptimeSeconds = c.Status.match(/Up (\d+)/)?.[1] ?? null;
10-
119
return {
1210
name: c.Names[0].replace(/^\//, ''),
1311
status: c.State,

0 commit comments

Comments
 (0)