From ce81084cdc491d25c4089b13b1cc3d677e6d3738 Mon Sep 17 00:00:00 2001 From: Pierre Date: Tue, 6 Oct 2026 07:06:49 +0000 Subject: [PATCH] =?UTF-8?q?Ajout=20des=20fonctions=20de=20cr=C3=A9ation=20?= =?UTF-8?q?auto=20d'un=20bateau=20avec=20timer=20toutes=20les=205min?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/definitions.php | 14 ++- config/fonctions_traductions.php | 2 +- generer_relais_live.php | 148 +++++++++++++++++++++++++++ html/bateaux.php | 42 +++++++- html/format_bateaux.css | 20 ++++ systemd/ship2net-relais-live.service | 12 +++ systemd/ship2net-relais-live.timer | 10 ++ 7 files changed, 245 insertions(+), 3 deletions(-) create mode 100644 generer_relais_live.php create mode 100644 systemd/ship2net-relais-live.service create mode 100644 systemd/ship2net-relais-live.timer diff --git a/config/definitions.php b/config/definitions.php index 8c18f8e..43489d9 100644 --- a/config/definitions.php +++ b/config/definitions.php @@ -124,6 +124,18 @@ define("ETAT_CARTE_EN_PANNE", 2); define("ETAT_CARTE_REBUT", 3); - +//===================================================================== +// Types et Sous_types d'historique : +define ("SYSTEME", 1000); +define ("SYSTEME_REINIT", 1001); // Réinitialisation de l'application +define ("SYSTEME_ERREUR_MYSQL", 1002); +define ("SYSTEME_CONNEXION_USER", 1003); +define ("SYSTEME_REINIT_MDP_USER", 1004); +define ("SYSTEME_REBOOT", 1005); // Reboot du système +define ("SYSTEME_ARRET", 1006); // Arrêt du système (Shutdown -h) +define ("SYSTEME_REINIT_BOX", 1007); +define ("SYSTEME_TIME_OUT_CONNEXION", 1008); +define ("SYSTEME_REINIT_TEIOT", 1009); +define ("SYSTEME_DECONNEXION_USER", 1010); diff --git a/config/fonctions_traductions.php b/config/fonctions_traductions.php index 85a18dc..cbe25a7 100644 --- a/config/fonctions_traductions.php +++ b/config/fonctions_traductions.php @@ -102,7 +102,7 @@ function chargement_langue() Ouverture_bdd(); // Connexion à MySql - $requete = "SELECT Fichier, Page, Bloc, Nom, (SELECT Texte FROM TB_Textes_traduction WHERE ID_LANGUAGES=(SELECT ID FROM TB_Langues WHERE Prefix=?) AND ID_TYPE = ".TYPE_UTILISATEUR_PROFESSIONNEL." AND TB_Textes_traduction.ID_TEXTES=TB_Textes_acces.ID_TEXTES) AS Libelle FROM TB_Textes_acces WHERE Validite='1'"; + $requete = "SELECT Fichier, Page, Bloc, Nom, (SELECT Texte FROM TB_Textes_traduction WHERE ID_LANGUAGES=(SELECT ID FROM TB_Langues WHERE Prefix=?) AND ID_TYPE = 2 AND TB_Textes_traduction.ID_TEXTES=TB_Textes_acces.ID_TEXTES) AS Libelle FROM TB_Textes_acces WHERE Validite='1'"; $reponse = Requete_preparee_bdd($requete, array( array('s', $_SESSION['langue'])), diff --git a/generer_relais_live.php b/generer_relais_live.php new file mode 100644 index 0000000..7a12cbe --- /dev/null +++ b/generer_relais_live.php @@ -0,0 +1,148 @@ +.yml : https://b.ship2net.fr relayé vers ce +// module, avec ouverture de session (/_auth) et vérification de la session à chaque requête (ForwardAuth) - cf +// live-commun.yml. Supprime les relais des bateaux qui ne remplissent plus ces conditions (bateau dévalidé, +// affectation clôturée, adresse retirée). +// +// Ajouter un bateau au Live ne demande donc rien d'autre que son affectation (lot 4) : ni DNS, ni certificat +// (certificat générique *.ship2net.fr), ni fichier à écrire à la main. Une réaffectation de module change la +// destination du relais, pas l'adresse du bateau. +// +// Principes : +// - seuls les fichiers "bateau-.yml" sont gérés : aucun autre fichier du dossier n'est jamais touché ; +// - un fichier n'est réécrit que si son contenu change (Traefik recharge sa configuration à chaque écriture) ; +// - écriture atomique (fichier temporaire puis renommage) : Traefik ne lit jamais un fichier à moitié écrit ; +// - l'adresse NetBird est validée (adresse IPv4 stricte) avant d'être écrite dans la configuration de Traefik. +// ============================================================================ + +chdir("/var/www/html"); +require_once __DIR__ . "/config/config.php"; +require_once __DIR__ . "/config/fonctions_bdd.php"; + +const REPERTOIRE_RELAIS = "/opt/netbird/dynamic"; +const MOTIF_FICHIER_RELAIS = '/^bateau-([1-9][0-9]*)\.yml$/'; + + +// ---------------------------------------------------------------------------- +// Contenu du fichier de relais d'un bateau (modèle validé sur le bateau 1, étape 2d). +// ---------------------------------------------------------------------------- +function Contenu_relais($id_bateau, $adresse_netbird) +{ + $hote = "b".$id_bateau.".".URL_SITE; + return <<query($requete); + $attendus = array(); + while ($ligne = $resultat->fetch_assoc()) + { + $adresse = trim($ligne['Adresse_netbird']); + if (filter_var($adresse, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false) + { + echo " ! bateau {$ligne['Cle_primaire']} : adresse NetBird '{$adresse}' invalide - relais non généré.\n"; + continue; + } + $attendus["bateau-".(int)$ligne['Cle_primaire'].".yml"] = Contenu_relais((int)$ligne['Cle_primaire'], $adresse); + } + $resultat->free(); + Fermeture_bdd(); + + $nb_crees = $nb_modifies = $nb_supprimes = $nb_inchanges = 0; + + // Création / mise à jour : + foreach ($attendus as $nom => $contenu) + { + $chemin = REPERTOIRE_RELAIS."/".$nom; + $existant = is_file($chemin) ? file_get_contents($chemin) : null; + if ($existant === $contenu) { $nb_inchanges++; continue; } + if (!Ecrire_atomique($chemin, $contenu)) { echo " ! écriture impossible : {$chemin}\n"; continue; } + if ($existant === null) { $nb_crees++; echo " + {$nom} créé\n"; } + else { $nb_modifies++; echo " ~ {$nom} mis à jour\n"; } + } + + // Suppression des relais qui ne correspondent plus à un bateau éligible (fichiers "bateau-.yml" seulement) : + foreach (scandir(REPERTOIRE_RELAIS) as $nom) + { + if (preg_match(MOTIF_FICHIER_RELAIS, $nom) !== 1 || isset($attendus[$nom])) continue; + if (unlink(REPERTOIRE_RELAIS."/".$nom)) { $nb_supprimes++; echo " - {$nom} supprimé\n"; } + else echo " ! suppression impossible : {$nom}\n"; + } + + echo count($attendus)." bateau(x) éligible(s) : {$nb_crees} créé(s), {$nb_modifies} mis à jour, {$nb_supprimes} supprimé(s), {$nb_inchanges} inchangé(s).\n"; + return 0; +} + +exit(Generer_relais_live()); diff --git a/html/bateaux.php b/html/bateaux.php index 0f5f9e9..a0f9f44 100644 --- a/html/bateaux.php +++ b/html/bateaux.php @@ -388,7 +388,9 @@ $requete .= ' TB_Bateaux.Date_derniere_maj AS Date_derniere_maj, '; $requete = 'SELECT *, '; $requete .= ' (SELECT Adresse_mac FROM TB_Affectations_bateaux WHERE ID_Bateau=TB_Bateaux.Cle_primaire AND Date_fin IS NULL) AS Adresse_mac, '; - $requete .= ' (SELECT Version_soft FROM TB_Generations_bateaux WHERE ID_Bateau=TB_Bateaux.Cle_primaire AND Date_fin IS NULL) AS version '; + $requete .= ' (SELECT Version_soft FROM TB_Generations_bateaux WHERE ID_Bateau=TB_Bateaux.Cle_primaire AND Date_fin IS NULL) AS version, '; + // Adresse NetBird du module affecté (Live distant possible seulement si elle est connue - synthèse §49) : + $requete .= ' (SELECT C.Adresse_netbird FROM TB_Affectations_bateaux A JOIN TB_Cartes C ON C.Adresse_mac=A.Adresse_mac WHERE A.ID_Bateau=TB_Bateaux.Cle_primaire AND A.Date_fin IS NULL) AS Adresse_netbird '; $requete .= ' FROM TB_Bateaux WHERE Cle_primaire=? '; //echo "Requête = [ ".$requete."; ]
"; $reponse = Requete_preparee_bdd($requete, @@ -491,6 +493,31 @@ $requete .= ' TB_Bateaux.Date_derniere_maj AS Date_derniere_maj, '; $est_administrateur = ($_SESSION['shipmember']['Niveau_acces'] >= NIVEAU_ACCES_MINI_ADMINISTRATEUR); $peut_inviter = ($est_administrateur || $acces_profil['Creation_profils'] == 1); + //------------------------------------------------------------------------------------------------------------ + // Live distant (synthèse §49) : création d'un ticket (aléatoire, 60 s, usage unique, lié à l'utilisateur et au + // bateau) puis redirection vers https://b.ship2net.fr/_auth?ticket=... - le vérificateur du VPS l'échange + // contre une session (cookie propre au sous-domaine du bateau). Seule l'empreinte du ticket est enregistrée. + // Droit : arriver jusqu'ici suffit (administrateur, ou accès actif à CE bateau - contrôlé plus haut) ; le + // vérificateur revérifie de toute façon à l'ouverture de la session puis à chaque requête. + $live_possible = ($bateau['Adresse_netbird'] ?? '') != ''; + if (isset($_POST['ouvrir_live'])) + { + if (!$live_possible) + { + $_SESSION['Bateaux']['flash_erreur'] = t("Aucun module ShipBase joignable n'est affecté à ce bateau : le Live est indisponible."); + Fermeture_bdd(); + header('Location: bateaux.php'); + exit(); + } + $ticket = bin2hex(random_bytes(32)); + Requete_preparee_bdd("INSERT INTO TB_Tickets_live (Empreinte, ID_Shipmember, ID_Bateau, Expiration) VALUES (?, ?, ?, NOW() + INTERVAL 60 SECOND)", + array(array('s', hash('sha256', $ticket)), array('i', $_SESSION['shipmember']['Cle_primaire']), array('i', $bateau['Cle_primaire'])), + __FILE__, __LINE__); + Fermeture_bdd(); + header('Location: https://b'.(int)$bateau['Cle_primaire'].'.'.URL_SITE.'/_auth?ticket='.$ticket, true, 303); + exit(); + } + if (isset($_POST['creer_invitation'])) { if (!$peut_inviter) $_SESSION['Bateaux']['flash_erreur'] = t("Vous n'avez pas le droit d'inviter sur ce bateau."); @@ -768,6 +795,19 @@ $requete .= ' TB_Bateaux.Date_derniere_maj AS Date_derniere_maj, '; echo ''; } + //-------------------------------- + // Bouton Live (synthèse §49) - formulaire POST avec jeton (un navigateur qui pré-charge les liens ne crée pas + // de tickets), ouvert dans un nouvel onglet : + echo '
'; + if ($live_possible) + { + echo '
'.Champ_jeton_csrf(); + echo ''; + echo '
'; + } + else echo ''.t("Live indisponible").''; + echo '
'; + echo '
'.t("Dernières données reçues").' : '.$bateau['Date_derniere_maj'].' [UTC]
'; echo ''; //-------------------------------- diff --git a/html/format_bateaux.css b/html/format_bateaux.css index 4bfb077..67cb12a 100644 --- a/html/format_bateaux.css +++ b/html/format_bateaux.css @@ -32,6 +32,26 @@ Fichier bateaux.php background-color: var(--bleu_koriolan); color: var(--bleu_koriolan_fonce); } +/* Bouton Live (synthèse §49) : à gauche du bouton "Gestion des accès" */ +#Bateaux .entete_bateau .bouton_live { + float: right; + margin-right: 15px; +} +#Bateaux .entete_bateau .bouton_live button { + padding: 5px 25px; + line-height: 1.5em; + font-weight: bold; +} +#Bateaux .entete_bateau .bouton_live button::before { + content: "\25CF\00A0"; /* point "en direct" */ + color: #e53935; +} +#Bateaux .entete_bateau .bouton_live .live_indisponible { + display: inline-block; + padding: 5px 10px; + color: var(--gris_pale); + font-style: italic; +} #Bateaux .boutons_haut_de_page { diff --git a/systemd/ship2net-relais-live.service b/systemd/ship2net-relais-live.service new file mode 100644 index 0000000..2d6af67 --- /dev/null +++ b/systemd/ship2net-relais-live.service @@ -0,0 +1,12 @@ +[Unit] +Description=Ship2net - génération des relais Traefik du Live distant (un par bateau) +After=network-online.target docker.service +Wants=network-online.target + +[Service] +Type=oneshot +User=koriolan +Group=koriolan +ExecStart=/usr/bin/php /var/www/generer_relais_live.php +Nice=10 +TimeoutStartSec=5min diff --git a/systemd/ship2net-relais-live.timer b/systemd/ship2net-relais-live.timer new file mode 100644 index 0000000..507a566 --- /dev/null +++ b/systemd/ship2net-relais-live.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Ship2net - relais du Live distant toutes les 5 minutes + +[Timer] +OnBootSec=1min +OnCalendar=*:0/5 +Persistent=true + +[Install] +WantedBy=timers.target