diff options
| author | polo <ordipolo@gmx.fr> | 2026-05-10 13:50:30 +0200 |
|---|---|---|
| committer | polo <ordipolo@gmx.fr> | 2026-05-10 13:50:30 +0200 |
| commit | 2c47085b27253c4ad2d062d15c18c3a8c7591298 (patch) | |
| tree | 096ca9bd97ee65550315bfae45ae1d79bbc70aab | |
| parent | 7d501b325f0389e53bf07ffb76718dcbc1b34c93 (diff) | |
| download | cms-2c47085b27253c4ad2d062d15c18c3a8c7591298.tar.gz cms-2c47085b27253c4ad2d062d15c18c3a8c7591298.tar.bz2 cms-2c47085b27253c4ad2d062d15c18c3a8c7591298.zip | |
mysqldump, téléchargement d'un .sql et stockage des sauvegardes dans /var/backups
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | composer.json | 3 | ||||
| -rw-r--r-- | config/index.php | 4 | ||||
| -rw-r--r-- | public/js/maintenance.js | 4 | ||||
| -rw-r--r-- | src/controller/MaintenanceController.php | 19 | ||||
| -rw-r--r-- | src/service/Backup.php | 70 | ||||
| -rw-r--r-- | src/service/Installation.php | 47 | ||||
| -rw-r--r-- | src/service/router.php | 5 | ||||
| -rw-r--r-- | src/view/templates/form.php | 3 | ||||
| -rw-r--r-- | src/view/templates/maintenance.php | 12 |
10 files changed, 145 insertions, 23 deletions
| @@ -1,4 +1,5 @@ | |||
| 1 | vendor/ | 1 | vendor/ |
| 2 | var/ | ||
| 2 | config/config.ini | 3 | config/config.ini |
| 3 | public/js/tinymce | 4 | public/js/tinymce |
| 4 | public/js/tinymce-langs | 5 | public/js/tinymce-langs |
diff --git a/composer.json b/composer.json index dd8aa9c..caf5a8c 100644 --- a/composer.json +++ b/composer.json | |||
| @@ -12,7 +12,8 @@ | |||
| 12 | "symfony/http-foundation": "^7.3", | 12 | "symfony/http-foundation": "^7.3", |
| 13 | "twbs/bootstrap-icons": "^1.13", | 13 | "twbs/bootstrap-icons": "^1.13", |
| 14 | "symfony/var-exporter": "^7.0", | 14 | "symfony/var-exporter": "^7.0", |
| 15 | "mklkj/tinymce-i18n": "^25.11" | 15 | "mklkj/tinymce-i18n": "^25.11", |
| 16 | "symfony/process": "^8.0" | ||
| 16 | }, | 17 | }, |
| 17 | "scripts": { | 18 | "scripts": { |
| 18 | "post-install-cmd": [ | 19 | "post-install-cmd": [ |
diff --git a/config/index.php b/config/index.php new file mode 100644 index 0000000..f2fdd42 --- /dev/null +++ b/config/index.php | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | <?php | ||
| 2 | // visite de config interdite lorsque la racine du serveur est visitable | ||
| 3 | header('Location: ..'); | ||
| 4 | die; \ No newline at end of file | ||
diff --git a/public/js/maintenance.js b/public/js/maintenance.js index 84ca8e1..c12bd6b 100644 --- a/public/js/maintenance.js +++ b/public/js/maintenance.js | |||
| @@ -7,7 +7,7 @@ function displayLogs(){ | |||
| 7 | return; | 7 | return; |
| 8 | } | 8 | } |
| 9 | 9 | ||
| 10 | let fetcher = new Fetcher({ | 10 | const fetcher = new Fetcher({ |
| 11 | endpoint: 'index.php?action=get_logs', | 11 | endpoint: 'index.php?action=get_logs', |
| 12 | method: 'POST', | 12 | method: 'POST', |
| 13 | onSuccess: (data) => { | 13 | onSuccess: (data) => { |
| @@ -26,7 +26,7 @@ function cleanLogs(){ | |||
| 26 | } | 26 | } |
| 27 | const log_table = getElementOrThrow('log_table'); | 27 | const log_table = getElementOrThrow('log_table'); |
| 28 | 28 | ||
| 29 | let fetcher = new Fetcher({ | 29 | const fetcher = new Fetcher({ |
| 30 | endpoint: 'index.php?action=erase_logs', | 30 | endpoint: 'index.php?action=erase_logs', |
| 31 | method: 'POST', | 31 | method: 'POST', |
| 32 | onSuccess: () => { | 32 | onSuccess: () => { |
diff --git a/src/controller/MaintenanceController.php b/src/controller/MaintenanceController.php index 3b804fc..fca45f1 100644 --- a/src/controller/MaintenanceController.php +++ b/src/controller/MaintenanceController.php | |||
| @@ -5,6 +5,7 @@ declare(strict_types=1); | |||
| 5 | 5 | ||
| 6 | use Doctrine\ORM\EntityManager; | 6 | use Doctrine\ORM\EntityManager; |
| 7 | use App\Entity\log; | 7 | use App\Entity\log; |
| 8 | use Symfony\Component\Process\Exception\ProcessFailedException; | ||
| 8 | 9 | ||
| 9 | class MaintenanceController | 10 | class MaintenanceController |
| 10 | { | 11 | { |
| @@ -47,4 +48,22 @@ class MaintenanceController | |||
| 47 | } | 48 | } |
| 48 | die; | 49 | die; |
| 49 | } | 50 | } |
| 51 | |||
| 52 | static public function getLastDump(EntityManager $entityManager): void | ||
| 53 | { | ||
| 54 | try{ | ||
| 55 | $file_path = Backup::mySQLdump($entityManager); | ||
| 56 | header('Content-Type: application/octet-stream'); // signifie fichier quelconque, du binaire quoi! | ||
| 57 | header('Content-Disposition: attachment; filename="' . basename($file_path) . '"'); // pour provoquer un téléchargement et non pour afficher | ||
| 58 | header('Content-Length: ' . filesize($file_path)); // peut servir côté client (barre de progression...) | ||
| 59 | readfile($file_path); | ||
| 60 | die; | ||
| 61 | } | ||
| 62 | // exeptions lancées dans Backup::mySQLdump | ||
| 63 | catch(ProcessFailedException $e){ // pas d'info $e pour le client | ||
| 64 | header('Location: ' . new URL(['page' => 'maintenance', 'error' => '500'])); | ||
| 65 | die; | ||
| 66 | } | ||
| 67 | die; | ||
| 68 | } | ||
| 50 | } \ No newline at end of file | 69 | } \ No newline at end of file |
diff --git a/src/service/Backup.php b/src/service/Backup.php new file mode 100644 index 0000000..d628c27 --- /dev/null +++ b/src/service/Backup.php | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | <?php | ||
| 2 | // src/service/Backup.php | ||
| 3 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | use Doctrine\ORM\EntityManager; | ||
| 7 | use Symfony\Component\Process\Process; // protection injection dans le shell | ||
| 8 | |||
| 9 | class Backup | ||
| 10 | { | ||
| 11 | static private string $backup_dir = '../var/backups'; | ||
| 12 | static private int $amount_to_keep = 30; | ||
| 13 | |||
| 14 | static public function mySQLdump(EntityManager $entityManager): string | ||
| 15 | { | ||
| 16 | $file_path = self::$backup_dir . '/db_' . Config::$database . '_' . new DateTime()->format('Y-m-d') . '.sql'; | ||
| 17 | |||
| 18 | // les versions de mysql sont comme ci: 8.0.36 | ||
| 19 | // celles de mariadb sont comme ça: 10.11.6-MariaDB | ||
| 20 | $version = $entityManager->getConnection()->fetchOne('SELECT VERSION()'); | ||
| 21 | $engine = stripos($version, 'mariadb') !== false ? 'mariadb-dump' : 'mysqldump'; | ||
| 22 | |||
| 23 | $tmp = tempnam('../var', 'tmp_db_codes_'); // crée un fichier avec un nom aléatoire et des droits 600 (concurrence) | ||
| 24 | file_put_contents($tmp, | ||
| 25 | "[client]\n | ||
| 26 | user=" . Config::$user . "\n | ||
| 27 | password=" . Config::$password . "\n | ||
| 28 | host=" . Config::$db_host . "\n"); | ||
| 29 | |||
| 30 | $command = new Process([ | ||
| 31 | $engine, | ||
| 32 | '--defaults-extra-file=' . $tmp, // pour ne pas enregistrer les codes dans l'historique de la console ou dans les processus de l'OS | ||
| 33 | '--single-transaction', | ||
| 34 | '--quick', // évite d'exploser la RAM si beaucoup de données | ||
| 35 | '--result-file=' . $file_path, | ||
| 36 | Config::$database | ||
| 37 | ]); | ||
| 38 | |||
| 39 | try{ | ||
| 40 | $command->mustRun(); // comme run() mais lance une ProcessFailedException | ||
| 41 | return $file_path; | ||
| 42 | } | ||
| 43 | finally{ | ||
| 44 | // exécuté même quand situé après "return" | ||
| 45 | unlink($tmp); | ||
| 46 | self::cleanBackups(); | ||
| 47 | } | ||
| 48 | |||
| 49 | // compression gzip (gros gain de place sur le serveur), nécessite l'extension zlib | ||
| 50 | /*try{ | ||
| 51 | file_put_contents( | ||
| 52 | $file_path . '.gz', | ||
| 53 | gzencode(file_get_contents($file_path), 5), // plus rapide que 9 et taille identique d'après mes essais | ||
| 54 | ); | ||
| 55 | return $file_path . '.gz'; | ||
| 56 | } | ||
| 57 | finally{ | ||
| 58 | unlink($file_path); | ||
| 59 | }*/ | ||
| 60 | } | ||
| 61 | |||
| 62 | static public function cleanBackups(): void { | ||
| 63 | $files = glob(self::$backup_dir . '/*.sql'); | ||
| 64 | usort($files, fn($a, $b) => filemtime($b) <=> filemtime($a)); // filemtime = date de dernière modification | ||
| 65 | $files_to_delete = array_slice($files, self::$amount_to_keep); | ||
| 66 | foreach($files_to_delete as $file){ | ||
| 67 | unlink($file); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } \ No newline at end of file | ||
diff --git a/src/service/Installation.php b/src/service/Installation.php index 059c093..eb4b6db 100644 --- a/src/service/Installation.php +++ b/src/service/Installation.php | |||
| @@ -9,10 +9,9 @@ class Installation | |||
| 9 | { | 9 | { |
| 10 | $flag = false; | 10 | $flag = false; |
| 11 | $extensions = ['pdo_mysql', 'mbstring', 'ctype', 'json', 'tokenizer', 'imagick']; // les 5 premières sont pour doctrine | 11 | $extensions = ['pdo_mysql', 'mbstring', 'ctype', 'json', 'tokenizer', 'imagick']; // les 5 premières sont pour doctrine |
| 12 | // ajouter plus tard zip pour les backup | 12 | // ajouter plus tard zlib pour la compression des backups |
| 13 | foreach($extensions as $extension){ | 13 | foreach($extensions as $extension){ |
| 14 | if(!extension_loaded($extension)) | 14 | if(!extension_loaded($extension)){ |
| 15 | { | ||
| 16 | echo("<p>l'extension <b>" . $extension . '</b> est manquante</p>'); | 15 | echo("<p>l'extension <b>" . $extension . '</b> est manquante</p>'); |
| 17 | $flag = true; | 16 | $flag = true; |
| 18 | } | 17 | } |
| @@ -34,8 +33,8 @@ class Installation | |||
| 34 | static public function checkFilesAndFoldersRights(): void | 33 | static public function checkFilesAndFoldersRights(): void |
| 35 | { | 34 | { |
| 36 | // -- droits des fichiers et dossiers -- | 35 | // -- droits des fichiers et dossiers -- |
| 37 | $droits_dossiers = 0700; | 36 | $droits_dossiers = 0755; |
| 38 | $droits_fichiers = 0600; | 37 | $droits_fichiers = 0644; |
| 39 | 38 | ||
| 40 | if(!file_exists('user_data')){ | 39 | if(!file_exists('user_data')){ |
| 41 | // créer le dossier user_data | 40 | // créer le dossier user_data |
| @@ -46,25 +45,43 @@ class Installation | |||
| 46 | <p>Aide: "serveur web" se nomme "www-data" sur debian et ubuntu, il s\'appelera "http" sur d\'autres distributions.</p>'; | 45 | <p>Aide: "serveur web" se nomme "www-data" sur debian et ubuntu, il s\'appelera "http" sur d\'autres distributions.</p>'; |
| 47 | die; | 46 | die; |
| 48 | } | 47 | } |
| 48 | if(!file_exists('../var')){ | ||
| 49 | mkdir('../var'); | ||
| 50 | chmod('../var', $droits_dossiers); | ||
| 51 | // | ||
| 52 | } | ||
| 53 | if(!file_exists('../var/backups')){ | ||
| 54 | mkdir('../var/backups'); | ||
| 55 | chmod('../var/backups', $droits_dossiers); | ||
| 56 | // | ||
| 57 | } | ||
| 49 | 58 | ||
| 59 | // droits 600 pour celui-ci | ||
| 50 | if(!file_exists('../config/config.ini')){ | 60 | if(!file_exists('../config/config.ini')){ |
| 51 | // aide à la création du config.ini | 61 | // aide à la création du config.ini |
| 52 | echo '<p>Le fichier config/config.ini est introuvable.</p>'; | 62 | echo '<p>Le fichier config/config.ini est introuvable.</p>'; |
| 53 | echo '<p>Il doit obligatoirement contenir les codes de la base de données, le protocole http ou https (et éventuellement le port) utilisé pour créer les liens internes.<br> | 63 | echo '<p>Il doit obligatoirement contenir les codes de la base de données, le protocole http ou https (et éventuellement le port) utilisé pour créer les liens internes.<br> |
| 54 | Un modèle est disponible, il s\'agit du fichier config/config-template.ini</p> | 64 | Un modèle est disponible, il s\'agit du fichier config/config-template.ini</p> |
| 55 | <p>Quand vous aurez terminé votre config.ini, donnez-lui par sécurité des droits 600.</p>'; | 65 | <p>Ce fichier a une importance critique. Si vous le pouvez faites en sorte que le serveur en soit le propriétaire et donner lui des droits 600.</p>'; |
| 56 | die; | 66 | die; |
| 57 | } | 67 | } |
| 58 | else{ | 68 | /*else{ |
| 59 | // droits du config.ini | 69 | // propriétaire du fichier |
| 60 | /*if(substr(sprintf('%o', fileperms('../config/config.ini')), -4) != 600){ | 70 | if(fileowner('../config/config.ini') != posix_geteuid()){ |
| 61 | chmod('../config/config.ini', $droits_fichiers); | 71 | echo "<p>le fichier config/config.ini n'appartient pas au serveur.</p>"; |
| 62 | }*/ | 72 | } |
| 73 | else{ | ||
| 74 | // droits du config.ini | ||
| 75 | if(substr(sprintf('%o', fileperms('../config/config.ini')), -4) != 600){ | ||
| 76 | echo '<p>Attention, le </p>'; | ||
| 77 | //chmod('../config/config.ini', $droits_fichiers); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | }*/ | ||
| 63 | 81 | ||
| 64 | // tester les liens internes | 82 | // tester les liens internes |
| 65 | // | 83 | // |
| 66 | 84 | ||
| 67 | // le test de connexion à la BDD est dans le doctrine bootstrap | 85 | // le test de connexion à la BDD est dans le doctrine bootstrap |
| 68 | } | ||
| 69 | } | 86 | } |
| 70 | } \ No newline at end of file | 87 | } \ No newline at end of file |
diff --git a/src/service/router.php b/src/service/router.php index 508721c..6973656 100644 --- a/src/service/router.php +++ b/src/service/router.php | |||
| @@ -45,7 +45,10 @@ if($request->getMethod() === 'GET'){ | |||
| 45 | } | 45 | } |
| 46 | 46 | ||
| 47 | if(IS_ADMIN === true){ | 47 | if(IS_ADMIN === true){ |
| 48 | // ... | 48 | if($request->query->has('action') && $request->query->get('action') === 'get_mysqldump'){ |
| 49 | MaintenanceController::getLastDump($entityManager); | ||
| 50 | die; | ||
| 51 | } | ||
| 49 | } | 52 | } |
| 50 | 53 | ||
| 51 | // construction d'une page | 54 | // construction d'une page |
diff --git a/src/view/templates/form.php b/src/view/templates/form.php index b3611c1..384bde9 100644 --- a/src/view/templates/form.php +++ b/src/view/templates/form.php | |||
| @@ -31,7 +31,6 @@ | |||
| 31 | <p class="send_email_success_<?= $node->getNodeData()->getId() ?> full_width_column"></p> | 31 | <p class="send_email_success_<?= $node->getNodeData()->getId() ?> full_width_column"></p> |
| 32 | </div> | 32 | </div> |
| 33 | <p id="form_warning_<?= $node->getNodeData()->getId() ?>" class="form_warning <?= ($keep_emails ?? false) ? '' : 'hidden' ?>"><i> | 33 | <p id="form_warning_<?= $node->getNodeData()->getId() ?>" class="form_warning <?= ($keep_emails ?? false) ? '' : 'hidden' ?>"><i> |
| 34 | Une copie de votre e-mail (nom, adresse et message) sera conservée dans notre base de données dans le but de pouvoir répondre à votre demande et éventuellement dans un but de prospection. Ces données seront traitées automatiquement par notre serveur et conservées pendant au maximum <?= $retention_period ?> mois à compter de votre dernier message.<br> | 34 | Une copie de votre e-mail (nom, adresse et message) sera conservée dans notre base de données dans le but de pouvoir mieux répondre à votre demande et éventuellement dans d'autres buts (prospection). Ces données seront traitées automatiquement par notre serveur et conservées pendant au maximum <?= $retention_period ?> mois à compter de votre dernier message. Ce traitement repose sur votre consentement. Vous pouvez consulter, modifier ou supprimer vos données sur simple demande. |
| 35 | Ce traitement repose sur votre consentement. Vous pouvez consulter, modifier ou supprimer vos données en base de données sur simple demande. | ||
| 36 | </i></p> | 35 | </i></p> |
| 37 | </section> \ No newline at end of file | 36 | </section> \ No newline at end of file |
diff --git a/src/view/templates/maintenance.php b/src/view/templates/maintenance.php index 0118bbf..3501fa4 100644 --- a/src/view/templates/maintenance.php +++ b/src/view/templates/maintenance.php | |||
| @@ -12,8 +12,16 @@ | |||
| 12 | </div> | 12 | </div> |
| 13 | <div class="basic_div"> | 13 | <div class="basic_div"> |
| 14 | <p> | 14 | <p> |
| 15 | <a href="<?= new URL(['page' => 'emails']) ?>"><button>Consulter les emails</button></a><br> | 15 | <a href="<?= new URL(['page' => 'emails']) ?>"><button>Consulter les emails</button></a> |
| 16 | <i>Emails reçus depuis tous les formulaires de contact</i> | 16 | <i>reçus depuis tous les formulaires de contact</i> |
| 17 | </p> | ||
| 18 | </div> | ||
| 19 | <div class="basic_div"> | ||
| 20 | <p> | ||
| 21 | <a href="<?= new URL(['action' => 'get_mysqldump']) ?>"> | ||
| 22 | <button id="get_mysqldump">Télécharger la base de données</button> | ||
| 23 | </a><br> | ||
| 24 | <i>Réalise un "mysqldump", vous obtiendrez un unique fichier contenant toute la BDD.</i> | ||
| 17 | </p> | 25 | </p> |
| 18 | </div> | 26 | </div> |
| 19 | 27 | ||
