Ajout des fonctions de création auto d'un bateau avec timer toutes les 5min

This commit is contained in:
2026-10-06 07:06:49 +00:00
parent 019cd44f98
commit ce81084cdc
7 changed files with 245 additions and 3 deletions
+13 -1
View File
@@ -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);
+1 -1
View File
@@ -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'])),
+148
View File
@@ -0,0 +1,148 @@
<?php
// ============================================================================
// generer_relais_live.php - Génération des relais Traefik du Live distant, un par bateau (synthèse §49, étape 2f).
// Exécuté NATIVEMENT sur le VPS, de façon planifiée (ship2net-relais-live.timer) ou à la main :
// /usr/bin/php /var/www/generer_relais_live.php
//
// Pour chaque bateau valide dont le module affecté (affectation active) a une adresse NetBird connue
// (TB_Cartes.Adresse_netbird), écrit /opt/netbird/dynamic/bateau-<N>.yml : https://b<N>.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-<N>.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 <<<YAML
# ============================================================================
# Live distant du bateau {$id_bateau} - https://{$hote} (synthèse §49)
# FICHIER GÉNÉRÉ AUTOMATIQUEMENT par /var/www/generer_relais_live.php - ne pas modifier à la main
# (il serait réécrit au passage suivant). Module affecté : {$adresse_netbird}
# ============================================================================
http:
routers:
bateau-{$id_bateau}-auth:
rule: "Host(`{$hote}`) && Path(`/_auth`)"
priority: 1000
entrypoints:
- websecure
tls:
certResolver: ovh
domains:
- main: "*.ship2net.fr"
middlewares:
- live-auth-chemin@file
service: live-auth@file
bateau-{$id_bateau}:
rule: "Host(`{$hote}`)"
entrypoints:
- websecure
tls:
certResolver: ovh
domains:
- main: "*.ship2net.fr"
middlewares:
- live-verification@file
service: bateau-{$id_bateau}
services:
bateau-{$id_bateau}:
loadBalancer:
servers:
- url: "http://{$adresse_netbird}:80"
YAML;
}
// ----------------------------------------------------------------------------
// Écriture atomique : fichier temporaire (extension non lue par Traefik) puis renommage.
// ----------------------------------------------------------------------------
function Ecrire_atomique($chemin, $contenu)
{
$temporaire = $chemin.".tmp";
if (file_put_contents($temporaire, $contenu) === false) return false;
chmod($temporaire, 0644);
return rename($temporaire, $chemin);
}
function Generer_relais_live()
{
if (!is_dir(REPERTOIRE_RELAIS) || !is_writable(REPERTOIRE_RELAIS))
{
echo "ERREUR : répertoire ".REPERTOIRE_RELAIS." absent ou non accessible en écriture.\n";
return 1;
}
Ouverture_bdd("127.0.0.1:3307", BDD_USER, BDD_PASSWORD, BDD_DATABASE);
$db = $_SESSION['db'];
// Bateaux éligibles : valides, avec une affectation active vers un module dont l'adresse NetBird est connue.
$requete = "SELECT B.Cle_primaire, C.Adresse_netbird
FROM TB_Bateaux B
JOIN TB_Affectations_bateaux A ON A.ID_Bateau = B.Cle_primaire AND A.Date_fin IS NULL
JOIN TB_Cartes C ON C.Adresse_mac = A.Adresse_mac
WHERE B.Validite = 1 AND C.Adresse_netbird IS NOT NULL AND C.Adresse_netbird <> ''";
$resultat = $db->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-<N>.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());
+41 -1
View File
@@ -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."; ]<br />";
$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<N>.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 '</div>';
}
//--------------------------------
// 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 '<div class="bouton_live">';
if ($live_possible)
{
echo '<form action="bateaux.php" method="post" target="_blank">'.Champ_jeton_csrf();
echo '<button type="submit" name="ouvrir_live" title="'.t("Ouvrir le bateau en temps réel (nouvel onglet)").'">'.t("Live").'</button>';
echo '</form>';
}
else echo '<span class="live_indisponible" title="'.t("Aucun module ShipBase joignable n'est affecté à ce bateau").'">'.t("Live indisponible").'</span>';
echo '</div>';
echo '<div class="derniere_maj">'.t("Dernières données reçues").' : <span class="gras">'.$bateau['Date_derniere_maj'].' [UTC]</span></div>';
echo '</div>';
//--------------------------------
+20
View File
@@ -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 {
+12
View File
@@ -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
+10
View File
@@ -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