33800 Docs

← Retour

Proposition : Architecture Résilience O2switch

Date : 15-01-2026 (MAJ 15-01-2026 02:00) Status : IMPLEMENTEE Objectif : Maximiser l'utilisation d'O2switch pour la résilience de l'infrastructure 33800


Contexte

Ressources O2switch (incluses dans l'abonnement)

Ressource Limite Usage actuel
Stockage NVMe Illimité (3.4 To dispo) ~650 Mo
PostgreSQL Illimité 2 BDD
MySQL Illimité 2 BDD
Redis Instance privée Non activé
Memcached Instance privée Non activé
SMTP Illimité 1 adresse
Bande passante Illimitée -
SSH Accès complet Actif
Cron Illimité -

Contraintes mesurées

Métrique Valeur Impact
Bande passante upload ~7 Mbps (~850 Ko/s) Pas de sync temps réel
Latence Variable Pas de haute dispo

Ce que O2switch peut faire

Usage Viable Raison
Backup BDD (pg_dump) Async, toutes les 6h
Backup GitLab Async, toutes les 6h
Replica temps réel Bande passante insuffisante
CDN/Assets Fichiers statiques
Dashboard externe HTML généré localement, push
Webhooks proxy Masque IP locale
Status page Monitoring externe
SMTP fallback Backup email
Stockage buffer Gros fichiers temporaires

Conclusion : O2switch = backup froid / résilience, pas haute disponibilité.


Architecture Cible

┌─────────────────────────────────────────────────────────────────────────┐
│                         INFRA LOCALE (PVE)                              │
│                                                                          │
│   ┌──────────────┐   ┌──────────────┐   ┌──────────────┐                │
│   │  Supabase    │   │   GitLab     │   │   stock_8to  │                │
│   │  PostgreSQL  │   │   (155)      │   │   (7.2 To)   │                │
│   │   :5433      │   │   :80        │   │              │                │
│   └──────┬───────┘   └──────┬───────┘   └──────┬───────┘                │
│          │                  │                  │                         │
│   ┌──────┴───────┐   ┌──────┴───────┐   ┌──────┴───────┐                │
│   │  Dashboard   │   │   Claude     │   │  Monitoring  │                │
│   │  collect.sh  │   │ propositions │   │  generate.sh │                │
│   └──────┬───────┘   └──────┬───────┘   └──────┬───────┘                │
│          │                  │                  │                         │
└──────────┼──────────────────┼──────────────────┼─────────────────────────┘
           │                  │                  │
           │ Git push 1h     │ Git push         │ rsync 2h
           │ (HTML)          │ (on commit)      │ (configs)
           ▼                  ▼                  ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                    O2SWITCH (BACKUP FROID / RÉSILIENCE)                 │
│                                                                          │
│   ┌──────────────┐   ┌──────────────┐   ┌──────────────┐                │
│   │  DASHBOARD   │   │   CLAUDE     │   │   BACKUP     │                │
│   │  externe     │   │  Viewer MD   │   │  configs +   │                │
│   │  (HTML)      │   │  (PHP)       │   │  BDD + GitLab│                │
│   └──────────────┘   └──────────────┘   └──────────────┘                │
│                             │                                            │
│                      .htaccess IP                                        │
│                                                                          │
│   ┌──────────────┐   ┌──────────────┐   ┌──────────────┐                │
│   │    SMTP      │   │   Webhooks   │   │     CDN      │                │
│   │   BACKUP     │   │   PROXY      │   │   ASSETS     │                │
│   │ @nowhere84   │   │  (masque IP) │   │  publics     │                │
│   └──────────────┘   └──────────────┘   └──────────────┘                │
│                                                                          │
│   ┌──────────────┐   ┌──────────────┐                                   │
│   │    CRON      │   │   STATUS     │                                   │
│   │   EXTERNE    │   │   PAGE       │                                   │
│   │  monitoring  │   │   publique   │                                   │
│   └──────────────┘   └──────────────┘                                   │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Plan d'implémentation

Phase 1 : Fondations (Jour 1)

1.1 Clé SSH pour authentification sans mot de passe

# Sur PVE - Générer clé si pas existante
ssh-keygen -t ed25519 -f ~/.ssh/o2switch_key -N ""

# Copier la clé publique sur O2switch
sshpass -p 'PASSWORD' ssh-copy-id -i ~/.ssh/o2switch_key.pub deas8499@yellow.o2switch.net

# Test
ssh -i ~/.ssh/o2switch_key deas8499@yellow.o2switch.net "echo OK"

1.2 Structure répertoires O2switch

ssh -i ~/.ssh/o2switch_key deas8499@yellow.o2switch.net "
mkdir -p ~/backup/{postgres,gitlab,configs,claude}
mkdir -p ~/public_html/dashboard
mkdir -p ~/public_html/cdn
mkdir -p ~/public_html/webhooks
mkdir -p ~/public_html/status
"

1.3 SMTP Backup dans notif-logger

Fichier : Modifier notif-logger pour ajouter fallback SMTP

// Ajouter SMTP O2switch comme fallback
smtp: {
  primary: { /* config actuelle */ },
  fallback: {
    host: 'yellow.o2switch.net',
    port: 465,
    secure: true,
    auth: {
      user: '2u@nowhere84.com',
      pass: process.env.O2SWITCH_SMTP_PASSWORD
    }
  }
}

Phase 2 : Dashboard Externalisé via Git (Jour 1-2)

2.1 Workflow Git

LOCAL                                    O2SWITCH
──────                                   ────────
collect.sh (cron 1h)
    │
    ▼
generate.sh
    │
    ▼
git add + commit + push  ──► Git ──►  post-receive hook
                                              │
                                        auto-deploy
                                              │
                                              ▼
                               ~/public_html/dashboard/
                                              │
                                        .htaccess (IP)
                                              │
                                              ▼
                              https://dashboard.nowhere84.com

2.2 Créer repo bare sur O2switch

ssh -i ~/.ssh/o2switch_key deas8499@yellow.o2switch.net "
mkdir -p ~/repositories
cd ~/repositories
git init --bare dashboard.git
"

2.3 Hook post-receive (auto-deploy dashboard)

Fichier O2switch : ~/repositories/dashboard.git/hooks/post-receive

#!/bin/bash
# Auto-deploy dashboard vers public_html

TARGET="/home/deas8499/public_html/dashboard"
GIT_DIR="/home/deas8499/repositories/dashboard.git"

echo "Deploying dashboard..."
git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f main

# Copier .htaccess s'il n'existe pas
if [ ! -f "$TARGET/.htaccess" ]; then
    cp /home/deas8499/config/dashboard.htaccess $TARGET/.htaccess
fi

echo "Dashboard deployed: $(date)"
# Rendre exécutable et créer config
ssh -i ~/.ssh/o2switch_key deas8499@yellow.o2switch.net "
chmod +x ~/repositories/dashboard.git/hooks/post-receive
mkdir -p ~/config
"

2.4 Initialiser repo monitoring local

# Sur PVE - Dans le dossier monitoring
cd /stock_8to/33800-stack/monitoring

# Si pas déjà un repo git
git init
echo '.htaccess' >> .gitignore  # htaccess géré séparément

# Ajouter remote O2switch
git remote add o2switch deas8499@yellow.o2switch.net:~/repositories/dashboard.git

# Commit initial
git add -A
git commit -m "Initial: dashboard monitoring"
git push -u o2switch main

2.5 Modifier generate.sh pour auto-push

Ajouter à la fin de /stock_8to/33800-stack/monitoring/generate.sh :

# Auto-deploy vers O2switch via Git
cd /stock_8to/33800-stack/monitoring
git add -A
git commit -m "Update: $(date +%Y-%m-%d_%H:%M)" 2>/dev/null || true
git push o2switch main 2>/dev/null &

2.6 Sécurité par IP (.htaccess)

Fichier O2switch : ~/config/dashboard.htaccess (copié automatiquement par le hook)

# Restriction par IP
Order Deny,Allow
Deny from all

# IP Box (fixe)
Allow from 82.65.119.221

# IP Mobile/Backup (ajouter selon besoin)
# Allow from x.x.x.x

# Basic Auth en fallback (si IP change)
AuthType Basic
AuthName "Dashboard 33800"
AuthUserFile /home/deas8499/.htpasswd
Require valid-user

# Autoriser si IP OK OU si auth OK
Satisfy any

# Cache navigateur
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType text/html "access plus 5 minutes"
    ExpiresByType text/css "access plus 1 hour"
    ExpiresByType application/javascript "access plus 1 hour"
</IfModule>

2.7 Créer htpasswd

ssh -i ~/.ssh/o2switch_key deas8499@yellow.o2switch.net "
htpasswd -cb ~/.htpasswd gouroubleu 'MOT_DE_PASSE_DASHBOARD'
"

Phase 3 : Backup Configs + BDD (Jour 2)

3.1 Script sync configs

Fichier : /stock_8to/33800-stack/scripts/backup/sync-configs-o2switch.sh

#!/bin/bash
# Sync configs critiques vers O2switch

O2_KEY="/root/.ssh/o2switch_key"
O2_HOST="deas8499@yellow.o2switch.net"
O2_PATH="~/backup/configs"

rsync -avz -e "ssh -i $O2_KEY" --delete \
  /stock_8to/33800-stack/claude/ \
  /stock_8to/33800-stack/docker/secrets/ \
  /stock_8to/33800-stack/docker/stacks/*/docker-compose.yml \
  /stock_8to/33800-stack/scripts/ \
  /stock_8to/33800-stack/monitoring/config.json \
  $O2_HOST:$O2_PATH/

echo "$(date): Configs synced to O2switch" >> /var/log/o2switch-sync.log

Cron PVE : 0 */2 * * * /stock_8to/33800-stack/scripts/backup/sync-configs-o2switch.sh

3.2 Script backup PostgreSQL

Fichier : /stock_8to/33800-stack/scripts/backup/pg-to-o2switch.sh

#!/bin/bash
# Dump PostgreSQL Supabase vers O2switch

O2_KEY="/root/.ssh/o2switch_key"
O2_HOST="deas8499@yellow.o2switch.net"
O2_PATH="~/backup/postgres"
TIMESTAMP=$(date +%Y%m%d_%H%M)

# Dump (depuis prod-portainer)
ssh gouroubleu@192.168.1.12 "
  docker exec supabase-db-prod pg_dump -U postgres postgres
" | gzip > /tmp/supabase_$TIMESTAMP.sql.gz

# Upload
scp -i $O2_KEY /tmp/supabase_$TIMESTAMP.sql.gz $O2_HOST:$O2_PATH/

# Cleanup local
rm -f /tmp/supabase_$TIMESTAMP.sql.gz

# Garder 14 derniers dumps sur O2switch (3.5 jours à 6h)
ssh -i $O2_KEY $O2_HOST "cd ~/backup/postgres && ls -t *.gz 2>/dev/null | tail -n +15 | xargs -r rm"

echo "$(date): PostgreSQL backup to O2switch" >> /var/log/o2switch-sync.log

Cron PVE : 0 */6 * * * /stock_8to/33800-stack/scripts/backup/pg-to-o2switch.sh

3.3 Script backup GitLab

Fichier : /stock_8to/33800-stack/scripts/backup/gitlab-to-o2switch.sh

#!/bin/bash
# Sync GitLab backups vers O2switch
# GitLab crée des backups toutes les 6h dans /var/opt/gitlab/backups/

O2_KEY="/root/.ssh/o2switch_key"
O2_HOST="deas8499@yellow.o2switch.net"
O2_PATH="~/backup/gitlab"
GITLAB_HOST="gouroubleu@192.168.1.196"
GITLAB_BACKUP_DIR="/var/opt/gitlab/backups"

# Récupérer le dernier backup GitLab
LATEST=$(ssh $GITLAB_HOST "ls -t $GITLAB_BACKUP_DIR/*.tar 2>/dev/null | head -1")

if [ -n "$LATEST" ]; then
  FILENAME=$(basename "$LATEST")

  # Copier vers PVE puis vers O2switch (évite accès direct GitLab→O2switch)
  scp $GITLAB_HOST:$LATEST /tmp/$FILENAME
  scp -i $O2_KEY /tmp/$FILENAME $O2_HOST:$O2_PATH/
  rm -f /tmp/$FILENAME

  echo "$(date): GitLab backup synced: $FILENAME" >> /var/log/o2switch-sync.log
fi

# Garder 7 derniers backups sur O2switch (1 semaine)
ssh -i $O2_KEY $O2_HOST "cd ~/backup/gitlab && ls -t *.tar 2>/dev/null | tail -n +8 | xargs -r rm"

Cron PVE : 30 */6 * * * /stock_8to/33800-stack/scripts/backup/gitlab-to-o2switch.sh

Note : Exécuté 30 min après le backup GitLab (0 */6) pour laisser le temps à la création.


Phase 4 : CDN Assets (Jour 2)

4.1 Structure

~/public_html/cdn/
├── images/
├── fonts/
├── js/
├── css/
└── downloads/

4.2 Script sync CDN

#!/bin/bash
# Sync assets publics vers CDN O2switch

O2_KEY="/root/.ssh/o2switch_key"
O2_HOST="deas8499@yellow.o2switch.net"

rsync -avz -e "ssh -i $O2_KEY" --delete \
  /stock_8to/33800-stack/public-assets/ \
  $O2_HOST:~/public_html/cdn/

URL : https://cdn.nowhere84.com/


Phase 5 : Webhooks Proxy (Jour 3)

5.1 Receiver PHP

Fichier O2switch : ~/public_html/webhooks/receive.php

<?php
// Webhook proxy - forwarde vers infra locale sans exposer IP

$local_endpoint = 'http://82.65.119.221:5400/webhooks/incoming';
$payload = file_get_contents('php://input');

// Log
$log = date('Y-m-d H:i:s') . " - " . $_SERVER['REQUEST_URI'] . " - " . strlen($payload) . " bytes\n";
file_put_contents('/home/deas8499/logs/webhooks.log', $log, FILE_APPEND);

// Forward
$ch = curl_init($local_endpoint);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'X-Forwarded-From: o2switch',
        'X-Webhook-Source: ' . ($_SERVER['HTTP_X_WEBHOOK_SOURCE'] ?? 'unknown')
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30
]);

$response = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

http_response_code($code ?: 200);
echo $response ?: '{"status":"forwarded"}';

URL : https://nowhere84.com/webhooks/receive.php


Phase 6 : Status Page Externe (Jour 3)

6.1 Check services depuis O2switch

Fichier O2switch : ~/public_html/status/index.php

<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');

$services = [
    'nginx' => ['url' => 'http://82.65.119.221:80', 'name' => 'Reverse Proxy'],
    'portainer' => ['url' => 'http://82.65.119.221:9443', 'name' => 'Docker PROD'],
    'supabase' => ['url' => 'http://82.65.119.221:8200', 'name' => 'Supabase API'],
    'ntfy' => ['url' => 'http://82.65.119.221:8080', 'name' => 'Notifications'],
    'grafana' => ['url' => 'http://82.65.119.221:3000', 'name' => 'Monitoring']
];

$results = [];
$all_up = true;

foreach ($services as $id => $svc) {
    $start = microtime(true);
    $ch = curl_init($svc['url']);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 5,
        CURLOPT_NOBODY => true,
        CURLOPT_SSL_VERIFYPEER => false
    ]);
    curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $latency = round((microtime(true) - $start) * 1000);
    curl_close($ch);

    $up = $code >= 200 && $code < 400;
    if (!$up) $all_up = false;

    $results[$id] = [
        'name' => $svc['name'],
        'status' => $up ? 'up' : 'down',
        'code' => $code,
        'latency_ms' => $latency
    ];
}

echo json_encode([
    'timestamp' => date('c'),
    'overall' => $all_up ? 'operational' : 'degraded',
    'services' => $results
], JSON_PRETTY_PRINT);

URL : https://status.nowhere84.com/ (créer sous-domaine)


Phase 7 : Cron Monitoring Externe (Jour 3)

7.1 Health check + alertes

Cron O2switch : */5 * * * *

#!/bin/bash
# /home/deas8499/scripts/healthcheck.sh

SERVICES="http://82.65.119.221:80 http://82.65.119.221:9443 http://82.65.119.221:8080"
ALERT_URL="https://ntfy.33800.nowhere84.com/infra-alerts"
LOG="/home/deas8499/logs/healthcheck.log"

for url in $SERVICES; do
    status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 -k $url)
    if [ "$status" != "200" ] && [ "$status" != "301" ] && [ "$status" != "302" ]; then
        echo "$(date): DOWN $url (code: $status)" >> $LOG
        curl -s -d "🔴 Service DOWN: $url (code: $status)" $ALERT_URL
    fi
done

Phase 8 : Git + Viewer Markdown Claude (Jour 4)

8.1 Initialiser repo Git pour claude/

# Sur PVE - Initialiser le repo
cd /stock_8to/33800-stack/claude
git init
git add -A
git commit -m "Initial: propositions, tasks, backlog"

# Créer repo sur GitLab (via API ou UI)
# URL: https://gitlab.33800.nowhere84.com/gouroubleu/claude-docs

# Ajouter remotes
git remote add origin git@gitlab.33800.nowhere84.com:gouroubleu/claude-docs.git
git remote add o2switch deas8499@yellow.o2switch.net:~/repositories/claude-docs.git

# Push initial
git push -u origin main
git push -u o2switch main

8.2 Créer repo bare sur O2switch

ssh -i ~/.ssh/o2switch_key deas8499@yellow.o2switch.net "
mkdir -p ~/repositories
cd ~/repositories
git init --bare claude-docs.git
"

8.3 Hook post-receive (auto-deploy)

Fichier O2switch : ~/repositories/claude-docs.git/hooks/post-receive

#!/bin/bash
# Auto-deploy claude-docs vers le dossier backup

TARGET="/home/deas8499/backup/claude"
GIT_DIR="/home/deas8499/repositories/claude-docs.git"

echo "Deploying claude-docs..."
git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f main
echo "Deploy complete: $(date)"
# Rendre exécutable
ssh -i ~/.ssh/o2switch_key deas8499@yellow.o2switch.net "
chmod +x ~/repositories/claude-docs.git/hooks/post-receive
mkdir -p ~/backup/claude
"

8.4 Hook local pour auto-push (optionnel)

Fichier local : /stock_8to/33800-stack/claude/.git/hooks/post-commit

#!/bin/bash
# Auto-push vers GitLab et O2switch après chaque commit

echo "Auto-pushing to remotes..."
git push origin main 2>/dev/null &
git push o2switch main 2>/dev/null &
wait
echo "Push complete"
chmod +x /stock_8to/33800-stack/claude/.git/hooks/post-commit

8.5 Workflow quotidien

# Après création/modification d'une proposition ou task
cd /stock_8to/33800-stack/claude
git add -A
git commit -m "Add: proposition resilience O2switch"
# → Auto-push vers GitLab + O2switch (via hook)
# → Auto-deploy sur O2switch (via post-receive)
# → Visible immédiatement dans le viewer Markdown

8.6 Viewer Markdown

Fichier O2switch : ~/public_html/dashboard/claude.php

<?php
// Viewer Markdown pour propositions/tasks/backlog

require_once 'Parsedown.php'; // Lib Markdown → HTML

$baseDir = '/home/deas8499/backup/claude';
$folders = ['propositions', 'tasks', 'backlog'];
$currentFolder = $_GET['folder'] ?? 'propositions';
$currentFile = $_GET['file'] ?? '';

// Sécurité : valider le dossier
if (!in_array($currentFolder, $folders)) {
    $currentFolder = 'propositions';
}

$folderPath = "$baseDir/$currentFolder";
$files = glob("$folderPath/*.md");

// Trier par date décroissante (nom de fichier)
usort($files, function($a, $b) {
    return filemtime($b) - filemtime($a);
});

// Charger le contenu si fichier sélectionné
$content = '';
$contentHtml = '';
if ($currentFile && file_exists("$folderPath/$currentFile")) {
    $content = file_get_contents("$folderPath/$currentFile");
    $parsedown = new Parsedown();
    $parsedown->setSafeMode(true);
    $contentHtml = $parsedown->text($content);
}
?>
<!DOCTYPE html>
<html>
<head>
    <title>Claude - <?= ucfirst($currentFolder) ?></title>
    <style>
        * { box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: #0d1117; color: #c9d1d9; margin: 0;
            display: flex; height: 100vh;
        }
        .sidebar {
            width: 300px; background: #161b22; padding: 20px;
            overflow-y: auto; border-right: 1px solid #30363d;
        }
        .main { flex: 1; padding: 30px; overflow-y: auto; }
        .tabs { display: flex; gap: 10px; margin-bottom: 20px; }
        .tab {
            padding: 8px 16px; background: #21262d; border-radius: 6px;
            text-decoration: none; color: #c9d1d9;
        }
        .tab.active { background: #238636; color: white; }
        .file-list { list-style: none; padding: 0; }
        .file-list li { margin: 5px 0; }
        .file-list a {
            display: block; padding: 8px 12px; background: #21262d;
            border-radius: 4px; text-decoration: none; color: #58a6ff;
            font-size: 13px; white-space: nowrap; overflow: hidden;
            text-overflow: ellipsis;
        }
        .file-list a:hover, .file-list a.active { background: #30363d; }
        .content {
            background: #161b22; padding: 30px; border-radius: 8px;
            line-height: 1.6;
        }
        .content h1, .content h2, .content h3 { color: #58a6ff; border-bottom: 1px solid #30363d; padding-bottom: 10px; }
        .content code { background: #0d1117; padding: 2px 6px; border-radius: 4px; }
        .content pre { background: #0d1117; padding: 15px; border-radius: 6px; overflow-x: auto; }
        .content table { border-collapse: collapse; width: 100%; }
        .content th, .content td { border: 1px solid #30363d; padding: 8px 12px; text-align: left; }
        .content th { background: #21262d; }
        .badge {
            display: inline-block; padding: 2px 8px; border-radius: 10px;
            font-size: 11px; margin-left: 5px;
        }
        .badge-propositions { background: #1f6feb; }
        .badge-tasks { background: #238636; }
        .badge-backlog { background: #a371f7; }
        .empty { color: #8b949e; font-style: italic; }
    </style>
</head>
<body>
    <div class="sidebar">
        <h2>Claude</h2>

        <div class="tabs">
            <?php foreach ($folders as $f): ?>
                <a href="?folder=<?= $f ?>" class="tab <?= $f === $currentFolder ? 'active' : '' ?>">
                    <?= ucfirst($f) ?>
                    <span class="badge badge-<?= $f ?>"><?= count(glob("$baseDir/$f/*.md")) ?></span>
                </a>
            <?php endforeach; ?>
        </div>

        <ul class="file-list">
            <?php foreach ($files as $file): ?>
                <?php $filename = basename($file); ?>
                <li>
                    <a href="?folder=<?= $currentFolder ?>&file=<?= urlencode($filename) ?>"
                       class="<?= $filename === $currentFile ? 'active' : '' ?>"
                       title="<?= $filename ?>">
                        <?= $filename ?>
                    </a>
                </li>
            <?php endforeach; ?>
            <?php if (empty($files)): ?>
                <li class="empty">Aucun fichier</li>
            <?php endif; ?>
        </ul>
    </div>

    <div class="main">
        <?php if ($contentHtml): ?>
            <div class="content">
                <?= $contentHtml ?>
            </div>
        <?php else: ?>
            <div class="empty">
                <h2>Sélectionnez un fichier</h2>
                <p>Choisissez un fichier dans la liste à gauche pour voir son contenu.</p>
            </div>
        <?php endif; ?>
    </div>
</body>
</html>

8.7 Installer Parsedown (lib Markdown)

ssh -i ~/.ssh/o2switch_key deas8499@yellow.o2switch.net "
cd ~/public_html/dashboard
curl -sL https://raw.githubusercontent.com/erusev/parsedown/master/Parsedown.php -o Parsedown.php
"

Phase 9 : Navigation Dashboard (Jour 4)

9.1 Page index avec liens

Fichier O2switch : ~/public_html/dashboard/index.php

<?php
// Dashboard principal - Hub de navigation
?>
<!DOCTYPE html>
<html>
<head>
    <title>Dashboard 33800</title>
    <meta http-equiv="refresh" content="300"> <!-- Refresh 5min -->
    <style>
        body {
            font-family: -apple-system, sans-serif;
            background: #0d1117; color: #c9d1d9;
            padding: 40px; max-width: 1200px; margin: 0 auto;
        }
        h1 { color: #58a6ff; }
        .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; }
        .card {
            background: #161b22; padding: 20px; border-radius: 8px;
            border: 1px solid #30363d;
        }
        .card h3 { margin-top: 0; color: #58a6ff; }
        .card a { color: #58a6ff; text-decoration: none; }
        .card a:hover { text-decoration: underline; }
        .status { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 8px; }
        .status.up { background: #238636; }
        .status.down { background: #da3633; }
        .timestamp { color: #8b949e; font-size: 12px; }
    </style>
</head>
<body>
    <h1>Dashboard 33800</h1>
    <p class="timestamp">Dernière mise à jour : <?= date('d/m/Y H:i:s') ?></p>

    <div class="grid">
        <div class="card">
            <h3>Monitoring</h3>
            <ul>
                <li><a href="index.html">Dashboard Services</a></li>
                <li><a href="crons.html">Crons & Schedulers</a></li>
                <li><a href="raidz.html">ZFS RAID Status</a></li>
            </ul>
        </div>

        <div class="card">
            <h3>Claude</h3>
            <ul>
                <li><a href="claude.php?folder=propositions">Propositions</a></li>
                <li><a href="claude.php?folder=tasks">Tasks</a></li>
                <li><a href="claude.php?folder=backlog">Backlog</a></li>
            </ul>
        </div>

        <div class="card">
            <h3>Monitoring</h3>
            <ul>
                <li><a href="http://grafana.33800.nowhere84.com" target="_blank">Grafana</a></li>
                <li><a href="/status/">Status API (JSON)</a></li>
            </ul>
        </div>

        <div class="card">
            <h3>Status Externe</h3>
            <ul>
                <li><a href="/status/">API Status (JSON)</a></li>
                <li><a href="https://status.nowhere84.com" target="_blank">Page Status Publique</a></li>
            </ul>
        </div>

        <div class="card">
            <h3>Outils</h3>
            <ul>
                <li><a href="https://portainer.33800.nowhere84.com" target="_blank">Portainer</a></li>
                <li><a href="https://gitlab.33800.nowhere84.com" target="_blank">GitLab</a></li>
                <li><a href="https://ntfy.33800.nowhere84.com" target="_blank">Ntfy</a></li>
            </ul>
        </div>
    </div>
</body>
</html>

Récapitulatif

Composant Source Destination Fréquence Taille estimée
Dashboard generate.sh + Git O2switch (post-receive) 1h ~500 Ko
Claude Docs propositions/tasks/backlog + Git O2switch (post-receive) À chaque commit ~1-5 Mo
Configs scripts/secrets O2switch rsync 2h ~10 Mo
PostgreSQL pg_dump Supabase O2switch 6h ~50-200 Mo
GitLab gitlab-backup .tar O2switch 6h (+30min) ~500 Mo - 2 Go
SMTP fallback O2switch Temps réel -
Webhooks externe Proxy O2switch Temps réel -
CDN assets O2switch Manuel Variable
Status PHP check O2switch Temps réel -
Monitoring Cron O2switch Alertes ntfy 5min -
Viewer MD claude.php O2switch - ~10 Ko

Sécurité

Élément Protection
Dashboard IP whitelist + Basic Auth fallback
SSH Clé ED25519, IP whitelistée
Webhooks Signature vérification optionnelle
Status Public (JSON, pas de données sensibles)
Backups Hors public_html, non accessible web

Domaines à configurer

Sous-domaine Cible Usage
dashboard.nowhere84.com ~/public_html/dashboard Dashboard monitoring
cdn.nowhere84.com ~/public_html/cdn Assets statiques
status.nowhere84.com ~/public_html/status Status page publique

Prochaines étapes

  1. [ ] Valider cette proposition
  2. [ ] Configurer clé SSH (Phase 1)
  3. [ ] Créer structure répertoires O2switch
  4. [ ] Créer repos bare Git : dashboard.git, claude-docs.git
  5. [ ] Configurer hooks post-receive
  6. [ ] Déployer .htaccess + htpasswd
  7. [ ] Configurer sous-domaines (dashboard, cdn, status)
  8. [ ] Déployer scripts sync (configs, pg_dump, gitlab)
  9. [ ] Configurer crons PVE
  10. [ ] Déployer PHP : webhooks, status, claude.php
  11. [ ] Installer Parsedown pour viewer Markdown
  12. [ ] Configurer cron monitoring O2switch
  13. [ ] Ajouter SMTP fallback dans notif-logger
  14. [ ] Tester le tout

Estimation

Phase Contenu Temps
Phase 1 SSH + structure + SMTP 1h
Phase 2 Dashboard Git + .htaccess 1h30
Phase 3 Backup configs + BDD + GitLab 1h30
Phase 4-5 CDN + Webhooks 1h
Phase 6-7 Status + Monitoring cron 1h
Phase 8 Git claude/ + Viewer MD 1h
Phase 9 Navigation dashboard 30min
Total ~7h30

En attente de validation.