From ea2d620c7a2b2d07c2e891add3ac69160d45bfce Mon Sep 17 00:00:00 2001 From: syoul Date: Sun, 23 Nov 2025 10:57:01 +0100 Subject: [PATCH] =?UTF-8?q?Ajout=20interface=20admin=20d=C3=A9ployable=20s?= =?UTF-8?q?ur=20serveur?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - API routes pour gestion clients (CRUD complet) - Système de stockage JSON persistant (data/clients.json) - Configuration Next.js serveur (next.config.server.js) - Script de build pour déploiement (scripts/build-server.sh) - Documentation complète de déploiement (ADMIN_DEPLOY.md) Fonctionnalités admin: - Création/modification/suppression de clients - Génération automatique de tokens uniques - QR codes pour configuration clients - Authentification par mot de passe - Backend Node.js avec API REST Déploiement prévu: marama.syoul.fr --- .gitignore | 16 + ADMIN_DEPLOY.md | 437 ++++++++++++++++++++++++++++ app/api/admin/clients/[id]/route.ts | 153 ++++++++++ app/api/admin/clients/route.ts | 131 +++++++++ app/api/client/[token]/route.ts | 55 ++++ data/.gitkeep | 2 + next.config.server.js | 19 ++ scripts/build-server.sh | 190 ++++++++++++ 8 files changed, 1003 insertions(+) create mode 100644 ADMIN_DEPLOY.md create mode 100644 app/api/admin/clients/[id]/route.ts create mode 100644 app/api/admin/clients/route.ts create mode 100644 app/api/client/[token]/route.ts create mode 100644 data/.gitkeep create mode 100644 next.config.server.js create mode 100755 scripts/build-server.sh diff --git a/.gitignore b/.gitignore index 8ac666e..7b1db7b 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,19 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts +# android +/android/.gradle +/android/build +/android/app/build +/android/local.properties +/android/.idea + +# capacitor +/ios +/android/.cxx + +# data storage (ne pas commit les données clients) +/data/clients.json + +# APK +/dist/*.apk diff --git a/ADMIN_DEPLOY.md b/ADMIN_DEPLOY.md new file mode 100644 index 0000000..2955f27 --- /dev/null +++ b/ADMIN_DEPLOY.md @@ -0,0 +1,437 @@ +# 🖥️ Guide de déploiement de l'interface Admin + +Ce guide explique comment déployer l'interface d'administration sur votre serveur **marama.syoul.fr**. + +--- + +## 📋 Vue d'ensemble + +``` +┌─────────────────────┐ ┌──────────────────────────┐ +│ APK Client │ │ Admin Web │ +│ (Statique) │ │ marama.syoul.fr │ +│ │ │ │ +│ • Accueil │ │ • Login admin │ +│ • Explorer │ ←────│ • Gestion clients │ +│ • Mana Tracker │ │ • Génération tokens │ +│ • Infos │ │ • QR codes │ +└─────────────────────┘ └──────────────────────────┘ + Voyageurs Gérant (vous) +``` + +--- + +## 🚀 Déploiement en 5 étapes + +### Étape 1 : Build de l'application admin + +```bash +cd "/home/syoul/Ccompagnon Marama" +./scripts/build-server.sh +``` + +Ce script va : +- ✅ Configurer Next.js en mode serveur (avec API routes) +- ✅ Build l'application +- ✅ Préparer les fichiers dans `deploy/` +- ✅ Créer les scripts de lancement + +**⏱️ Durée :** 2-3 minutes + +--- + +### Étape 2 : Transférer sur votre serveur + +```bash +# Depuis votre machine locale +rsync -avz --delete deploy/ user@marama.syoul.fr:/var/www/pension-admin/ + +# Remplacez 'user' par votre nom d'utilisateur SSH +``` + +**Alternative avec SCP :** +```bash +scp -r deploy/* user@marama.syoul.fr:/var/www/pension-admin/ +``` + +--- + +### Étape 3 : Configuration sur le serveur + +```bash +# Se connecter au serveur +ssh user@marama.syoul.fr + +# Aller dans le dossier +cd /var/www/pension-admin + +# Configurer le mot de passe admin +cp .env.example .env +nano .env +``` + +**Contenu de `.env` :** +```bash +# ⚠️ IMPORTANT : Changez ce mot de passe ! +ADMIN_PASSWORD=votre_mot_de_passe_tres_securise + +# Port (optionnel) +PORT=3000 +``` + +--- + +### Étape 4 : Installer et démarrer + +#### Option A : Lancement simple (test) + +```bash +chmod +x start.sh +./start.sh +``` + +L'admin sera accessible sur `http://marama.syoul.fr:3000` + +#### Option B : Avec PM2 (production, recommandé ⭐) + +```bash +# Installer PM2 (si pas déjà fait) +npm install -g pm2 + +# Démarrer l'application +pm2 start npm --name "pension-admin" -- start + +# Sauvegarder la config PM2 +pm2 save + +# Configurer le démarrage auto +pm2 startup +# Suivre les instructions affichées + +# Commandes utiles PM2 +pm2 status # Voir le statut +pm2 logs pension-admin # Voir les logs +pm2 restart pension-admin # Redémarrer +pm2 stop pension-admin # Arrêter +``` + +--- + +### Étape 5 : Configuration Nginx (reverse proxy) + +#### A. Créer la configuration Nginx + +```bash +sudo nano /etc/nginx/sites-available/pension-admin +``` + +**Contenu du fichier :** +```nginx +server { + listen 80; + server_name admin.marama.syoul.fr; + + # Logs + access_log /var/log/nginx/pension-admin-access.log; + error_log /var/log/nginx/pension-admin-error.log; + + location / { + proxy_pass http://localhost:3000; + 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; + proxy_cache_bypass $http_upgrade; + } +} +``` + +#### B. Activer la configuration + +```bash +# Créer le lien symbolique +sudo ln -s /etc/nginx/sites-available/pension-admin /etc/nginx/sites-enabled/ + +# Tester la configuration +sudo nginx -t + +# Recharger Nginx +sudo systemctl reload nginx +``` + +#### C. Configurer SSL (HTTPS) avec Let's Encrypt + +```bash +# Installer Certbot (si pas déjà fait) +sudo apt install certbot python3-certbot-nginx + +# Obtenir le certificat SSL +sudo certbot --nginx -d admin.marama.syoul.fr + +# Le renouvellement est automatique ! +``` + +--- + +## 🔐 Configuration DNS + +Ajoutez un enregistrement A ou CNAME dans votre DNS : + +``` +Type: A +Nom: admin.marama.syoul.fr (ou juste "admin") +Valeur: [IP de votre serveur] +TTL: 3600 +``` + +--- + +## 🎯 Utilisation de l'interface admin + +### 1. Se connecter + +``` +URL: https://admin.marama.syoul.fr +Mot de passe: celui défini dans .env +``` + +### 2. Ajouter un client + +1. Cliquer sur **"Nouveau client"** +2. Remplir le formulaire : + - **Email** : email du voyageur + - **N° Bungalow** : 1, 2, 3, etc. + - **WiFi** : nom et mot de passe + - **Message du gérant** : message personnalisé + +3. Cliquer sur **"Créer"** +4. Un **QR code** et un **lien unique** sont générés automatiquement + +### 3. Partager avec le client + +**Option A : QR Code** (recommandé) +- Afficher le QR code +- Le voyageur scan avec son téléphone +- L'app se configure automatiquement + +**Option B : Lien** +- Copier le lien unique +- L'envoyer par email/SMS au client + +--- + +## 🔄 Mise à jour de l'application + +Quand vous modifiez le code : + +```bash +# Sur votre machine locale +cd "/home/syoul/Ccompagnon Marama" +./scripts/build-server.sh + +# Transférer +rsync -avz --delete deploy/ user@marama.syoul.fr:/var/www/pension-admin/ + +# Sur le serveur +ssh user@marama.syoul.fr +cd /var/www/pension-admin +pm2 restart pension-admin +``` + +--- + +## 📂 Structure des fichiers sur le serveur + +``` +/var/www/pension-admin/ +├── .next/ # Application Next.js compilée +├── public/ # Assets statiques +├── data/ +│ └── clients.json # Base de données des clients (créé auto) +├── package.json # Dépendances +├── next.config.js # Config Next.js +├── .env # Variables d'environnement (mot de passe) +├── start.sh # Script de lancement +└── DEPLOY.md # Documentation +``` + +--- + +## 🔒 Sécurité + +### Recommandations importantes + +1. **Mot de passe fort** + ```bash + # Générer un mot de passe sécurisé + openssl rand -base64 32 + ``` + +2. **Permissions fichiers** + ```bash + chmod 600 /var/www/pension-admin/.env + chmod 600 /var/www/pension-admin/data/clients.json + ``` + +3. **Firewall** + ```bash + # Autoriser uniquement HTTP/HTTPS + sudo ufw allow 80/tcp + sudo ufw allow 443/tcp + sudo ufw enable + ``` + +4. **Sauvegarde des données** + ```bash + # Créer un cron job pour sauvegarder clients.json + 0 2 * * * cp /var/www/pension-admin/data/clients.json /backup/clients-$(date +\%Y\%m\%d).json + ``` + +--- + +## 🐛 Dépannage + +### L'application ne démarre pas + +```bash +# Vérifier les logs +pm2 logs pension-admin + +# Vérifier que Node.js est installé +node --version # doit être >= 18 + +# Réinstaller les dépendances +cd /var/www/pension-admin +rm -rf node_modules +npm ci --production +pm2 restart pension-admin +``` + +### Erreur 502 Bad Gateway + +```bash +# Vérifier que l'app tourne +pm2 status + +# Vérifier les logs Nginx +sudo tail -f /var/log/nginx/pension-admin-error.log + +# Redémarrer Nginx +sudo systemctl restart nginx +``` + +### Les clients ne peuvent pas se connecter + +```bash +# Vérifier les permissions du fichier data +ls -la /var/www/pension-admin/data/ + +# Vérifier le contenu +cat /var/www/pension-admin/data/clients.json + +# Si le fichier n'existe pas, l'app le créera automatiquement +``` + +### Changer le mot de passe admin + +```bash +# Sur le serveur +cd /var/www/pension-admin +nano .env +# Modifier ADMIN_PASSWORD + +# Redémarrer +pm2 restart pension-admin +``` + +--- + +## 📊 Monitoring + +### Voir les statistiques avec PM2 + +```bash +pm2 monit # Monitoring en temps réel +pm2 logs # Voir tous les logs +pm2 status # Statut de toutes les apps +``` + +### Logs Nginx + +```bash +# Logs d'accès +sudo tail -f /var/log/nginx/pension-admin-access.log + +# Logs d'erreurs +sudo tail -f /var/log/nginx/pension-admin-error.log +``` + +--- + +## 🎓 Commandes utiles + +```bash +# Redémarrer l'application +pm2 restart pension-admin + +# Voir les logs en direct +pm2 logs pension-admin --lines 100 + +# Recharger l'app (sans downtime) +pm2 reload pension-admin + +# Arrêter l'application +pm2 stop pension-admin + +# Démarrer l'application +pm2 start pension-admin + +# Supprimer de PM2 +pm2 delete pension-admin + +# Sauvegarder la config PM2 +pm2 save +``` + +--- + +## 💡 Conseils + +1. **Tester localement d'abord** + ```bash + # Sur votre machine + npm run dev + # Ouvrir http://localhost:3000/admin + ``` + +2. **Utiliser un sous-domaine dédié** + - `admin.marama.syoul.fr` (recommandé) + - Plutôt que `marama.syoul.fr/admin` + +3. **Configurer les sauvegardes automatiques** + - Le fichier `data/clients.json` contient toutes vos données + - Sauvegardez-le régulièrement + +4. **Monitoring avec Uptime Robot** + - Gratuit + - Vous alerte si le site est down + +--- + +## 📞 Support + +Si vous rencontrez des problèmes : + +1. Vérifier les logs : `pm2 logs pension-admin` +2. Vérifier Nginx : `sudo nginx -t` +3. Vérifier les permissions : `ls -la data/` + +--- + +**Version** : 1.0.0 - Novembre 2025 +**Serveur** : marama.syoul.fr +**URL Admin** : https://admin.marama.syoul.fr + diff --git a/app/api/admin/clients/[id]/route.ts b/app/api/admin/clients/[id]/route.ts new file mode 100644 index 0000000..0795769 --- /dev/null +++ b/app/api/admin/clients/[id]/route.ts @@ -0,0 +1,153 @@ +import { NextRequest, NextResponse } from "next/server"; +import { writeFile, readFile, mkdir } from "fs/promises"; +import { existsSync } from "fs"; +import path from "path"; +import { Client, ClientInput } from "@/lib/types/client"; + +const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "admin123"; +const DATA_DIR = path.join(process.cwd(), "data"); +const CLIENTS_FILE = path.join(DATA_DIR, "clients.json"); + +function verifyAuth(request: NextRequest): boolean { + const authHeader = request.headers.get("authorization"); + if (!authHeader) return false; + + const token = authHeader.replace("Bearer ", ""); + return token === ADMIN_PASSWORD; +} + +async function loadClients(): Promise { + try { + if (!existsSync(CLIENTS_FILE)) { + if (!existsSync(DATA_DIR)) { + await mkdir(DATA_DIR, { recursive: true }); + } + await writeFile(CLIENTS_FILE, JSON.stringify([], null, 2)); + return []; + } + + const data = await readFile(CLIENTS_FILE, "utf-8"); + return JSON.parse(data); + } catch (error) { + console.error("Erreur lecture clients:", error); + return []; + } +} + +async function saveClients(clients: Client[]): Promise { + try { + if (!existsSync(DATA_DIR)) { + await mkdir(DATA_DIR, { recursive: true }); + } + await writeFile(CLIENTS_FILE, JSON.stringify(clients, null, 2)); + } catch (error) { + console.error("Erreur sauvegarde clients:", error); + throw error; + } +} + +// GET - Récupérer un client par ID +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + if (!verifyAuth(request)) { + return NextResponse.json({ error: "Non autorisé" }, { status: 401 }); + } + + try { + const clients = await loadClients(); + const client = clients.find(c => c.id === params.id); + + if (!client) { + return NextResponse.json( + { error: "Client non trouvé" }, + { status: 404 } + ); + } + + return NextResponse.json(client); + } catch (error) { + console.error("Erreur GET client:", error); + return NextResponse.json( + { error: "Erreur serveur" }, + { status: 500 } + ); + } +} + +// PUT - Mettre à jour un client +export async function PUT( + request: NextRequest, + { params }: { params: { id: string } } +) { + if (!verifyAuth(request)) { + return NextResponse.json({ error: "Non autorisé" }, { status: 401 }); + } + + try { + const input: ClientInput = await request.json(); + const clients = await loadClients(); + const clientIndex = clients.findIndex(c => c.id === params.id); + + if (clientIndex === -1) { + return NextResponse.json( + { error: "Client non trouvé" }, + { status: 404 } + ); + } + + // Mettre à jour le client + const updatedClient: Client = { + ...clients[clientIndex], + email: input.email || clients[clientIndex].email, + bungalowNumber: input.bungalowNumber || clients[clientIndex].bungalowNumber, + wifiName: input.wifiName || clients[clientIndex].wifiName, + wifiPassword: input.wifiPassword || clients[clientIndex].wifiPassword, + gerantMessage: input.gerantMessage || clients[clientIndex].gerantMessage, + }; + + clients[clientIndex] = updatedClient; + await saveClients(clients); + + return NextResponse.json(updatedClient); + } catch (error) { + console.error("Erreur PUT client:", error); + return NextResponse.json( + { error: "Erreur serveur" }, + { status: 500 } + ); + } +} + +// DELETE - Supprimer un client +export async function DELETE( + request: NextRequest, + { params }: { params: { id: string } } +) { + if (!verifyAuth(request)) { + return NextResponse.json({ error: "Non autorisé" }, { status: 401 }); + } + + try { + const clients = await loadClients(); + const filteredClients = clients.filter(c => c.id !== params.id); + + if (filteredClients.length === clients.length) { + return NextResponse.json( + { error: "Client non trouvé" }, + { status: 404 } + ); + } + + await saveClients(filteredClients); + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Erreur DELETE client:", error); + return NextResponse.json( + { error: "Erreur serveur" }, + { status: 500 } + ); + } +} + diff --git a/app/api/admin/clients/route.ts b/app/api/admin/clients/route.ts new file mode 100644 index 0000000..054ca40 --- /dev/null +++ b/app/api/admin/clients/route.ts @@ -0,0 +1,131 @@ +import { NextRequest, NextResponse } from "next/server"; +import { writeFile, readFile, mkdir } from "fs/promises"; +import { existsSync } from "fs"; +import path from "path"; +import { Client, ClientInput } from "@/lib/types/client"; + +// Mot de passe admin (à changer en production via variable d'environnement) +const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "admin123"; + +// Chemin vers le fichier de stockage +const DATA_DIR = path.join(process.cwd(), "data"); +const CLIENTS_FILE = path.join(DATA_DIR, "clients.json"); + +// Vérifier l'authentification +function verifyAuth(request: NextRequest): boolean { + const authHeader = request.headers.get("authorization"); + if (!authHeader) return false; + + const token = authHeader.replace("Bearer ", ""); + return token === ADMIN_PASSWORD; +} + +// Charger les clients depuis le fichier +async function loadClients(): Promise { + try { + if (!existsSync(CLIENTS_FILE)) { + // Créer le répertoire et le fichier si nécessaire + if (!existsSync(DATA_DIR)) { + await mkdir(DATA_DIR, { recursive: true }); + } + await writeFile(CLIENTS_FILE, JSON.stringify([], null, 2)); + return []; + } + + const data = await readFile(CLIENTS_FILE, "utf-8"); + return JSON.parse(data); + } catch (error) { + console.error("Erreur lecture clients:", error); + return []; + } +} + +// Sauvegarder les clients dans le fichier +async function saveClients(clients: Client[]): Promise { + try { + if (!existsSync(DATA_DIR)) { + await mkdir(DATA_DIR, { recursive: true }); + } + await writeFile(CLIENTS_FILE, JSON.stringify(clients, null, 2)); + } catch (error) { + console.error("Erreur sauvegarde clients:", error); + throw error; + } +} + +// GET - Récupérer tous les clients +export async function GET(request: NextRequest) { + if (!verifyAuth(request)) { + return NextResponse.json({ error: "Non autorisé" }, { status: 401 }); + } + + try { + const clients = await loadClients(); + return NextResponse.json(clients); + } catch (error) { + console.error("Erreur GET clients:", error); + return NextResponse.json( + { error: "Erreur serveur" }, + { status: 500 } + ); + } +} + +// POST - Créer un nouveau client +export async function POST(request: NextRequest) { + if (!verifyAuth(request)) { + return NextResponse.json({ error: "Non autorisé" }, { status: 401 }); + } + + try { + const input: ClientInput = await request.json(); + + // Validation + if (!input.email || !input.bungalowNumber) { + return NextResponse.json( + { error: "Email et numéro de bungalow requis" }, + { status: 400 } + ); + } + + const clients = await loadClients(); + + // Vérifier si l'email existe déjà + if (clients.some(c => c.email === input.email)) { + return NextResponse.json( + { error: "Un client avec cet email existe déjà" }, + { status: 409 } + ); + } + + // Créer le nouveau client + const newClient: Client = { + id: `client-${Date.now()}`, + token: generateToken(), + email: input.email, + bungalowNumber: input.bungalowNumber, + wifiName: input.wifiName || "Lagon-WiFi", + wifiPassword: input.wifiPassword || "", + gerantMessage: input.gerantMessage || "Bienvenue dans notre pension de famille !", + createdAt: new Date().toISOString(), + }; + + clients.push(newClient); + await saveClients(clients); + + return NextResponse.json(newClient, { status: 201 }); + } catch (error) { + console.error("Erreur POST client:", error); + return NextResponse.json( + { error: "Erreur serveur" }, + { status: 500 } + ); + } +} + +// Générer un token unique +function generateToken(): string { + return Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15); +} + diff --git a/app/api/client/[token]/route.ts b/app/api/client/[token]/route.ts new file mode 100644 index 0000000..c8c4b0d --- /dev/null +++ b/app/api/client/[token]/route.ts @@ -0,0 +1,55 @@ +import { NextRequest, NextResponse } from "next/server"; +import { readFile } from "fs/promises"; +import { existsSync } from "fs"; +import path from "path"; +import { Client } from "@/lib/types/client"; + +const DATA_DIR = path.join(process.cwd(), "data"); +const CLIENTS_FILE = path.join(DATA_DIR, "clients.json"); + +async function loadClients(): Promise { + try { + if (!existsSync(CLIENTS_FILE)) { + return []; + } + + const data = await readFile(CLIENTS_FILE, "utf-8"); + return JSON.parse(data); + } catch (error) { + console.error("Erreur lecture clients:", error); + return []; + } +} + +// GET - Récupérer les données d'un client par son token +export async function GET( + request: NextRequest, + { params }: { params: { token: string } } +) { + try { + const clients = await loadClients(); + const client = clients.find(c => c.token === params.token); + + if (!client) { + return NextResponse.json( + { error: "Client non trouvé" }, + { status: 404 } + ); + } + + // Retourner uniquement les données nécessaires (pas le token ni l'ID) + return NextResponse.json({ + bungalowNumber: client.bungalowNumber, + wifiName: client.wifiName, + wifiPassword: client.wifiPassword, + gerantMessage: client.gerantMessage, + }); + } catch (error) { + console.error("Erreur GET client par token:", error); + return NextResponse.json( + { error: "Erreur serveur" }, + { status: 500 } + ); + } +} + diff --git a/data/.gitkeep b/data/.gitkeep new file mode 100644 index 0000000..2ec7180 --- /dev/null +++ b/data/.gitkeep @@ -0,0 +1,2 @@ +# Ce dossier contiendra clients.json (ignoré par git) + diff --git a/next.config.server.js b/next.config.server.js new file mode 100644 index 0000000..f8e83c5 --- /dev/null +++ b/next.config.server.js @@ -0,0 +1,19 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + swcMinify: true, + // Mode serveur (pas d'export statique) + // Les API routes fonctionnent normalement + compress: true, + poweredByHeader: false, + images: { + formats: ["image/avif", "image/webp"], + minimumCacheTTL: 60, + }, + experimental: { + optimizePackageImports: ["lucide-react"], + }, +}; + +module.exports = nextConfig; + diff --git a/scripts/build-server.sh b/scripts/build-server.sh new file mode 100755 index 0000000..69402be --- /dev/null +++ b/scripts/build-server.sh @@ -0,0 +1,190 @@ +#!/bin/bash + +set -e + +echo "🚀 Build de l'admin pour déploiement serveur" +echo "============================================" +echo "" + +# Couleurs +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +NC='\033[0m' + +# Vérifier qu'on est dans le bon répertoire +if [ ! -f "package.json" ]; then + echo "❌ Erreur: Exécutez ce script depuis la racine du projet" + exit 1 +fi + +# Étape 1: Configuration Next.js pour serveur +echo -e "${BLUE}📦 Étape 1/4: Configuration Next.js${NC}" +if [ -f "next.config.js" ]; then + cp next.config.js next.config.js.backup +fi +cp next.config.server.js next.config.js +echo -e "${GREEN}✅ Configuration serveur activée${NC}" +echo "" + +# Étape 2: Installation des dépendances +echo -e "${BLUE}📦 Étape 2/4: Vérification des dépendances${NC}" +if [ ! -d "node_modules" ]; then + echo "Installation des dépendances..." + npm install +else + echo "Dépendances déjà installées" +fi +echo -e "${GREEN}✅ Dépendances OK${NC}" +echo "" + +# Étape 3: Build Next.js +echo -e "${BLUE}📦 Étape 3/4: Build de production${NC}" +rm -rf .next + +if npm run build; then + echo -e "${GREEN}✅ Build réussi${NC}" +else + echo -e "${YELLOW}⚠️ Build avec avertissements${NC}" +fi +echo "" + +# Étape 4: Préparer les fichiers pour le déploiement +echo -e "${BLUE}📦 Étape 4/4: Préparation du déploiement${NC}" + +# Créer le dossier de déploiement +mkdir -p deploy +rm -rf deploy/* + +# Copier les fichiers nécessaires +echo "Copie des fichiers..." +cp -r .next deploy/ +cp -r public deploy/ +cp package.json deploy/ +cp package-lock.json deploy/ 2>/dev/null || true +cp next.config.server.js deploy/next.config.js + +# Créer le dossier data +mkdir -p deploy/data +cp data/.gitkeep deploy/data/ 2>/dev/null || true + +# Créer un fichier .env.example +cat > deploy/.env.example << 'EOF' +# Mot de passe admin +ADMIN_PASSWORD=votre_mot_de_passe_securise + +# Port (optionnel, par défaut 3000) +PORT=3000 +EOF + +# Créer un fichier de lancement +cat > deploy/start.sh << 'EOF' +#!/bin/bash +# Script de lancement pour le serveur + +# Installer les dépendances production uniquement +npm ci --production + +# Démarrer le serveur +npm start +EOF + +chmod +x deploy/start.sh + +# Créer un fichier README pour le déploiement +cat > deploy/DEPLOY.md << 'EOF' +# Déploiement sur serveur + +## Étapes de déploiement + +1. **Transférer les fichiers sur le serveur** + ```bash + rsync -avz --delete deploy/ user@marama.syoul.fr:/var/www/pension-admin/ + ``` + +2. **Se connecter au serveur** + ```bash + ssh user@marama.syoul.fr + cd /var/www/pension-admin + ``` + +3. **Configurer les variables d'environnement** + ```bash + cp .env.example .env + nano .env # Modifier ADMIN_PASSWORD + ``` + +4. **Installer et démarrer** + ```bash + chmod +x start.sh + ./start.sh + ``` + +5. **Avec PM2 (recommandé)** + ```bash + npm install -g pm2 + pm2 start npm --name "pension-admin" -- start + pm2 save + pm2 startup + ``` + +## Configuration Nginx (reverse proxy) + +```nginx +server { + listen 80; + server_name admin.marama.syoul.fr; + + location / { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } +} +``` + +## SSL avec Certbot + +```bash +sudo certbot --nginx -d admin.marama.syoul.fr +``` +EOF + +echo -e "${GREEN}✅ Fichiers prêts dans deploy/${NC}" +echo "" + +# Restaurer la config originale +if [ -f "next.config.js.backup" ]; then + mv next.config.js.backup next.config.js +fi + +# Afficher le résumé +DEPLOY_SIZE=$(du -sh deploy | cut -f1) + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}✅ BUILD SERVEUR TERMINÉ !${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" +echo -e "📁 Dossier de déploiement: ${BLUE}deploy/${NC}" +echo -e "📊 Taille: ${BLUE}${DEPLOY_SIZE}${NC}" +echo "" +echo -e "${YELLOW}📤 Prochaines étapes:${NC}" +echo "" +echo "1. Transférer sur le serveur:" +echo " ${BLUE}rsync -avz --delete deploy/ user@marama.syoul.fr:/var/www/pension-admin/${NC}" +echo "" +echo "2. Sur le serveur:" +echo " ${BLUE}cd /var/www/pension-admin${NC}" +echo " ${BLUE}./start.sh${NC}" +echo "" +echo "3. Configurer le mot de passe admin:" +echo " ${BLUE}nano .env${NC}" +echo " ${BLUE}ADMIN_PASSWORD=votre_mot_de_passe${NC}" +echo "" +echo -e "${YELLOW}📖 Documentation complète: deploy/DEPLOY.md${NC}" +echo "" +