Skip to content

Deploiement Scell.io ​

Version: 1.0 Date: 2026-03-03


Table des matieres ​

  1. Infrastructure
  2. Prerequis
  3. Deploiement Backend (api.scell.io)
  4. Deploiement Frontend (scell.io)
  5. Variables d'Environnement
  6. Monitoring
  7. Troubleshooting

1. Infrastructure ​

Architecture de deploiement ​

mermaid
graph TB
    subgraph "Internet"
        U["Utilisateurs"]
        AI["Agents IA (MCP)"]
        SPDP["SuperPDP"]
        OA["OpenAPI.com"]
    end

    subgraph "Ploi.io (Scaleway)"
        subgraph "scell.io"
            FE["Frontend React<br/>Static (Vite build)<br/>dist/"]
        end

        subgraph "api.scell.io"
            BE["Laravel 12<br/>PHP 8.2+<br/>backend/public/"]
            HZ["Horizon<br/>(Queue Worker)"]
        end

        subgraph "Services Manages (Scaleway)"
            PG["PostgreSQL 17<br/>(base principale)"]
            PGS["PostgreSQL 17<br/>(base sandbox)"]
            RD["Redis 7.4<br/>(cache + sessions + queue)"]
            S3["Scaleway S3<br/>(stockage factures/docs)"]
        end
    end

    U --> FE
    U --> BE
    AI -->|"MCP JSON-RPC"| BE
    SPDP -->|"Webhooks"| BE
    OA -->|"Webhooks"| BE
    BE --> PG
    BE --> PGS
    BE --> RD
    BE --> S3
    BE -->|"API"| SPDP
    BE -->|"API"| OA
    HZ --> PG
    HZ --> RD

Composants ​

ComposantDomaineTypeStack
Frontendscell.ioSite statiqueReact 19 + Vite (dist/)
Backendapi.scell.ioApplication PHPLaravel 12 + PHP 8.2
Base de donnees-Service managePostgreSQL 17 (Scaleway)
Cache/Queue-Service manageRedis 7.4 (Scaleway)
Stockage-Service manageScaleway S3 (fr-par)

Hebergement Ploi.io ​

Ploi.io gere :

  • Le provisionning des serveurs Scaleway
  • Les certificats SSL Let's Encrypt (renouvellement automatique)
  • Le deploiement automatise via webhook Git
  • La supervision des processus (PHP-FPM, Horizon, daemon WebSocket Reverb)

Serveur : Scaleway DEV1-M (3 vCPU, 4GB RAM) SSH : ssh ploi@51.15.234.246Chemins :

SiteChemin sur le serveur
Backend/home/ploi/api.scell.io/backend
Frontend/home/ploi/scell.io/frontend

2. Prerequis ​

Backend ​

ComposantVersion minimaleVerification
PHP8.2+php -v
Composer2.xcomposer --version
PostgreSQL17psql --version
Redis7.4redis-cli --version
wkhtmltopdf0.12+wkhtmltopdf --version
LibreOffice (headless)24.2+soffice --headless --version

Extensions PHP requises :

bcmath, ctype, curl, dom, fileinfo, gd, intl, json, mbstring,
openssl, pdo, pdo_pgsql, redis, tokenizer, xml, zip

LibreOffice — conversion Word -> PDF (wizard de signature) ​

Le wizard de signature convertit les documents Word (.docx, .doc) en PDF fidele cote serveur via LibreOffice headless. Sans cette dependance, l'endpoint POST /api/v1/signatures/convert-document renvoie 422.

bash
# Debian / Ubuntu (24.04+) — paquets minimaux (headless, ~350 Mo)
sudo apt-get install --no-install-recommends \
    libreoffice-writer libreoffice-core \
    fonts-liberation fonts-dejavu \
    fonts-crosextra-carlito fonts-crosextra-caladea

Les polices fonts-crosextra-carlito (≈ Calibri) et fonts-crosextra-caladea (≈ Cambria) sont metriques-compatibles : elles garantissent une pagination fidele aux .docx Word sans les polices Microsoft (non redistribuables). Ne pas les omettre. Variables d'env optionnelles : LIBREOFFICE_BIN, LIBREOFFICE_TIMEOUT.

Frontend ​

ComposantVersion minimaleVerification
Node.js20+node --version
npm10+npm --version

Certificats SSL ​

Les certificats Let's Encrypt sont geres automatiquement par Ploi.io :

/etc/letsencrypt/live/api.scell.io/fullchain.pem
/etc/letsencrypt/live/api.scell.io/privkey.pem

3. Deploiement Backend (api.scell.io) ​

Script de deploiement ​

bash
#!/bin/bash
# deploy-backend.sh
set -e

cd /home/ploi/api.scell.io/backend

# 1. Mettre le site en maintenance
php artisan down --render="errors::503" --retry=60

# 2. Recuperer les derniers changements
git pull origin main

# 3. Installer les dependances (production)
composer install --no-dev --optimize-autoloader --no-interaction

# 4. Executer les migrations
php artisan migrate --force

# 5. Mettre en cache la configuration
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache

# 6. Nettoyer les caches obsoletes (AVANT les signaux de restart : cache:clear
#    effacerait le signal `laravel:reverb:restart`)
php artisan cache:clear

# 7. Redemarrer les workers de queue et le serveur WebSocket
php artisan horizon:terminate
php artisan reverb:restart
# Horizon et Reverb sont relances automatiquement par leur superviseur
# (smoke check Reverb non bloquant : voir "Serveur WebSocket Reverb")

# 8. Remettre le site en ligne
php artisan up

echo "Deploiement backend termine avec succes."

Commandes post-deploiement ​

bash
# Verifier que Horizon tourne
php artisan horizon:status

# Verifier que Reverb (WebSocket) repond — voir "Serveur WebSocket Reverb"
curl -fsS http://127.0.0.1:6001/up

# Verifier la sante de l'API
curl -s https://api.scell.io/health | jq .

# Verifier les migrations
php artisan migrate:status

Configuration Nginx (reference) ​

Le fichier nginx.conf.example a la racine du projet contient la configuration de reference :

nginx
server {
    listen 443 ssl http2;
    server_name api.scell.io;

    root /home/ploi/api.scell.io/backend/public;
    index index.php;

    ssl_certificate /etc/letsencrypt/live/api.scell.io/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.scell.io/privkey.pem;

    # CORS headers
    add_header Access-Control-Allow-Origin "https://scell.io" always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-API-Key, X-Tenant-Key, X-API-Key, X-Scell-API-Key, mcp-session-id" always;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # SSE pour MCP streaming
    location /mcp/stream {
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 3600s;
        try_files $uri /index.php?$query_string;
    }
}

Superviseur Horizon ​

Ploi.io configure automatiquement le superviseur pour Horizon :

ini
[program:horizon]
process_name=%(program_name)s
command=php /home/ploi/api.scell.io/backend/artisan horizon
autostart=true
autorestart=true
user=ploi
redirect_stderr=true
stdout_logfile=/home/ploi/api.scell.io/backend/storage/logs/horizon.log
stopwaitsecs=3600

Serveur WebSocket Reverb ​

BROADCAST_CONNECTION=reverb suppose un serveur Laravel Reverb en fonctionnement. Sans lui, les events temps reel (statuts factures, signatures, ledger) ne sont jamais delivres au dashboard, sans aucune erreur cote API. Le daemon et le proxy nginx sont provisionnes cote serveur (panel Ploi), hors du repo ; le repo porte la configuration (config/reverb.php), le restart au deploy et le smoke check.

Chaine complete :

text
Dashboard (app.scell.io / app.jefacturebien.fr)
  └─ wss://api.scell.io/app/{REVERB_APP_KEY}      VITE_REVERB_HOST=api.scell.io, port 443, https
       └─ nginx `location /app/` → http://127.0.0.1:6001   (Upgrade / Connection)
            └─ daemon supervisor : php artisan reverb:start --host=127.0.0.1 --port=6001
Backend (API + Horizon) → POST http://127.0.0.1:6001/apps/{REVERB_APP_ID}/events   (REVERB_HOST:REVERB_PORT)

1. Daemon supervisor (Ploi → Server → Daemons) ​

ini
[program:reverb]
process_name=%(program_name)s
command=php /home/ploi/api.scell.io/backend/artisan reverb:start --host=127.0.0.1 --port=6001
directory=/home/ploi/api.scell.io/backend
autostart=true
autorestart=true
user=ploi
redirect_stderr=true
stdout_logfile=/home/ploi/api.scell.io/backend/storage/logs/reverb.log
stopwaitsecs=30
stopasgroup=true
killasgroup=true
  • Ploi nomme le programme worker-<id> (et non reverb) : l'identifier avec grep -l 'reverb:start' /etc/supervisor/conf.d/*.conf.
  • --host / --port doivent rester identiques a REVERB_SERVER_HOST / REVERB_SERVER_PORT (sans option, reverb:start lit ces valeurs). Le port serveur doit etre celui de REVERB_PORT : c'est la que le backend publie les events.
  • Ne jamais binder 0.0.0.0 : le serveur n'est joignable que via nginx (TLS + filtrage).
  • Beaucoup de connexions simultanees : relever la limite de descripteurs du daemon (minfds supervisor / ulimit -n).

2. Nginx (api.scell.io, et api.jefacturebien.fr si VITE_REVERB_HOST y pointe) ​

nginx
# WebSocket Reverb (protocole Pusher). Prefixe `/app/` AVEC slash final : ne capture
# pas `/apps/*` (API HTTP de publication, reservee au backend en loopback).
location /app/ {
    proxy_pass http://127.0.0.1:6001;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    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;
    # > REVERB_APP_PING_INTERVAL (60 s) sinon nginx coupe les sockets inactives
    proxy_read_timeout 120s;
    proxy_send_timeout 120s;
}

3. Origines autorisees ​

REVERB_ALLOWED_ORIGINS (defaut app.scell.io,app.jefacturebien.fr) liste les hotes autorises a ouvrir une socket : Reverb compare l'hote de l'en-tete Origin a chaque entree (motifs *.domaine acceptes). Les URLs completes sont normalisees en hote et le joker global * est toujours ignore. Une origine refusee recoit, apres l'upgrade, un pusher:error code 4009. Variables : voir section 5 "WebSockets (Reverb)".

4. Deploiement et smoke checks ​

scripts/deploy-api.sh execute php artisan reverb:restart apres horizon:terminate (signal en cache lu toutes les 5 s par le serveur, relance par le superviseur), puis sonde http://127.0.0.1:6001/up. Ce smoke est non bloquant : il affiche WARN reverb down si aucun serveur ne repond (daemon non provisionne) sans faire echouer le deploy.

Apres toute modification des variables REVERB_* : php artisan config:cache && php artisan reverb:restart.

Verifications manuelles :

bash
# Daemon en ecoute + sante
ss -tln | grep ':6001 '
curl -fsS http://127.0.0.1:6001/up                      # {"health":"OK"}
sudo supervisorctl status | grep -E 'reverb|worker-'

# Proxy nginx + origine : attendu "HTTP/1.1 101 Switching Protocols"
# (404 = `location /app/` absente, la requete est tombee sur Laravel)
curl -si --http1.1 --max-time 5 \
  -H "Connection: Upgrade" -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \
  -H "Origin: https://app.scell.io" \
  "https://api.scell.io/app/${REVERB_APP_KEY}?protocol=7" | head -n 1

5. Dashboard : variables VITE_REVERB_* (build Vite) ​

Le client Echo du dashboard lit sa configuration au build (import.meta.env, valeurs inlinees dans le bundle). Un bundle builde sans ces variables n'a pas de temps reel.

VariableValeur prodSource / role
VITE_REVERB_APP_KEYvaleur de REVERB_APP_KEY du backendCle publique Reverb (jamais REVERB_APP_SECRET). Obligatoire pour activer le temps reel
VITE_REVERB_HOSTapi.scell.ioHote public servant location /app/ (nginx). Obligatoire pour activer le temps reel
VITE_REVERB_PORT443Port public TLS. Defaut 443 si absent ou invalide
VITE_REVERB_SCHEMEhttpshttps active forceTLS ; http reserve au dev local. Defaut https

Fichier persistant hors git : /home/ploi/scell.io/frontend/.env.production (gitignore, survit aux git pull / git stash ; modele versionne : frontend/.env.production.example). scripts/deploy-dashboard.sh et scripts/deploy-website.sh (les deux buildent le dashboard) chargent ces 4 cles via scripts/lib/dashboard-realtime-env.sh juste avant vite build. Une variable deja exportee dans l'environnement du deploy prime sur le fichier. Seules les lignes VITE_REVERB_*=valeur sont lues : le fichier n'est jamais sourcee.

  • Cles obligatoires absentes : le deploy affiche WARN temps reel desactive ... et continue ; le bundle est fonctionnel, sans rafraichissement live.
  • Cles presentes : smoke post-build bloquant, la cle doit etre inlinee dans dist/assets/*.js (sinon ❌ smoke temps reel et le deploy echoue). Les wsHost:"..." litteraux eventuels sont affiches a titre informatif (le minifier peut garder l'hote dans une variable).

Hote WebSocket unique pour les deux canaux : le build est partage entre app.scell.io et app.jefacturebien.fr, qui ouvrent tous deux la socket sur wss://api.scell.io (l'hote app.jefacturebien.fr doit donc figurer dans REVERB_ALLOWED_ORIGINS). Pointer VITE_REVERB_HOST vers api.jefacturebien.fr exigerait la location /app/ sur ce vhost et s'appliquerait aussi a app.scell.io.

Authentification des channels prives, canal-aware au runtime : POST {resolveApiBaseUrl()}/broadcasting/auth, soit https://api.scell.io/api/v1/broadcasting/auth ou https://api.jefacturebien.fr/api/v1/broadcasting/auth (cookie de session same-domain + jeton XSRF). La route est enregistree sous /api/v1 par withBroadcasting(routes/channels.php, ['prefix' => 'api/v1', 'middleware' => ['api', 'auth:sanctum']]) (bootstrap/app.php) : invite → 401 JSON, tenant etranger → 403. Elle est couverte par le motif CORS api/* (plus d'entree dediee dans cors.paths).

Comportement runtime (frontend/src/lib/echo.ts) : sans VITE_REVERB_APP_KEY ou VITE_REVERB_HOST, getEcho() retourne null et emet une seule fois console.warn("[Echo] temps reel desactive : ...") ; useTenantChannel ne souscrit alors a rien. Aucune page ne doit dependre du temps reel pour fonctionner.

Noms d'events : les events App\Events\Tenant\* publient leur broadcastAs() (nom court de la classe). Echo prefixant tout nom par le namespace App.Events sauf s'il commence par ., useTenantChannel ecoute .InvoiceCreated, .CreditNoteSent, etc. La liste typee TENANT_BROADCAST_EVENTS (frontend/src/hooks/use-tenant-channel.ts) est le miroir des 11 sous-classes de AbstractTenantBroadcastEvent : tout ajout ou renommage cote backend doit y etre reporte (les handlers d'un event absent de la liste ne compilent pas).

Verification apres deploy :

bash
# La cle publique doit apparaitre dans le bundle servi (vide = temps reel non inline)
KEY="$(grep '^VITE_REVERB_APP_KEY=' /home/ploi/scell.io/frontend/.env.production | cut -d= -f2-)"
grep -rlF --include='*.js' "$KEY" /home/ploi/scell.io/frontend/dist/assets | head -n 1
grep -rhoE --include='*.js' 'wsHost:"[^"]*"' /home/ploi/scell.io/frontend/dist/assets | sort -u

Taches planifiees (Cron) ​

bash
# Ajouter au crontab de l'utilisateur ploi
* * * * * cd /home/ploi/api.scell.io/backend && php artisan schedule:run >> /dev/null 2>&1

Taches planifiees automatiques :

CommandeFrequenceDescription
fiscal:daily-closingQuotidien 00:05 UTCCloture fiscale journaliere
fiscal:monthly-closing1er du moisCloture fiscale mensuelle
fiscal:integrity-checkQuotidien 02:00 UTCVerification d'integrite
fiscal:check-version-changeQuotidienDetection changement de version
billing:generate-monthly-invoices1er du moisFactures tenants
superpdp:sync-statusesToutes les 5 minSynchronisation statuts
horizon:snapshotToutes les 5 minMetriques Horizon

4. Deploiement Frontend (scell.io) ​

Script de deploiement ​

bash
#!/bin/bash
# deploy-frontend.sh
set -e

cd /home/ploi/scell.io/frontend

# 1. Recuperer les derniers changements
git pull origin main

# 2. Installer les dependances
npm ci

# 3. Build de production
npm run build

echo "Deploiement frontend termine. Le dossier dist/ est pret."

Le dossier dist/ genere par Vite est servi directement par Nginx comme site statique.

Builds bornes en memoire et rebuild du dashboard ​

Incident du 2026-10-14 : sur l'instance DEV1-S (2 Go), le build Vite lance avec --max-old-space-size=4096 a fait swapper toute la machine jusqu'au gel. API, dashboard et sites ont ete hors service pendant environ 20 min. L'instance est depuis passee en DEV1-M (4 Go). scripts/deploy-website.sh et scripts/deploy-dashboard.sh appliquent en outre deux protections :

  • Plafond memoire par build (scripts/lib/bounded-build.sh) : type-check, vite build et les deux next build tournent chacun dans un scope cgroup systemd-run --user avec MemoryMax=2816M, MemorySwapMax=1536M, RuntimeMaxSec=1800, et un tas V8 plafonne a 2048 Mo. Un depassement tue le build seul : le deploy echoue, la prod reste servie, et le message tue par le plafond memoire du cgroup s'affiche. Chaque build journalise son pic ([build:<nom>] pic memoire cgroup). Le deploy refuse de demarrer si MemAvailable < 1536 Mo. Le linger systemd de ploi est active automatiquement : sans lui, la fin de la session SSH ou du hook arreterait le build en cours. Les valeurs se surchargent via SCELL_BUILD_*.
  • Dashboard reconstruit seulement si ses entrees changent (scripts/lib/dashboard-publish.sh) : l'empreinte couvre l'arbre git frontend/, les scripts de publication, la version de Node, les variables VITE_* (hors VITE_RELEASE) et les fichiers dotenv de frontend/. Si l'empreinte et le index.html servi correspondent au marqueur /home/ploi/.scell-dashboard-published, le build est saute (Dashboard inchange). Un bundle inchange garde la release du commit qui l'a produit, celle de ses source maps. SCELL_FORCE_DASHBOARD_BUILD=1 force la reconstruction.

Tests : bash scripts/tests/dashboard-publish.test.sh (job CI deploy-scripts-test).

Configuration Nginx ​

nginx
server {
    listen 443 ssl http2;
    server_name scell.io www.scell.io;

    root /home/ploi/scell.io/frontend/dist;
    index index.html;

    ssl_certificate /etc/letsencrypt/live/scell.io/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/scell.io/privkey.pem;

    # SPA fallback
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Cache des assets statiques
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

Variables d'environnement frontend ​

Le fichier /home/ploi/scell.io/frontend/.env.production (gitignore, persistant hors git ; modele frontend/.env.production.example) :

bash
VITE_API_URL=https://api.scell.io/api/v1
VITE_APP_NAME=Scell.io
VITE_REVERB_APP_KEY=<valeur de REVERB_APP_KEY du backend>
VITE_REVERB_HOST=api.scell.io
VITE_REVERB_PORT=443
VITE_REVERB_SCHEME=https

Ces variables sont integrees au build Vite (prefixe VITE_). Les VITE_REVERB_* sont optionnelles (absentes = dashboard sans temps reel) et verifiees au deploy : voir "Serveur WebSocket Reverb" §5.


5. Variables d'Environnement ​

Backend (backend/.env) ​

Application ​

VariableDescriptionExemple
APP_NAMENom de l'applicationScell.io
APP_ENVEnvironnementproduction
APP_KEYCle de chiffrement (generer avec php artisan key:generate)base64:...
APP_DEBUGMode debug (toujours false en production)false
APP_TIMEZONEFuseau horaireEurope/Paris
APP_URLURL de base de l'APIhttps://api.scell.io
APP_LOCALELangue par defautfr

Base de donnees ​

VariableDescriptionExemple
DB_CONNECTIONDriver de BDDpgsql
DB_HOSTHote PostgreSQLrdb-xxx.scw.cloud
DB_PORTPort5432
DB_DATABASENom de la basescell_production
DB_USERNAMEUtilisateurscell
DB_PASSWORDMot de passe***
DB_HOST_SANDBOXHote BDD sandbox (optionnel)127.0.0.1
DB_DATABASE_SANDBOXBase sandboxscell_sandbox

Cache et Queue ​

VariableDescriptionExemple
CACHE_STOREDriver de cacheredis
CACHE_PREFIXPrefixe des cles cachescell_
QUEUE_CONNECTIONDriver de queueredis
SESSION_DRIVERDriver de sessionredis
REDIS_HOSTHote Redisredis-xxx.scw.cloud
REDIS_PORTPort Redis6379
REDIS_PASSWORDMot de passe Redis***
HORIZON_PREFIXPrefixe Horizonscell_horizon:

WebSockets (Reverb) ​

VariableDescriptionExemple
BROADCAST_CONNECTIONBroadcaster Laravel (exige un daemon Reverb actif)reverb
REVERB_APP_IDIdentifiant de l'application Reverbscell
REVERB_APP_KEYCle publique (aussi VITE_REVERB_APP_KEY du dashboard)***
REVERB_APP_SECRETSecret de signature des events***
REVERB_HOSTHote ou le backend publie les events127.0.0.1
REVERB_PORTPort ou le backend publie les events6001
REVERB_SCHEMESchema backend → serveur (loopback)http
REVERB_SERVER_HOSTAdresse d'ecoute du daemon (loopback, derriere nginx)127.0.0.1
REVERB_SERVER_PORTPort d'ecoute du daemon (defaut : REVERB_PORT)6001
REVERB_ALLOWED_ORIGINSHotes autorises (en-tete Origin), separes par des virgules ; * ignoreapp.scell.io,app.jefacturebien.fr

Cote dashboard (build Vite) : VITE_REVERB_APP_KEY (= REVERB_APP_KEY), VITE_REVERB_HOST=api.scell.io, VITE_REVERB_PORT=443, VITE_REVERB_SCHEME=https, lues depuis /home/ploi/scell.io/frontend/.env.production par les scripts de deploy (details, smoke et comportement sans config : "Serveur WebSocket Reverb" §5).

Mail ​

VariableDescriptionExemple
MAIL_MAILERDriver mailsmtp
MAIL_HOSTServeur SMTPsmtp.tem.scw.cloud
MAIL_PORTPort SMTP587
MAIL_USERNAMEUtilisateur SMTP***
MAIL_PASSWORDMot de passe SMTP***
MAIL_ENCRYPTIONChiffrementtls
MAIL_FROM_ADDRESSAdresse d'envoinoreply@scell.io

Services externes ​

VariableDescriptionExemple
GOOGLE_CLIENT_IDOAuth Googlexxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRETSecret Google OAuth***
OPENAPI_BASE_URLURL OpenAPI.comhttps://esignature.openapi.com
OPENAPI_SANDBOX_URLURL sandbox OpenAPIhttps://test.esignature.openapi.com
OPENAPI_BEARER_TOKENToken OpenAPI.com***
SUPERPDP_API_URLURL SuperPDP (host unique, auth OAuth 2.1 via SUPERPDP_OAUTH2_*)https://api.superpdp.tech
SUPERPDP_WEBHOOK_SECRETSecret webhooks SuperPDP***
SUPERPDP_SANDBOXMode sandbox SuperPDPfalse
BULKGATE_APPLICATION_IDID app BulkGate (SMS)***
BULKGATE_APPLICATION_TOKENToken BulkGate***

INSEE (verification SIRET) ​

VariableDescriptionExemple
INSEE_API_URLURL API Sirenehttps://api.insee.fr/api-sirene/3.11
INSEE_API_KEYCle API INSEE (header X-INSEE-Api-Key-Integration)***

Stockage S3 ​

VariableDescriptionExemple
AWS_ACCESS_KEY_IDAccess key ScalewaySCW...
AWS_SECRET_ACCESS_KEYSecret key***
AWS_DEFAULT_REGIONRegionfr-par
AWS_BUCKETBucketscell-storage
AWS_ENDPOINTEndpoint S3https://s3.fr-par.scw.cloud
AWS_USE_PATH_STYLE_ENDPOINTPath styletrue

MCP ​

VariableDescriptionExemple
MCP_ENABLEDActiver le serveur MCPtrue
MCP_SESSION_TTLDuree de session (secondes)3600
MCP_SESSION_DRIVERDriver de session MCPredis
MCP_RATE_LIMITRequetes par minute60

Conformite fiscale ​

VariableDescriptionExemple
FISCAL_COMPLIANCE_ENABLEDActiver la conformite fiscaletrue
FISCAL_EDITOR_NAMENom de l'editeurQR Communication SAS
FISCAL_EDITOR_SIRETSIRET de l'editeur12345678900001
FISCAL_LICENSE_NUMBERNumero de licence/attestation ISCAISCA-AUTO-2026-001
FISCAL_ANCHOR_PROVIDERProvider d'ancragerfc3161_tsa
FISCAL_TSA_URLURL du TSAhttps://freetsa.org/tsr
FISCAL_RETENTION_YEARSDuree de retention (annees)6

PDF ​

VariableDescriptionExemple
SNAPPY_PDF_BINARYChemin wkhtmltopdf/usr/bin/wkhtmltopdf
SNAPPY_IMAGE_BINARYChemin wkhtmltoimage/usr/bin/wkhtmltoimage

6. Monitoring ​

Horizon Dashboard ​

Laravel Horizon fournit un dashboard web pour superviser les queues :

URL : https://api.scell.io/horizonAcces : Restreint aux administrateurs (authentification Sanctum + role admin).

Le dashboard affiche :

  • Etat des workers (actifs, en pause, termines)
  • Metriques de throughput (jobs/minute)
  • Jobs en attente, en cours, echoues
  • Temps d'execution moyen
  • Historique des jobs

Logs Laravel ​

Les logs sont stockes dans backend/storage/logs/ :

FichierDescription
laravel.logLog general de l'application
horizon.logLog du worker Horizon
scheduler.logLog du planificateur

Configuration dans config/logging.php :

php
'channels' => [
    'stack' => ['driver' => 'stack', 'channels' => ['single']],
    'single' => ['driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => 'info'],
],

En production, le niveau de log est info (pas de debug).

Health Checks ​

Le endpoint /health retourne l'etat de l'API :

bash
curl -s https://api.scell.io/health | jq .
json
{
  "status": "ok",
  "timestamp": "2026-03-03T14:30:00+00:00"
}

Le MCP health_check tool fournit un diagnostic plus detaille (database, cache).

Metriques recommandees ​

MetriqueSourceSeuil d'alerte
Temps de reponse API (p95)Nginx access log> 500ms
Queue pending jobsHorizon> 1000
Queue failed jobsHorizon> 0
Espace disqueServeur< 20% libre
Memoire PHPPHP-FPM> 80% utilise
Connexions PostgreSQLPG> 80% du max
Redis memoireRedis> 80% du max

7. Troubleshooting ​

Erreurs communes ​

"Connection refused" sur l'API ​

Symptome : L'API retourne une erreur 502 Bad Gateway.

Causes possibles :

  1. PHP-FPM n'est pas demarre
  2. Le socket Unix n'existe pas

Solution :

bash
# Verifier le statut de PHP-FPM
sudo systemctl status php8.2-fpm

# Redemarrer si necessaire
sudo systemctl restart php8.2-fpm

# Verifier le socket
ls -la /var/run/php/php8.2-fpm.sock

Les jobs de queue ne s'executent pas ​

Symptome : Les factures restent au statut "pending", les webhooks ne sont pas envoyes.

Solution :

bash
# Verifier Horizon
cd /home/ploi/api.scell.io/backend
php artisan horizon:status

# Si termine, redemarrer
php artisan horizon:terminate
# Le superviseur le relancera automatiquement

# Verifier les jobs echoues
php artisan queue:failed

# Relancer les jobs echoues
php artisan queue:retry all

Le dashboard ne recoit aucun event temps reel (Reverb) ​

Symptome : statuts de factures/signatures non rafraichis en direct, console navigateur en echec de connexion wss://api.scell.io/app/..., ou deploy affichant WARN reverb down.

Cause cote dashboard (a verifier en premier si la console affiche [Echo] temps reel desactive) : le bundle a ete builde sans VITE_REVERB_APP_KEY / VITE_REVERB_HOST (deploy affichant WARN temps reel desactive). Renseigner /home/ploi/scell.io/frontend/.env.production puis redeployer le dashboard (voir "Serveur WebSocket Reverb" §5). Un POST /api/v1/broadcasting/auth en echec CORS ou 404 indique une URL d'auth ou une config cors.paths non a jour.

Solution :

bash
# 1. Le daemon ecoute-t-il ? (vide = daemon absent ou arrete)
ss -tln | grep ':6001 '
curl -fsS http://127.0.0.1:6001/up
grep -l 'reverb:start' /etc/supervisor/conf.d/*.conf   # programme worker-<id>
sudo supervisorctl status

# 2. Relancer (apres config:cache si des variables REVERB_* ont change)
php artisan config:cache && php artisan reverb:restart
tail -50 /home/ploi/api.scell.io/backend/storage/logs/reverb.log

# 3. Le proxy nginx transmet-il l'upgrade ? (404 = `location /app/` absente)
curl -si --http1.1 --max-time 5 -H "Connection: Upgrade" -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \
  -H "Origin: https://app.scell.io" \
  "https://api.scell.io/app/${REVERB_APP_KEY}?protocol=7" | head -n 1

Un pusher:error code 4009 apres connexion = origine refusee : ajouter l'hote du dashboard a REVERB_ALLOWED_ORIGINS (hote, pas URL). Provisionnement complet : section "Serveur WebSocket Reverb".

Erreur de migration ​

Symptome : SQLSTATE[42P07]: Duplicate table ou SQLSTATE[42P01]: Undefined table.

Solution :

bash
# Verifier l'etat des migrations
php artisan migrate:status

# Si une migration est bloquee, ne JAMAIS la modifier en production
# Creer une nouvelle migration corrective a la place

Probleme de certificat SSL ​

Symptome : cURL error 60: SSL certificate problem

Solution :

bash
# Verifier les certificats
sudo certbot certificates

# Renouveler manuellement si necessaire
sudo certbot renew --force-renewal

# Verifier la configuration Nginx
sudo nginx -t
sudo systemctl reload nginx

Solde insuffisant pour un tenant ​

Symptome : Erreur 402 "Solde insuffisant" lors de la creation de facture.

Solution :

bash
# Verifier le solde du tenant (via Tinker)
php artisan tinker
>>> Tenant::find('uuid-du-tenant')->balance
# 0.00

# Crediter manuellement (admin)
curl -X POST https://api.scell.io/api/v1/admin/users/{userId}/credit \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{ "amount": 100.00, "reason": "Recharge manuelle" }'

Redis saturee ​

Symptome : MISCONF Redis is configured to save RDB snapshots

Solution :

bash
# Verifier l'utilisation memoire
redis-cli info memory

# Nettoyer les sessions MCP expirees
php artisan tinker
>>> App\Models\McpSession::where('expires_at', '<', now())->delete()

# Vider le cache si necessaire
php artisan cache:clear

Performance degradee ​

Symptome : Temps de reponse > 1 seconde.

Diagnostic :

bash
# Verifier que les caches sont actifs
php artisan config:cache
php artisan route:cache

# Verifier les requetes lentes PostgreSQL
# Dans psql :
SELECT query, calls, mean_time, total_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10;

# Verifier les index manquants
php artisan tinker
>>> DB::select("SELECT schemaname, tablename, indexname FROM pg_indexes WHERE schemaname = 'public' ORDER BY tablename")

FAQ technique ​

Q : Comment regenerer la cle APP_KEY ?

bash
php artisan key:generate
# ATTENTION : invalide toutes les sessions et tokens existants

Q : Comment ajouter un nouveau worker Horizon ?

Editer config/horizon.php et ajouter un nouveau superviseur dans la section environments.

Q : Comment debugger un webhook SuperPDP ?

Les webhooks sont traces dans la table processed_webhooks. Consulter les logs :

bash
grep "SuperPDP" /home/ploi/api.scell.io/backend/storage/logs/laravel.log | tail -20

Q : Comment forcer une cloture fiscale journaliere ?

bash
php artisan fiscal:daily-closing --force

Q : Ou sont stockes les fichiers (factures PDF, documents KYB) ?

En production, les fichiers sont sur Scaleway S3 (bucket scell-storage). En local, ils sont dans backend/storage/app/.

Q : Comment acceder a la base de donnees en production ?

bash
ssh ploi@51.15.234.246
psql -h rdb-xxx.scw.cloud -U scell -d scell_production

Documentation Scell.io