206 lines
14 KiB
Markdown
206 lines
14 KiB
Markdown
# Runbook — VPN NAS↔VPS + pont SAV + `api.navier-instruments.com`
|
||
|
||
> Objectif : exposer **un seul** service public (`api.navier-instruments.com`) qui relaie vers
|
||
> le **pont** (interne, sur le NAS), lequel parle à la gestion (Dolibarr) + ThingsBoard.
|
||
> **La gestion n'est JAMAIS exposée.** Documenté pour être reproductible — ne rien casser.
|
||
|
||
## Architecture cible
|
||
```
|
||
public ──HTTPS──▶ api.navier-instruments.com
|
||
(VPS 78.138.45.8, Nginx Proxy Manager + SSL)
|
||
│
|
||
▼ VPN OpenVPN (le VPS est client du serveur OpenVPN du NAS)
|
||
pont @navier/sav-pont (NAS 10.8.0.1:8080)
|
||
│
|
||
┌─────────┴─────────┐
|
||
▼ ▼
|
||
Dolibarr (interne) ThingsBoard (navier-cloud.com)
|
||
```
|
||
|
||
## Inventaire (état 2026-07-13)
|
||
| Élément | Détail |
|
||
|---|---|
|
||
| **NAS** | Synology `N_instruments`, LAN `192.168.10.104`, **DSM 6.2.4**. SSH : `ssh nas` (SteveC, port **1981**, clé `id_ed25519_nas`). |
|
||
| **NAS = serveur OpenVPN** | `server 10.8.0.0/24`, **proto `udp6`**, **port 1194**, `dev tun`. NAS = `10.8.0.1`. IP publique actuelle = **IPv6** `2a05:6e02:106c:fc10:...`. |
|
||
| **Pont** | `@navier/sav-pont` (Node ≥20, TS). Source **propre** = repo `Ecosystem-Navier/SAV/pont` (Dockerfile, écoute **:8080**, métriques :9100). ⚠️ Le dossier `/volume1/docker/sav/` du NAS est un **dump de build cassé** (noms mélangés) → NE PAS l'utiliser. |
|
||
| **Gestion** | Dolibarr en Docker sur le NAS (`/volume1/docker/dolibarr/`, image `tuxgasy/dolibarr` + mariadb). Interne uniquement. |
|
||
| **VPS** | `78.138.45.8`, `ssh navier-vps` (user `deploy`). NPM (conteneur) fait le reverse-proxy + SSL. |
|
||
|
||
## Choix du VPN — recommandation : WireGuard (VPS serveur / NAS client)
|
||
Contrainte : le serveur OpenVPN du NAS est en **`udp6`** + IP publique **IPv6** → dépendance IPv6 ou
|
||
port‑forward box. On l'évite en **inversant le sens** : le **VPS** (IPv4 publique stable) = **serveur**,
|
||
le **NAS** = **client qui compose** (dial‑out) → **aucun port à ouvrir sur la box**, pas d'IPv6.
|
||
|
||
**Reco : WireGuard** (moderne, léger, self‑hosted/souverain, reconnexion propre). Sur DSM 6.2.4 = **conteneur Docker**.
|
||
Sous‑réseau VPN choisi : **`10.9.0.0/24`** (VPS = `10.9.0.1`, NAS = `10.9.0.2`).
|
||
- *Alternative simplicité :* Tailscale (WireGuard clé‑en‑main, gère NAT/IPv6/clés) — service tiers.
|
||
- *Alternative « réutiliser l'existant » :* OpenVPN NAS‑serveur (voir annexe) — plus contraignant.
|
||
|
||
## Étape 1 — VPN WireGuard (VPS serveur, NAS client)
|
||
1. **VPS (root)** — serveur :
|
||
```bash
|
||
sudo apt-get update && sudo apt-get install -y wireguard
|
||
umask 077; wg genkey | tee /etc/wireguard/vps.key | wg pubkey > /etc/wireguard/vps.pub
|
||
# /etc/wireguard/wg0.conf :
|
||
# [Interface] Address=10.9.0.1/24 ListenPort=51820 PrivateKey=<contenu vps.key>
|
||
# [Peer] PublicKey=<nas.pub> AllowedIPs=10.9.0.2/32
|
||
sudo systemctl enable --now wg-quick@wg0
|
||
sudo ufw allow 51820/udp # (ou règle firewall équivalente)
|
||
```
|
||
2. **NAS (Docker, root)** — client (conteneur `linuxserver/wireguard`) :
|
||
```bash
|
||
mkdir -p /volume1/docker/wireguard
|
||
# /volume1/docker/wireguard/wg_confs/wg0.conf :
|
||
# [Interface] Address=10.9.0.2/32 PrivateKey=<contenu nas.key>
|
||
# [Peer] PublicKey=<vps.pub> Endpoint=<IP_PUBLIQUE_VPS>:51820 AllowedIPs=10.9.0.1/32 PersistentKeepalive=25
|
||
docker run -d --name wireguard --cap-add NET_ADMIN --restart unless-stopped \
|
||
-v /volume1/docker/wireguard:/config linuxserver/wireguard
|
||
```
|
||
3. **Vérifier** : `ssh navier-vps 'ping -c2 10.9.0.2'` (le VPS doit joindre le NAS). Le pont sera en **`10.9.0.2:8080`**.
|
||
*(Générer les clés : `wg genkey|tee nas.key|wg pubkey>nas.pub` côté NAS ; échanger les .pub entre les deux.)*
|
||
|
||
## Étape 2 — Déployer le pont (sur le NAS, proprement)
|
||
Depuis la **source du repo** (pas le dossier cassé). Le pont a un `Dockerfile` (écoute :8080).
|
||
```bash
|
||
# sur le NAS (root — Docker DSM 6)
|
||
mkdir -p /volume1/docker/sav-pont && cd /volume1/docker/sav-pont
|
||
# récupérer Ecosystem-Navier/SAV/pont (git clone ou rsync du sous-dossier)
|
||
docker build -t navier-sav-pont .
|
||
# pont.env = VRAIE config (URL Dolibarr interne, ThingsBoard, secrets) — à recréer proprement
|
||
docker run -d --name sav-pont --restart unless-stopped \
|
||
-p 8080:8080 --env-file /volume1/docker/sav-pont/pont.env navier-sav-pont
|
||
curl -s http://localhost:8080/health # doit répondre 200
|
||
```
|
||
|
||
## Étape 3 — Exposer `api.navier-instruments.com` (VPS)
|
||
|
||
> ⚠️ **PÉRIMÉ — ne pas appliquer tel quel.** Cette étape date du plan WireGuard, **abandonné**
|
||
> (voir « ✅ RÉALISÉ » plus bas : la Freebox Pro filtre l'UDP sortant → VPN **IKEv2** retenu).
|
||
> L'adresse du pont n'est **pas** `10.8.0.1` mais **`192.168.10.104:8080`**.
|
||
> Rappel du piège réseau : un conteneur en **bridge** est SNAT hors du tunnel → il faut
|
||
> **`--network host`** (c'est ce que fait `navier-espace-web`).
|
||
> État au 2026-07-20 : `api.navier-instruments.com` **n'a aucun enregistrement DNS** — cette
|
||
> étape n'a jamais été exécutée. Le pont est joint en interne, pas via un domaine public.
|
||
|
||
1. **DNS (LWS)** : `api.navier-instruments.com A 78.138.45.8` (+ `AAAA` si IPv6 VPS).
|
||
2. **NPM (Nginx Proxy Manager)** : Proxy Host `api.navier-instruments.com` → ~~`10.8.0.1:8080`~~
|
||
→ **`192.168.10.104:8080`** + **SSL Let's Encrypt** (Force SSL, HTTP/2).
|
||
3. Vérifier : `curl -I https://api.navier-instruments.com/health` → **200**.
|
||
|
||
## Étape 4 — Brancher les consommateurs
|
||
- Espace client, PWA SAV atelier, webapp → base API = **`https://api.navier-instruments.com`** (le seul point d'entrée).
|
||
|
||
## Résilience / ne rien casser
|
||
- VPN VPS : `openvpn-client@nas` en `enable` → reconnexion auto. Si NAS/box down → `api.` down → les consommateurs doivent **dégrader proprement** (cache / message « temporairement indisponible »).
|
||
- **Dolibarr jamais exposé** — accessible seulement via le pont, **scopé par client**.
|
||
- Ne pas exposer le port 9100 (métriques) publiquement.
|
||
- Sauvegardes : base mariadb Dolibarr (sur le NAS) — vérifier la tâche de sauvegarde Synology.
|
||
|
||
## Vérifs de bout en bout
|
||
```bash
|
||
ssh navier-vps 'ping -c2 10.8.0.1 && curl -s http://10.8.0.1:8080/health'
|
||
curl -I https://api.navier-instruments.com/health
|
||
```
|
||
|
||
---
|
||
## ✅ RÉALISÉ (2026-07-13) — VPN opérationnel = Freebox IKEv2 (pas WireGuard)
|
||
WireGuard abandonné pour NAS↔VPS : le réseau du NAS (Freebox Pro) **filtre l'UDP sortant** (seul dport 53 passe, prouvé par tcpdump) → WireGuard (UDP) impossible depuis le NAS. **Retenu : serveur VPN IKEv2 de la Freebox Pro.**
|
||
|
||
Montage : `VPS (client strongSwan) → 160189667.box.freepro.com (Freebox, VPN IKEv2) → LAN → NAS 192.168.10.104`.
|
||
|
||
- **VPS** `/etc/ipsec.conf` conn `freebox` : `keyexchange=ikev2`, `right=160189667.box.freepro.com`, `rightsubnet=192.168.10.0/24` (route LAN seule — le VPS garde son net), `leftauth=eap-mschapv2`, `leftid`/`eap_identity=VPSNavier`, `leftsourceip=%config`, `auto=start`. Secrets : `/etc/ipsec.secrets` → `VPSNavier : EAP "<mdp>"`.
|
||
- **Fix cert indispensable** : copier `ISRG_Root_X1.pem` (+X2) **individuellement** dans `/etc/ipsec.d/cacerts/` (strongSwan ne lit qu'1 cert/fichier ; le bundle `ca-certificates.crt` ne suffit pas).
|
||
- **Persistance** : `systemctl enable strongswan-starter`. Reconnexion : MOBIKE.
|
||
- **Vérif OK** : `ping 192.168.10.104` + `curl http://192.168.10.104:4444` → 200.
|
||
- **Mobile** : même serveur Freebox IKEv2 pour iPhone/Mac nativement (Réglages → VPN → IKEv2).
|
||
|
||
> **Accès NAS depuis n'importe quel réseau** : quand on n'est PAS sur le LAN interne, le NAS
|
||
> reste joignable via le VPS : `ssh -J navier-vps nas` (ProxyJump — l'auth SSH reste bout‑à‑bout
|
||
> avec la clé NAS, seul le TCP transite par le VPS+tunnel). Idem `scp`/`tar | ssh -J navier-vps nas`.
|
||
|
||
---
|
||
## ✅ RÉALISÉ (2026-07-13) — Pont SAV sur module INTERVENTION (fichinter), pas Ticket
|
||
|
||
**DÉCISION ACTÉE (Steve + Tiago) : le SAV s'écrit en FICHE D'INTERVENTION Dolibarr (module
|
||
`Intervention`, endpoints `/interventions`, table `llx_fichinter`), PAS en Ticket.**
|
||
Raison : le module Ticket renvoie **403** sur l'instance de prod (`GET /tickets` → 403), alors que
|
||
`GET /interventions` → **200**. Validé en direct sur Dolibarr **19**. Cf. commit
|
||
`steve-dev #14 « bascule Tickets → fiches d'intervention »`, convergé dans la branche `sav-integration`.
|
||
|
||
**Source du pont = branche `sav-integration`** (repo `Ecosystem-Navier`, la plus à jour : pont
|
||
modulaire *Port + Adapters* config‑driven ; `HttpDolibarrClient` écrit en `/interventions`,
|
||
`/thirdparties`, `/stockmovements`). L'ancien build (`main`) tapait `/tickets` → remplacé.
|
||
|
||
### Déploiement du pont (NAS, `/volume1/docker/sav-pont`)
|
||
- Image `navier/sav-pont:interv` buildée sur le NAS (`sudo docker build`, node 22 alpine, tsc → dist).
|
||
Dossier possédé par SteveC (`chown -R`) pour édition sans sudo ; **docker exige `sudo`** sur DSM 6.
|
||
- Conteneur : `sudo docker run -d --name sav-pont --restart unless-stopped -p 8080:8080
|
||
--env-file .env -v /volume1/docker/sav-pont-data:/data navier/sav-pont:interv`.
|
||
⚠️ **`docker restart` NE recharge PAS `--env-file`** → toujours **`rm` + `run`** après un changement `.env`.
|
||
- **Volume data** : le pont écrit un magasin de comptes/sessions dans `/data` → le volume
|
||
`/volume1/docker/sav-pont-data` doit appartenir à **uid 1000** (`node`) : `sudo chown -R 1000:1000`.
|
||
Sinon boucle de crash `EACCES … accounts.tmp`.
|
||
- `.env` (secrets jamais affichés) : adapters auto — `DOLIBARR_BASE_URL` + `DOLIBARR_API_KEY`
|
||
remplis → **HttpDolibarrClient (fichinter)** ; `TB_BASE_URL` → HttpThingsBoard. Ajoutés vs l'ancien :
|
||
`SESSION_SECRET` (HMAC sessions LAN, sinon sessions perdues à chaque redémarrage), `MEDIA_DIR=/data/media`,
|
||
`SEED_SUPERVISOR_NAME=Steve` / `SEED_SUPERVISOR_PIN=1234` (**superviseur initial — CHANGER le PIN**).
|
||
- Santé : `GET /healthz` → `{"status":"ok","deps":{"dolibarr":"http","thingsboard":"http","media":"local"}}`.
|
||
|
||
### Prérequis Dolibarr (fichinter) — appliqués et validés
|
||
Conforme au schéma **officiel** (wiki Extrafields / Table llx_extrafields) — pas de bricolage :
|
||
- **Module Intervention activé** (`GET /interventions` → 200). Compte technique `cpont-sav` (clé `DOLAPIKEY`).
|
||
- **2 extrafields créés** sur l'élément `fichinter` (le pont les écrit en `array_options`) :
|
||
`numero_serie` (varchar 128) + `sav_statut` (varchar 64). Créés par : ligne dans `llx_extrafields`
|
||
(`elementtype='fichinter'`) **+** colonne ajoutée dans `llx_fichinter_extrafields`. Backup préalable :
|
||
`/volume1/docker/dolibarr-extrafields.backup.sql`. Requête `sqlfilters=(ef.numero_serie:=:'…')` → 200.
|
||
- **Fix pont `createThirdparty`** : ajout de **`code_client: '-1'`** (numérotation auto). Sans ça, l'API
|
||
Dolibarr (addon `mod_codeclient_monkey`) refuse la création de tiers avec `ErrorCustomerCodeRequired`.
|
||
`-1` = *« automatic assignment »* **documenté dans la classe `Societe`** du cœur Dolibarr. Poussé sur
|
||
`sav-integration` (commit `fix(sav/pont): createThirdparty envoie code_client:'-1'`).
|
||
- **Aller‑retour réel validé** : tiers (code auto) + fiche d'intervention avec `numero_serie`/`sav_statut`
|
||
créés puis supprimés (POST/GET/DELETE → 200) sur Dolibarr 19.
|
||
|
||
### Chaîne complète prouvée
|
||
`Mac (ssh -J) → VPS → tunnel IKEv2 → NAS pont :8080 (/interventions fichinter) → Dolibarr 19 réel`.
|
||
Le pont **n'est pas exposé publiquement** : l'espace client (VPS) le relaie en interne via le tunnel.
|
||
|
||
---
|
||
## ✅ RÉALISÉ (2026-07-13) — Espace client déployé sur le VPS + pont `/api` (host-network)
|
||
|
||
**Conteneur `navier-espace-web`** (nginx:alpine) sur le VPS, sert le front statique de l'espace
|
||
client (`/home/deploy/navier-espace-web`, rsync depuis `~/esp/navier-espace-client`) sur **:8085**
|
||
et **relaie `/api/` → le pont** `http://192.168.10.104:8080/` (conf `/home/deploy/espace.nginx.conf`).
|
||
|
||
### ⚠️ Point réseau clé : atteindre le pont (tunnel IKEv2) depuis un conteneur
|
||
La politique IPsec ne capture QUE le trafic sourcé de l'IP VPN du VPS `192.168.2.1`
|
||
(`ip xfrm policy` : `src 192.168.2.1/32 dst 192.168.10.0/24`). Un conteneur **bridge** est SNAT
|
||
vers l'IP hôte → **hors tunnel → `curl pont` = 000**. Deux solutions :
|
||
- **RETENUE (sans firewall)** : lancer le conteneur en **`--network host`** → il source depuis
|
||
l'hôte (`192.168.2.1`) → le tunnel le transporte. C'est pourquoi `navier-espace-web` tourne en
|
||
`--network host` (écoute :8085, proxy_pass direct vers le pont). Prouvé : `/api/healthz` → 200.
|
||
- **Alternative (si un conteneur *bridge* doit joindre le pont, ex. NPM en direct)** : règle SNAT
|
||
`iptables -t nat -A POSTROUTING -s 172.16.0.0/12 -d 192.168.10.0/24 -j SNAT --to-source 192.168.2.1`
|
||
(+ persistance). **Non appliquée** (changement firmware du VPS partagé = à valider hors auto-mode).
|
||
|
||
### Commandes
|
||
```bash
|
||
# déploiement statique
|
||
rsync -az --delete ~/esp/navier-espace-client/ navier-vps:/home/deploy/navier-espace-web/
|
||
# conteneur (host-network pour joindre le pont via le tunnel)
|
||
sudo docker run -d --name navier-espace-web --restart unless-stopped --network host \
|
||
-v /home/deploy/espace.nginx.conf:/etc/nginx/conf.d/default.conf:ro \
|
||
-v /home/deploy/navier-espace-web:/usr/share/nginx/html:ro nginx:alpine
|
||
# vérif
|
||
curl http://127.0.0.1:8085/ # 200 (statique)
|
||
curl http://127.0.0.1:8085/api/healthz # 200 (pont via tunnel)
|
||
```
|
||
|
||
### RESTE (2 actions distinctes)
|
||
1. **URL publique de revue** : `:8085` est fermé au public (seul NPM 80/443 est ouvert). Pour une
|
||
URL SSL → **ajouter DNS `espace.navier-instruments.com A → 78.138.45.8` (LWS)**, puis NPM proxy
|
||
host `espace.…` → `78.138.45.8:8085` + Let's Encrypt (même schéma que `staging.` = `set $server 78.138.45.8`).
|
||
2. **Câblage données réelles** : le front est encore un **prototype** (login factice + `var DATA` en
|
||
dur). À implémenter : login → JWT ThingsBoard (le pont le valide), puis lecture parc/SAV/factures
|
||
via le pont (`/interventions` fichinter). ⚠️ Le pont est scopé SAV-atelier (RBAC techniciens/superviseurs) :
|
||
vérifier/ajouter les routes de lecture **client** avant de câbler (ne pas deviner l'API).
|