Initial import of db-manager
This commit is contained in:
@@ -0,0 +1 @@
|
||||
data/
|
||||
@@ -0,0 +1 @@
|
||||
<a href="/CMD_PLUGINS/db-manager">Kopie Zapasowe Baz Danych</a>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 4167 4167" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="Layer1"><path d="M2886.56,2026c124.454,-33.789 233.937,-75.427 323.802,-123.378c69.763,-37.225 127.389,-77.144 167.339,-120.484c35.773,-38.807 56.966,-79.979 56.966,-124.494c-0,-45.344 38.329,-82.102 85.611,-82.102l73.642,-0c47.281,-0 85.611,36.758 85.611,82.102l-0,864.735c-0,4.074 -0.075,8.141 -0.226,12.202l0.226,-0.217l-0,864.736c-0,8.328 -1.301,16.366 -3.696,23.896c-21.51,142.017 -139.065,279.212 -340.73,386.819c-282.402,150.686 -737.537,251.334 -1251.77,251.334c-514.231,0 -969.367,-100.648 -1251.77,-251.334c-201.675,-107.612 -319.232,-244.815 -340.719,-386.772c-2.406,-7.575 -3.707,-15.614 -3.707,-23.943l-0,-2606.19c-0,-159.939 119.985,-316.305 344.426,-436.064c282.401,-150.686 737.537,-251.335 1251.77,-251.335c514.232,0 969.367,100.649 1251.77,251.335c224.441,119.759 344.426,276.125 344.426,436.064c-0,159.939 -119.985,316.305 -344.426,436.064c-282.402,150.686 -737.537,251.335 -1251.77,251.335c-514.231,-0 -969.367,-100.649 -1251.77,-251.335c-35.856,-19.132 -69.082,-39.133 -99.562,-59.866l0,488.538c0,44.515 21.192,85.687 56.965,124.494c39.951,43.34 97.576,83.259 167.34,120.484c80.548,42.98 176.858,80.888 285.583,112.618c-49.419,59.66 -92.441,124.806 -128.011,194.385c-106.482,-33.748 -201.476,-72.782 -282.315,-115.917c-35.856,-19.132 -69.082,-39.134 -99.562,-59.866l0,488.537c0,44.516 21.192,85.688 56.965,124.495c39.951,43.339 97.576,83.259 167.34,120.484c14.929,7.966 30.4,15.758 46.39,23.368c8.598,92.898 29.185,182.343 60.198,266.77c-85.995,-29.677 -163.627,-62.927 -231.331,-99.053c-35.856,-19.132 -69.082,-39.133 -99.562,-59.866l0,475.174c0,44.515 21.192,85.687 56.965,124.494c39.951,43.34 97.576,83.259 167.34,120.484c253.957,135.509 664.589,220.603 1127.03,220.603c462.437,0 873.069,-85.094 1127.03,-220.603c69.763,-37.225 127.389,-77.144 167.339,-120.484c35.773,-38.807 56.965,-79.979 56.966,-124.494l-0.002,-475.173c-30.479,20.732 -63.705,40.733 -99.56,59.865c-81.528,43.502 -177.452,82.834 -285.038,116.778c32.16,-82.912 54.2,-170.852 64.602,-262.303c33.941,-14.455 65.895,-29.661 95.693,-45.56c69.763,-37.225 127.389,-77.145 167.339,-120.484c35.773,-38.807 56.965,-79.979 56.966,-124.495l-0.002,-488.536c-30.479,20.731 -63.705,40.733 -99.56,59.865c-91.011,48.562 -199.962,91.927 -323.05,128.402c-34.671,-70.088 -76.855,-135.81 -125.495,-196.11Zm-2154.55,-1233.09c0,44.516 21.192,85.688 56.965,124.495c39.951,43.339 97.576,83.259 167.34,120.484c253.957,135.508 664.589,220.603 1127.03,220.603c462.437,-0 873.069,-85.095 1127.03,-220.603c69.763,-37.225 127.389,-77.145 167.339,-120.484c35.773,-38.807 56.966,-79.979 56.966,-124.495c-0,-44.516 -21.193,-85.687 -56.966,-124.495c-39.95,-43.339 -97.576,-83.258 -167.339,-120.483c-253.957,-135.509 -664.589,-220.604 -1127.03,-220.604c-462.436,0 -873.068,85.095 -1127.03,220.604c-69.764,37.225 -127.389,77.144 -167.34,120.483c-35.773,38.808 -56.965,79.979 -56.965,124.495Z" style="fill:#61bdf6;"/><g><path d="M2707.95,2284.57c32.156,55.441 57.73,115.414 74.017,179.216c109.768,429.98 -145.253,875.727 -569.136,994.783c-423.882,119.056 -857.138,-133.374 -966.906,-563.354c-94.361,-369.63 80.894,-750.88 401.201,-926.284l-12.154,-180.13c-2.692,-39.891 16.834,-78.324 50.539,-99.475c33.706,-21.15 75.891,-21.443 109.189,-0.757l396.892,246.56c28.349,17.612 46.481,48.131 48.772,82.094c2.292,33.962 -11.534,67.262 -37.189,89.572l-359.182,312.337c-30.134,26.204 -71.866,33.212 -108.015,18.139c-36.15,-15.074 -60.604,-49.68 -63.296,-89.572l-7.158,-106.117c-166.194,138.489 -248.329,367.045 -191.521,589.572c77.17,302.29 381.763,479.756 679.766,396.056c298.002,-83.7 477.29,-397.074 400.12,-699.364c-11.974,-46.902 -31.513,-90.592 -56.106,-130.733c42.956,-21.312 81.857,-39.186 164.715,-86.536c18.265,-10.437 32.929,-18.906 45.452,-26.007Z" style="fill:#5a5d9d;"/><path d="M2752.93,2182.83c166.761,69.848 244.223,265.476 172.874,436.587c-71.349,171.112 -264.663,253.325 -431.424,183.478c-166.76,-69.848 -244.223,-265.476 -172.874,-436.587c71.349,-171.112 264.663,-253.325 431.424,-183.478Z" style="fill:#ffa008;"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SCRIPT="$PLUGIN_DIR/scripts/install.sh"
|
||||
|
||||
if [ ! -x "$SCRIPT" ]; then
|
||||
chmod 755 "$SCRIPT" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
exec "$SCRIPT"
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
return [
|
||||
'MySQL/MariaDB - Przywracanie baz danych' => 'MySQL/MariaDB - Database Restore',
|
||||
'Manager kopii zapasowych baz danych' => 'Database backup manager',
|
||||
'Kopie zapasowe udostepniane przez HITME.PL' => 'Backups provided by HITME.PL',
|
||||
'Kopie zapasowe uzytkownika' => 'User backups',
|
||||
'Backupy udostepniane przez HITME.PL' => 'Backups Provided by HITME.PL',
|
||||
'Backupy Uzytkownika' => 'User Backups',
|
||||
'Tryb przywracania baz danych' => 'Database Restore Mode',
|
||||
'Nadpisanie istniejacej bazy danych (obecne dzialanie)' => 'Overwrite existing database (current behavior)',
|
||||
'Przywrocenie wskazanej bazy do nowej bazy danych' => 'Restore selected database into a new database',
|
||||
'Nazwa docelowej bazy danych' => 'Target database name',
|
||||
'Status zadan' => 'Job Status',
|
||||
'Katalogi kopii zapasowych' => 'Backup Directories',
|
||||
'Kalendarz backupow' => 'Backup Calendar',
|
||||
'Kalendarz backupow uzytkownika' => 'User Backup Calendar',
|
||||
'Wybierz backup i baze' => 'Select Backup and Database',
|
||||
'Data rozpoczecia generowania backupow' => 'Backup generation start time',
|
||||
'Wybierz aktywna date w kalendarzu.' => 'Select an active date in the calendar.',
|
||||
'Wybierz godzine backupu, aby zobaczyc liste baz.' => 'Select backup time to see database list.',
|
||||
'Godzina backupu:' => 'Backup time:',
|
||||
'Miesiac:' => 'Month:',
|
||||
'Brak aktywnych dat backupu dla tego uzytkownika.' => 'No active backup dates for this user.',
|
||||
'Brak backupow uzytkownika.' => 'No user backups.',
|
||||
'Brak kopii zapasowych uzytkownika' => 'No user backups.',
|
||||
'Brak dostepnych Kopii zapasowych uzytkownika' => 'No available user backups.',
|
||||
'Brak kopii zapasowych użytkownika dostępnych do przywrócenia - najpierw utwórz kopię zapasową na zakładce Wykonaj Backup Baz danych' => 'No user backups are available for restore. First create a backup in the "Create Database Backup" tab.',
|
||||
'Brak baz dla wybranego backupu.' => 'No databases for the selected backup.',
|
||||
'Brak baz danych uzytkownika do backupu.' => 'No user databases available for backup.',
|
||||
'Data Rozpoczecia' => 'Start Date',
|
||||
'Nazwa Bazy' => 'Database Name',
|
||||
'Data Backupu' => 'Backup Date',
|
||||
'Rodzaj przywracanej bazy' => 'Restored backup type',
|
||||
'Tryb Pracy' => 'Work mode',
|
||||
'Tryb przywracania' => 'Restore mode',
|
||||
'Status' => 'Status',
|
||||
'Akcje' => 'Actions',
|
||||
'Kopia bazy udostepnianej przez HITME.PL' => 'HITME.PL-provided database backup',
|
||||
'Kopia bazy uzytkownika' => 'User database backup',
|
||||
'Nadpisanie istniejacej bazy' => 'Overwrite existing database',
|
||||
'Przywrocenie kopii do nowej bazy' => 'Restore backup to a new database',
|
||||
'Nie dotyczy' => 'Not applicable',
|
||||
'Tworzenie Kopii bazy danych' => 'Database backup creation',
|
||||
'Przywracanie bazy danych - Nadpisanie oryginalnej bazy danych' => 'Database restore - overwrite original database',
|
||||
'Przywracanie bazy danych - Do nowej bazy danych' => 'Database restore - to a new database',
|
||||
'Baza danych' => 'Database',
|
||||
'Rozmiar' => 'Size',
|
||||
'Data i godzina backupu' => 'Backup date and time',
|
||||
'Wykonaj Backup' => 'Create Backup',
|
||||
'Wykonaj backupy wszystkich zaznaczonych baz' => 'Create backups for all selected databases',
|
||||
'Wykonaj Backup Baz danych' => 'Create Database Backup',
|
||||
'Przywróć kopię zapasową' => 'Restore backup',
|
||||
'Przywroc' => 'Restore',
|
||||
'Backup przed przywroceniem:' => 'Pre-restore backup:',
|
||||
'Backup przed przywroceniem backupow uzytkownika:' => 'Pre-restore backup for user backups:',
|
||||
'Katalog backupow uzytkownika:' => 'User backup directory:',
|
||||
'Log' => 'Log',
|
||||
'Pokaż logi' => 'Show logs',
|
||||
'Zapisz log' => 'Download Log',
|
||||
'Pobierz' => 'Download',
|
||||
'Pobierz kopię zapasową' => 'Download backup copy',
|
||||
'Usun' => 'Delete',
|
||||
'Usun log' => 'Delete Log',
|
||||
'Anuluj' => 'Cancel',
|
||||
'Przywroc backup przed przywroceniem' => 'Restore Pre-restore Backup',
|
||||
'Usun backup przed przywroceniem' => 'Delete Pre-restore Backup',
|
||||
'Przywróć poprzednią wersję bazy danych' => 'Restore previous database version',
|
||||
'Usuń backup poprzedniej wersji bazy danych' => 'Delete backup of previous database version',
|
||||
'Podglad na zywo (odswiezanie co 3s)' => 'Live preview (refresh every 3s)',
|
||||
'Data rozpoczecia:' => 'Start time:',
|
||||
'Baza danych:' => 'Database:',
|
||||
'Backup (data katalogu):' => 'Backup (folder date):',
|
||||
'Rodzaj przywracanej bazy:' => 'Restored backup type:',
|
||||
'Tryb przywracania:' => 'Restore mode:',
|
||||
'Koniecznosc sanityzacji:' => 'Sanitization required:',
|
||||
'Status:' => 'Status:',
|
||||
'Postep:' => 'Progress:',
|
||||
'Kopiuj do schowka' => 'Copy to clipboard',
|
||||
'Kopiuj' => 'Copy',
|
||||
'Zamknij' => 'Close',
|
||||
'Potwierdzenie operacji' => 'Confirm Operation',
|
||||
'Uwaga:' => 'Warning:',
|
||||
'Tak, kontynuuj' => 'Yes, continue',
|
||||
'Nie' => 'No',
|
||||
'Poprzedni miesiac' => 'Previous month',
|
||||
'Nastepny miesiac' => 'Next month',
|
||||
'Backupy HITME.PL:' => 'HITME.PL backups:',
|
||||
'Backupy uzytkownika:' => 'User backups:',
|
||||
'Backup przed przywroceniem (HITME.PL):' => 'Pre-restore backup (HITME.PL):',
|
||||
'Backup przed przywroceniem (backupy uzytkownika):' => 'Pre-restore backup (user backups):',
|
||||
'Oczekuje' => 'Pending',
|
||||
'W trakcie' => 'In progress',
|
||||
'Zakonczone' => 'Completed',
|
||||
'Blad' => 'Error',
|
||||
'Anulowane' => 'Canceled',
|
||||
'Nieznany' => 'Unknown',
|
||||
'Brak zadan do wyswietlenia.' => 'No jobs to display.',
|
||||
'Nieprawidlowy token CSRF.' => 'Invalid CSRF token.',
|
||||
'Brak katalogu HITME_SQL_BACKUP_DIR.' => 'Missing HITME_SQL_BACKUP_DIR directory.',
|
||||
'ENABLE_PRE_RESTORE_BACKUP ma niepoprawna wartosc (uzywam true/false).' => 'ENABLE_PRE_RESTORE_BACKUP has an invalid value (expected true/false).',
|
||||
'ENABLE_USER_PRE_RESTORE_BACKUP ma niepoprawna wartosc (uzywam true/false).' => 'ENABLE_USER_PRE_RESTORE_BACKUP has an invalid value (expected true/false).',
|
||||
'SECURE_BACKUP ma niepoprawna wartosc (uzywam true/false).' => 'SECURE_BACKUP has an invalid value (expected true/false).',
|
||||
'SECURE_RESTORE ma niepoprawna wartosc (uzywam true/false).' => 'SECURE_RESTORE has an invalid value (expected true/false).',
|
||||
'Nie wybrano poprawnej daty backupu.' => 'No valid backup date selected.',
|
||||
'Nie wybrano poprawnej godziny backupu.' => 'No valid backup time selected.',
|
||||
'Nie wybrano poprawnej bazy danych.' => 'No valid database selected.',
|
||||
'Tworzenie kopii zapasowej' => 'Backup creation',
|
||||
'Przywracanie kopii zapasowej' => 'Backup restore',
|
||||
'oczekuje w kolejce' => 'pending in queue',
|
||||
'jest w trakcie' => 'is in progress',
|
||||
'Aktualnie trwa tworzenie lub przywracanie kopii zapasowej - zaczekaj na zakończenie aktywnego zadania i ponów próbę. Aktywne zadanie: %s dla bazy %s (%s).'
|
||||
=> 'A backup or restore task is currently active. Wait until it finishes and try again. Active task: %s for database %s (%s).',
|
||||
'Dla bazy %s istnieje juz aktywne zadanie (%s, %s). Zaczekaj na jego zakonczenie i ponow probe.'
|
||||
=> 'There is already an active task for database %s (%s, %s). Wait until it completes and try again.',
|
||||
'Nie mozna zablokowac kolejki zadan. Sprobuj ponownie.'
|
||||
=> 'Could not lock the jobs queue. Try again.',
|
||||
'Nie mozna utworzyc katalogu zadan.' => 'Cannot create jobs directory.',
|
||||
'Nie znaleziono pliku dump dla wybranej bazy.' => 'Selected database dump file was not found.',
|
||||
'Nie udalo sie zapisac pliku zadania.' => 'Failed to save job file.',
|
||||
'Zadanie przywracania bazy dodane do kolejki. Otrzymasz powiadomienie po zakonczeniu.' => 'Database restore job has been queued. You will be notified after completion.',
|
||||
'Aby wykonac backup zbiorczy zaznacz co najmniej dwie bazy.' => 'Select at least two databases for bulk backup.',
|
||||
'Nie wybrano baz do backupu.' => 'No databases selected for backup.',
|
||||
'Nieprawidlowa baza danych: ' => 'Invalid database: ',
|
||||
'W trybie przywracania do nowej bazy tworzenie backupow uzytkownika jest ukryte.' => 'In restore-to-new-database mode, creating user backups is hidden.',
|
||||
'Nazwa docelowej bazy danych jest nieprawidlowa. Musi zaczynac sie od nazwy uzytkownika i znaku _.' => 'Target database name is invalid. It must start with the DirectAdmin username and underscore.',
|
||||
'Wskazana docelowa baza danych nie istnieje. Utworz ja recznie w DirectAdmin.' => 'The selected target database does not exist. Create it manually in DirectAdmin.',
|
||||
'Nie udalo sie sprawdzic, czy docelowa baza danych jest pusta.' => 'Could not verify whether the target database is empty.',
|
||||
'Docelowa baza danych nie jest pusta. Usun dane przed kontynuowaniem.' => 'Target database is not empty. Remove data before continuing.',
|
||||
'Docelowa baza danych musi byc inna niz zrodlowa baza z backupu.' => 'Target database must be different from the source backup database.',
|
||||
'Nie mozna utworzyc katalogu backupu uzytkownika.' => 'Cannot create user backup directory.',
|
||||
'Nie udalo sie zapisac pliku zadania backupu dla bazy ' => 'Failed to save backup job file for database ',
|
||||
'Dodano zadanie backupu bazy danych.' => 'Database backup job added.',
|
||||
'Dodano zadania backupu %d baz danych.' => 'Backup jobs added for %d databases.',
|
||||
'Nie wybrano poprawnej daty backupu uzytkownika.' => 'No valid user backup date selected.',
|
||||
'Nie wybrano poprawnej godziny backupu uzytkownika.' => 'No valid user backup time selected.',
|
||||
'Zadanie przywracania backupu uzytkownika dodane do kolejki.' => 'User backup restore job has been queued.',
|
||||
'Nie znaleziono zadania.' => 'Job not found.',
|
||||
'Brak nazwy bazy w zadaniu.' => 'Missing database name in job.',
|
||||
'Backup przed przywroceniem nie istnieje.' => 'Pre-restore backup does not exist.',
|
||||
'Nieprawidlowa sciezka backupu przed przywroceniem.' => 'Invalid pre-restore backup path.',
|
||||
'Dodano zadanie przywracania backupu przed przywroceniem.' => 'Pre-restore backup restore job has been queued.',
|
||||
'Nie udalo sie usunac backupu przed przywroceniem.' => 'Failed to delete pre-restore backup.',
|
||||
'Backup przed przywroceniem zostal usuniety.' => 'Pre-restore backup deleted.',
|
||||
'Nieprawidlowa sciezka backupu uzytkownika.' => 'Invalid user backup path.',
|
||||
'Nieprawidlowa nazwa pliku backupu uzytkownika.' => 'Invalid user backup filename.',
|
||||
'Pobieranie backupow HITME.SQL jest wylaczone.' => 'Downloading backups from HITME_SQL_BACKUP_DIR is disabled.',
|
||||
'Sekcja backupow uzytkownika jest wylaczona.' => 'User backup section is disabled.',
|
||||
'Opcja zarzadzania bazami danych jest niedostepna - skontaktuj sie z supportem aby uzyskac wiecej informacji'
|
||||
=> 'Database management is unavailable - contact support for more information.',
|
||||
'Usuwanie logow i wpisow zadan jest zablokowane przez konfiguracje.' => 'Deleting logs and job entries is blocked by configuration.',
|
||||
'Nieprawidlowa nazwa pliku backupu.' => 'Invalid backup filename.',
|
||||
'Nieprawidlowa sciezka backupu.' => 'Invalid backup path.',
|
||||
'Nie udalo sie usunac backupu uzytkownika.' => 'Failed to delete user backup.',
|
||||
'Backup uzytkownika zostal usuniety.' => 'User backup has been deleted.',
|
||||
'Nie mozna pobrac pliku backupu (naglowki zostaly juz wyslane).' => 'Cannot download backup file (headers already sent).',
|
||||
'Nieprawidlowe zadanie.' => 'Invalid job.',
|
||||
'Zadanie zostalo anulowane.' => 'Job has been canceled.',
|
||||
'Zadanie nie jest w trakcie.' => 'Job is not in progress.',
|
||||
'Nie znaleziono procesu do anulowania.' => 'No process found to cancel.',
|
||||
'Brak obsugi posix_kill na serwerze.' => 'posix_kill is not available on the server.',
|
||||
'Nie udalo sie anulowac zadania.' => 'Failed to cancel job.',
|
||||
'Wybrane zadania zostaly usuniete.' => 'Selected jobs have been deleted.',
|
||||
'Nie wybrano zadnych zadan.' => 'No jobs selected.',
|
||||
'Zadanie zostalo usuniete.' => 'Job has been deleted.',
|
||||
'Nie znaleziono logu.' => 'Log not found.',
|
||||
'Log nie istnieje.\n' => "Log does not exist.\n",
|
||||
'Data rozpoczecia: ' => 'Start time: ',
|
||||
'Baza danych: ' => 'Database: ',
|
||||
'Backup (data katalogu): ' => 'Backup (folder date): ',
|
||||
'Rodzaj przywracanej bazy: ' => 'Restored backup type: ',
|
||||
'Tryb przywracania: ' => 'Restore mode: ',
|
||||
'Koniecznosc Sanityzacji: Tak' => 'Sanitization required: Yes',
|
||||
'Koniecznosc Sanityzacji: Nie' => 'Sanitization required: No',
|
||||
'Koniecznosc Sanityzacji: ' => 'Sanitization required: ',
|
||||
'Status: ' => 'Status: ',
|
||||
'Postep: ' => 'Progress: ',
|
||||
'Przywracanie backupu moze nadpisac obecne dane bazy. Czy kontynuowac?' => 'Restoring backup may overwrite current database data. Continue?',
|
||||
'Przywracanie backupu uzytkownika moze nadpisac obecne dane bazy. Czy kontynuowac?' => 'Restoring user backup may overwrite current database data. Continue?',
|
||||
'Przywrocenie backupu sprzed przywrocenia moze nadpisac obecne dane. Czy kontynuowac?' => 'Restoring pre-restore backup may overwrite current data. Continue?',
|
||||
'Usuniecie backupu sprzed przywrocenia jest nieodwracalne. Czy kontynuowac?' => 'Deleting pre-restore backup is irreversible. Continue?',
|
||||
'Usuniesz backup uzytkownika. Tej operacji nie mozna cofnac. Czy kontynuowac?' => 'You will delete a user backup. This operation cannot be undone. Continue?',
|
||||
'Usuniesz log i wpis zadania. Tej operacji nie mozna cofnac. Czy kontynuowac?' => 'You will delete the log and the job entry. This operation cannot be undone. Continue?',
|
||||
'Czy kontynuowac operacje?' => 'Continue operation?',
|
||||
'Przywrócenie backupu sprzed przywrócenia może nadpisać obecne dane. Czy kontynuować?' => 'Restoring pre-restore backup may overwrite current data. Continue?',
|
||||
'Usunięcie backupu sprzed przywrócenia jest nieodwracalne. Czy kontynuować?' => 'Deleting pre-restore backup is irreversible. Continue?',
|
||||
'Usuniesz backup użytkownika. Tej operacji nie można cofnąć. Czy kontynuować?' => 'You will delete a user backup. This operation cannot be undone. Continue?',
|
||||
'Usuniesz log i wpis zadania. Tej operacji nie można cofnąć. Czy kontynuować?' => 'You will delete the log and the job entry. This operation cannot be undone. Continue?',
|
||||
'Czy kontynuować operację?' => 'Continue operation?',
|
||||
'UWAGA: Czy na pewno anulowac zadanie?' => 'WARNING: Are you sure you want to cancel this job?',
|
||||
'UWAGA: Przywrocenie backupu sprzed przywrocenia moze nadpisac obecne dane. Kontynuowac?' => 'WARNING: Restoring the pre-restore backup may overwrite current data. Continue?',
|
||||
'UWAGA: Usuniecie backupu sprzed przywrocenia jest nieodwracalne. Kontynuowac?' => 'WARNING: Deleting the pre-restore backup is irreversible. Continue?',
|
||||
'UWAGA: Usuniesz log i wpis zadania. Tej operacji nie mozna cofnac. Kontynuowac?' => 'WARNING: You are about to delete the log and job entry. This action cannot be undone. Continue?',
|
||||
'UWAGA: Rozpoczynasz przywracanie backupu bazy danych. Operacja moze nadpisac obecne dane. Kontynuowac?' => 'WARNING: You are starting a database restore. This operation may overwrite current data. Continue?',
|
||||
];
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
$en = include __DIR__ . '/en.php';
|
||||
|
||||
function pl_normalize_text(string $text): string
|
||||
{
|
||||
static $replacements = null;
|
||||
if ($replacements === null) {
|
||||
$replacements = [
|
||||
'Opcja zarzadzania bazami danych jest niedostepna - skontaktuj sie z supportem aby uzyskac wiecej informacji'
|
||||
=> 'Opcja zarządzania bazami danych jest niedostępna - skontaktuj się z supportem aby uzyskać więcej informacji',
|
||||
'Brak kopii zapasowych użytkownika dostępnych do przywrócenia - najpierw utwórz kopię zapasową na zakładce Wykonaj Backup Baz danych'
|
||||
=> 'Brak kopii zapasowych użytkownika dostępnych do przywrócenia - najpierw utwórz kopię zapasową na zakładce Wykonaj Backup Baz danych',
|
||||
'Kopie zapasowe udostepniane przez HITME.PL' => 'Kopie zapasowe udostępniane przez HITME.PL',
|
||||
'Backupy udostepniane przez HITME.PL' => 'Backupy udostępniane przez HITME.PL',
|
||||
'Kopie zapasowe uzytkownika' => 'Kopie zapasowe użytkownika',
|
||||
'Backupy Uzytkownika' => 'Backupy Użytkownika',
|
||||
'Kalendarz backupow uzytkownika' => 'Kalendarz backupów użytkownika',
|
||||
'Brak aktywnych dat backupu dla tego uzytkownika.' => 'Brak aktywnych dat backupu dla tego użytkownika.',
|
||||
'Brak backupow uzytkownika.' => 'Brak backupów użytkownika.',
|
||||
'Brak kopii zapasowych uzytkownika' => 'Brak kopii zapasowych użytkownika',
|
||||
'Brak dostepnych Kopii zapasowych uzytkownika' => 'Brak dostępnych kopii zapasowych użytkownika',
|
||||
'Brak baz danych uzytkownika do backupu.' => 'Brak baz danych użytkownika do backupu.',
|
||||
'Kopia bazy uzytkownika' => 'Kopia bazy użytkownika',
|
||||
'Kopia bazy udostepnianej przez HITME.PL' => 'Kopia bazy udostępnianej przez HITME.PL',
|
||||
'Nadpisanie istniejacej bazy danych (obecne dzialanie)' => 'Nadpisanie istniejącej bazy danych (obecne działanie)',
|
||||
'Nadpisanie istniejacej bazy' => 'Nadpisanie istniejącej bazy',
|
||||
'Przywrocenie wskazanej bazy do nowej bazy danych' => 'Przywrócenie wskazanej bazy do nowej bazy danych',
|
||||
'Przywrocenie kopii do nowej bazy' => 'Przywrócenie kopii do nowej bazy',
|
||||
'Przywroc' => 'Przywróć',
|
||||
'Przywroc backup przed przywroceniem' => 'Przywróć backup przed przywróceniem',
|
||||
'Usun backup przed przywroceniem' => 'Usuń backup przed przywróceniem',
|
||||
'Backup przed przywroceniem:' => 'Backup przed przywróceniem:',
|
||||
'Backup przed przywroceniem backupow uzytkownika:' => 'Backup przed przywróceniem backupów użytkownika:',
|
||||
'Katalog backupow uzytkownika:' => 'Katalog backupów użytkownika:',
|
||||
'Podglad na zywo (odswiezanie co 3s)' => 'Podgląd na żywo (odświeżanie co 3s)',
|
||||
'Data rozpoczecia generowania backupow' => 'Data rozpoczęcia generowania backupów',
|
||||
'Data rozpoczecia:' => 'Data rozpoczęcia:',
|
||||
'Data Rozpoczecia' => 'Data Rozpoczęcia',
|
||||
'Data i godzina backupu' => 'Data i godzina backupu',
|
||||
'Wybierz godzine backupu, aby zobaczyc liste baz.' => 'Wybierz godzinę backupu, aby zobaczyć listę baz.',
|
||||
'Wybierz aktywna date w kalendarzu.' => 'Wybierz aktywną datę w kalendarzu.',
|
||||
'Miesiac:' => 'Miesiąc:',
|
||||
'Poprzedni miesiac' => 'Poprzedni miesiąc',
|
||||
'Nastepny miesiac' => 'Następny miesiąc',
|
||||
'Status zadan' => 'Status zadań',
|
||||
'Brak zadan do wyswietlenia.' => 'Brak zadań do wyświetlenia.',
|
||||
'Nie wybrano zadnych zadan.' => 'Nie wybrano żadnych zadań.',
|
||||
'Koniecznosc sanityzacji:' => 'Konieczność sanityzacji:',
|
||||
'Koniecznosc Sanityzacji: Tak' => 'Konieczność Sanityzacji: Tak',
|
||||
'Koniecznosc Sanityzacji: Nie' => 'Konieczność Sanityzacji: Nie',
|
||||
'Koniecznosc Sanityzacji: ' => 'Konieczność Sanityzacji: ',
|
||||
'Postep:' => 'Postęp:',
|
||||
'Zakonczone' => 'Zakończone',
|
||||
'Blad' => 'Błąd',
|
||||
'Nieprawidlowy token CSRF.' => 'Nieprawidłowy token CSRF.',
|
||||
'Nieprawidlowe zadanie.' => 'Nieprawidłowe zadanie.',
|
||||
'Nieprawidlowa baza danych: ' => 'Nieprawidłowa baza danych: ',
|
||||
'Nieprawidlowa nazwa pliku backupu uzytkownika.' => 'Nieprawidłowa nazwa pliku backupu użytkownika.',
|
||||
'Nieprawidlowa sciezka backupu uzytkownika.' => 'Nieprawidłowa ścieżka backupu użytkownika.',
|
||||
'Nieprawidlowa nazwa pliku backupu.' => 'Nieprawidłowa nazwa pliku backupu.',
|
||||
'Nieprawidlowa sciezka backupu.' => 'Nieprawidłowa ścieżka backupu.',
|
||||
'Nieprawidlowa sciezka backupu przed przywroceniem.' => 'Nieprawidłowa ścieżka backupu przed przywróceniem.',
|
||||
'Nazwa docelowej bazy danych jest nieprawidlowa. Musi zaczynac sie od nazwy uzytkownika i znaku _.'
|
||||
=> 'Nazwa docelowej bazy danych jest nieprawidłowa. Musi zaczynać się od nazwy użytkownika i znaku _.',
|
||||
'Wskazana docelowa baza danych nie istnieje. Utworz ja recznie w DirectAdmin.'
|
||||
=> 'Wskazana docelowa baza danych nie istnieje. Utwórz ją ręcznie w DirectAdmin.',
|
||||
'Nie udalo sie sprawdzic, czy docelowa baza danych jest pusta.'
|
||||
=> 'Nie udało się sprawdzić, czy docelowa baza danych jest pusta.',
|
||||
'Docelowa baza danych musi byc inna niz zrodlowa baza z backupu.'
|
||||
=> 'Docelowa baza danych musi być inna niż źródłowa baza z backupu.',
|
||||
'W trybie przywracania do nowej bazy tworzenie backupow uzytkownika jest ukryte.'
|
||||
=> 'W trybie przywracania do nowej bazy tworzenie backupów użytkownika jest ukryte.',
|
||||
'Nie mozna utworzyc katalogu zadan.' => 'Nie można utworzyć katalogu zadań.',
|
||||
'Nie mozna utworzyc katalogu backupu uzytkownika.' => 'Nie można utworzyć katalogu backupu użytkownika.',
|
||||
'Nie mozna pobrac pliku backupu (naglowki zostaly juz wyslane).'
|
||||
=> 'Nie można pobrać pliku backupu (nagłówki zostały już wysłane).',
|
||||
'Nie udalo sie zapisac pliku zadania.' => 'Nie udało się zapisać pliku zadania.',
|
||||
'Nie udalo sie zapisac pliku zadania backupu dla bazy ' => 'Nie udało się zapisać pliku zadania backupu dla bazy ',
|
||||
'Zadanie przywracania bazy dodane do kolejki. Otrzymasz powiadomienie po zakonczeniu.'
|
||||
=> 'Zadanie przywracania bazy dodane do kolejki. Otrzymasz powiadomienie po zakończeniu.',
|
||||
'Zadanie przywracania backupu uzytkownika dodane do kolejki.'
|
||||
=> 'Zadanie przywracania backupu użytkownika dodane do kolejki.',
|
||||
'Backup przed przywroceniem nie istnieje.' => 'Backup przed przywróceniem nie istnieje.',
|
||||
'Dodano zadanie przywracania backupu przed przywroceniem.' => 'Dodano zadanie przywracania backupu przed przywróceniem.',
|
||||
'Nie udalo sie usunac backupu przed przywroceniem.' => 'Nie udało się usunąć backupu przed przywróceniem.',
|
||||
'Backup przed przywroceniem zostal usuniety.' => 'Backup przed przywróceniem został usunięty.',
|
||||
'Nie udalo sie usunac backupu uzytkownika.' => 'Nie udało się usunąć backupu użytkownika.',
|
||||
'Backup uzytkownika zostal usuniety.' => 'Backup użytkownika został usunięty.',
|
||||
'Pobieranie backupow HITME.SQL jest wylaczone.' => 'Pobieranie backupów HITME.SQL jest wyłączone.',
|
||||
'Sekcja backupow uzytkownika jest wylaczona.' => 'Sekcja backupów użytkownika jest wyłączona.',
|
||||
'Usuwanie logow i wpisow zadan jest zablokowane przez konfiguracje.' => 'Usuwanie logów i wpisów zadań jest zablokowane przez konfigurację.',
|
||||
'Brak obsugi posix_kill na serwerze.' => 'Brak obsługi posix_kill na serwerze.',
|
||||
'Nie udalo sie anulowac zadania.' => 'Nie udało się anulować zadania.',
|
||||
'Wybrane zadania zostaly usuniete.' => 'Wybrane zadania zostały usunięte.',
|
||||
'Zadanie zostalo usuniete.' => 'Zadanie zostało usunięte.',
|
||||
'Zadanie zostalo anulowane.' => 'Zadanie zostało anulowane.',
|
||||
'Czy kontynuowac operacje?' => 'Czy kontynuować operację?',
|
||||
'Przywracanie backupu moze nadpisac obecne dane bazy. Czy kontynuowac?'
|
||||
=> 'Przywracanie backupu może nadpisać obecne dane bazy. Czy kontynuować?',
|
||||
'Przywracanie backupu uzytkownika moze nadpisac obecne dane bazy. Czy kontynuowac?'
|
||||
=> 'Przywracanie backupu użytkownika może nadpisać obecne dane bazy. Czy kontynuować?',
|
||||
'Przywrocenie backupu sprzed przywrocenia moze nadpisac obecne dane. Czy kontynuowac?'
|
||||
=> 'Przywrócenie backupu sprzed przywrócenia może nadpisać obecne dane. Czy kontynuować?',
|
||||
'Usuniecie backupu sprzed przywrocenia jest nieodwracalne. Czy kontynuowac?'
|
||||
=> 'Usunięcie backupu sprzed przywrócenia jest nieodwracalne. Czy kontynuować?',
|
||||
'Usuniesz backup uzytkownika. Tej operacji nie mozna cofnac. Czy kontynuowac?'
|
||||
=> 'Usuniesz backup użytkownika. Tej operacji nie można cofnąć. Czy kontynuować?',
|
||||
'Usuniesz log i wpis zadania. Tej operacji nie mozna cofnac. Czy kontynuowac?'
|
||||
=> 'Usuniesz log i wpis zadania. Tej operacji nie można cofnąć. Czy kontynuować?',
|
||||
'UWAGA: Czy na pewno anulowac zadanie?' => 'UWAGA: Czy na pewno anulować zadanie?',
|
||||
'UWAGA: Przywrocenie backupu sprzed przywrocenia moze nadpisac obecne dane. Kontynuowac?'
|
||||
=> 'UWAGA: Przywrócenie backupu sprzed przywrócenia może nadpisać obecne dane. Kontynuować?',
|
||||
'UWAGA: Usuniecie backupu sprzed przywrocenia jest nieodwracalne. Kontynuowac?'
|
||||
=> 'UWAGA: Usunięcie backupu sprzed przywrócenia jest nieodwracalne. Kontynuować?',
|
||||
'UWAGA: Usuniesz log i wpis zadania. Tej operacji nie mozna cofnac. Kontynuowac?'
|
||||
=> 'UWAGA: Usuniesz log i wpis zadania. Tej operacji nie można cofnąć. Kontynuować?',
|
||||
'UWAGA: Rozpoczynasz przywracanie backupu bazy danych. Operacja moze nadpisac obecne dane. Kontynuowac?'
|
||||
=> 'UWAGA: Rozpoczynasz przywracanie backupu bazy danych. Operacja może nadpisać obecne dane. Kontynuować?',
|
||||
'uzytkownika' => 'użytkownika',
|
||||
'Uzytkownika' => 'Użytkownika',
|
||||
'backupow' => 'backupów',
|
||||
'Backupow' => 'Backupów',
|
||||
'przywroceniem' => 'przywróceniem',
|
||||
'przywrocenia' => 'przywrócenia',
|
||||
'przywrocic' => 'przywrócić',
|
||||
'zrodlowa' => 'źródłowa',
|
||||
'zrodlowej' => 'źródłowej',
|
||||
'zaczynac' => 'zaczynać',
|
||||
'nieprawidlowa' => 'nieprawidłowa',
|
||||
'Nieprawidlowa' => 'Nieprawidłowa',
|
||||
'nieprawidlowe' => 'nieprawidłowe',
|
||||
'Nieprawidlowe' => 'Nieprawidłowe',
|
||||
'nieprawidlowy' => 'nieprawidłowy',
|
||||
'Nieprawidlowy' => 'Nieprawidłowy',
|
||||
'sciezka' => 'ścieżka',
|
||||
'Sciezka' => 'Ścieżka',
|
||||
'wyswietlenia' => 'wyświetlenia',
|
||||
'zadnych' => 'żadnych',
|
||||
'zadan' => 'zadań',
|
||||
'udalo' => 'udało',
|
||||
'zapisac' => 'zapisać',
|
||||
'sprawdzic' => 'sprawdzić',
|
||||
'utworzyc' => 'utworzyć',
|
||||
'mozna' => 'można',
|
||||
'zostalo' => 'zostało',
|
||||
'zostaly' => 'zostały',
|
||||
'usuniety' => 'usunięty',
|
||||
'usuniete' => 'usunięte',
|
||||
'cofnac' => 'cofnąć',
|
||||
'kontynuowac' => 'kontynuować',
|
||||
'miesiac' => 'miesiąc',
|
||||
'Miesiac' => 'Miesiąc',
|
||||
'nastepny' => 'następny',
|
||||
'Nastepny' => 'Następny',
|
||||
'rozpoczecia' => 'rozpoczęcia',
|
||||
'Rozpoczecia' => 'Rozpoczęcia',
|
||||
'postep' => 'postęp',
|
||||
'Postep' => 'Postęp',
|
||||
'koniecznosc' => 'konieczność',
|
||||
'Koniecznosc' => 'Konieczność',
|
||||
'odswiezanie' => 'odświeżanie',
|
||||
'Podglad' => 'Podgląd',
|
||||
'Blad' => 'Błąd',
|
||||
'zakonczone' => 'zakończone',
|
||||
'Zakonczone' => 'Zakończone',
|
||||
'recznie' => 'ręcznie',
|
||||
'powiadomienie po zakonczeniu' => 'powiadomienie po zakończeniu',
|
||||
'naglowki' => 'nagłówki',
|
||||
'wartosc' => 'wartość',
|
||||
'obslugi' => 'obsługi',
|
||||
];
|
||||
}
|
||||
uksort($replacements, static function ($a, $b) {
|
||||
return strlen((string)$b) <=> strlen((string)$a);
|
||||
});
|
||||
return str_replace(array_keys($replacements), array_values($replacements), $text);
|
||||
}
|
||||
|
||||
$pl = [];
|
||||
foreach ($en as $key => $_value) {
|
||||
$pl[$key] = pl_normalize_text((string)$key);
|
||||
}
|
||||
|
||||
return $pl;
|
||||
@@ -0,0 +1,42 @@
|
||||
# Ustawienia pluginu db-manager (MySQL/MariaDB).
|
||||
# Always_enable - gdy 1, plugin jest dostepny dla wszystkich uzytkownikow bez checkboxa w edycji konta.
|
||||
Always_enable=0
|
||||
# HITME_SQL_BACKUP_DIR - katalog z dumpami SQL przygotowanymi przez admina.
|
||||
HITME_SQL_BACKUP_DIR=/home/admin/mysql_backup
|
||||
# USER_SQL_BACKUP_DIR - katalog na backupy tworzone przez uzytkownika (DA_USER zostanie podmieniony).
|
||||
USER_SQL_BACKUP_DIR=/home/da_user/mysql_backups/user_backups
|
||||
# ALLOW_DOWNLOAD_HITME_SQL_BACKUP - czy uzytkownik moze pobierac backupy z HITME_SQL_BACKUP_DIR (true/false).
|
||||
ALLOW_DOWNLOAD_HITME_SQL_BACKUP=true
|
||||
# ENABLE_USER_BACKUPS - wlacza/ukrywa cala sekcje backupow uzytkownika (true/false).
|
||||
ENABLE_USER_BACKUPS=true
|
||||
# PRE_RESTORE_BACKUP_DIR - katalog na backupy wykonywane przed przywracaniem (DA_USER zostanie podmieniony).
|
||||
PRE_RESTORE_BACKUP_DIR=/home/da_user/mysql_backups/pre_restore_backups
|
||||
# PRE_RESTORE_USER_BACKUP_DIR - katalog na backupy wykonywane przed przywracaniem backupow uzytkownika (DA_USER zostanie podmieniony).
|
||||
PRE_RESTORE_USER_BACKUP_DIR=/home/da_user/mysql_backups/pre_restore_user_backups
|
||||
# ENABLE_PRE_RESTORE_BACKUP - czy tworzyc backup przed przywroceniem (true/false).
|
||||
ENABLE_PRE_RESTORE_BACKUP=true
|
||||
# ENABLE_USER_PRE_RESTORE_BACKUP - czy tworzyc backup przed przywroceniem backupu utworzonego przez uzytkownika (true/false).
|
||||
ENABLE_USER_PRE_RESTORE_BACKUP=true
|
||||
# SECURE_BACKUP - tworzy tymczasowego uzytkownika bazy (dbtmp__NAZWA_BAZY) tylko do backupu wskazanej bazy.
|
||||
SECURE_BACKUP=true
|
||||
# SECURE_RESTORE - tworzy tymczasowego uzytkownika bazy (dbtmp__NAZWA_BAZY) tylko do przywracania wskazanej bazy.
|
||||
SECURE_RESTORE=true
|
||||
# PREVENT_LOG_DELETION - blokuje usuwanie logow i wpisow zadan przez uzytkownika (true/false).
|
||||
PREVENT_LOG_DELETION=true
|
||||
# OWN_BACKUP_USER_RESTORE - przywracaj backupy uzytkownika tylko z kopii utworzonych przez plugin DB-Manager (true/false).
|
||||
# Gdy true, nowe backupy uzytkownika sa zapisywane jako root:root i oznaczane metadanymi pluginu.
|
||||
OWN_BACKUP_USER_RESTORE=true
|
||||
# CHECK_DATABASE_COLLISIONS - blokuje rownolegle zadania na tej samej bazie (backup/restore) (true/false).
|
||||
CHECK_DATABASE_COLLISIONS=true
|
||||
# ENABLE_OPERATION_LIMITS - globalny przelacznik limitow operacji backup/restore (true/false).
|
||||
ENABLE_OPERATION_LIMITS=true
|
||||
# Limity przywracania (0 = brak limitu): 5 minut / 1 godzina / 24 godziny.
|
||||
RESTORE_LIMIT_5MIN=10
|
||||
RESTORE_LIMIT_HOUR=30
|
||||
RESTORE_LIMIT_DAY=100
|
||||
# Limity backupu (0 = brak limitu): 5 minut / 1 godzina / 24 godziny.
|
||||
BACKUP_LIMIT_5MIN=10
|
||||
BACKUP_LIMIT_HOUR=30
|
||||
BACKUP_LIMIT_DAY=100
|
||||
# ENABLE_LOGS_OVERLAY - czy otwierac logi w oknie overlay (true/false).
|
||||
ENABLE_LOGS_OVERLAY=true
|
||||
@@ -0,0 +1,6 @@
|
||||
name=DB-Manager
|
||||
id=db-manager
|
||||
type=user
|
||||
author=HITME.PL
|
||||
version=1.9.11
|
||||
user_run_as=root
|
||||
Executable
+437
@@ -0,0 +1,437 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
ERROR_MSG=""
|
||||
BACKUP_OK=0
|
||||
DA_USER=""
|
||||
LOG_FILE=""
|
||||
TMP_DB_USER="tmprestore"
|
||||
TMP_DB_PASS=""
|
||||
TMP_DB_ACTIVE=0
|
||||
SECURE_BACKUP="true"
|
||||
OWN_BACKUP_USER_RESTORE="true"
|
||||
|
||||
JOB_FILE="${1:-}"
|
||||
if [ -z "$JOB_FILE" ] || [ ! -f "$JOB_FILE" ]; then
|
||||
echo "db-manager: missing job file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
urlencode() {
|
||||
local s="$1"
|
||||
local out=""
|
||||
local i c
|
||||
for ((i=0; i<${#s}; i++)); do
|
||||
c="${s:i:1}"
|
||||
case "$c" in
|
||||
[a-zA-Z0-9.~_-]) out+="$c" ;;
|
||||
' ') out+='%20' ;;
|
||||
*) printf -v c '%%%02X' "'$c"; out+="$c" ;;
|
||||
esac
|
||||
done
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
notify_user() {
|
||||
local code="$1"
|
||||
if [ -z "${DA_USER:-}" ]; then
|
||||
return
|
||||
fi
|
||||
local subject message
|
||||
if [ "$code" -eq 0 ] && [ "$BACKUP_OK" -eq 1 ]; then
|
||||
subject="Backup bazy danych zakonczony pomyslnie"
|
||||
message="Backup bazy ${DB_NAME:-} zostal wykonany."
|
||||
else
|
||||
subject="Blad wykonywania backupu bazy danych"
|
||||
if [ -n "$ERROR_MSG" ]; then
|
||||
message="Blad backupu: ${ERROR_MSG}"
|
||||
else
|
||||
message="Blad backupu bazy ${DB_NAME:-}. Log: ${LOG_FILE:-brak}."
|
||||
fi
|
||||
if [ -n "${LOG_FILE:-}" ] && [ -f "$LOG_FILE" ]; then
|
||||
local log_content
|
||||
log_content="$(cat "$LOG_FILE")"
|
||||
message="${message}\n\nLog:\n${log_content}"
|
||||
fi
|
||||
fi
|
||||
local task_queue="/usr/local/directadmin/data/task.queue"
|
||||
local users="select1%3D$(urlencode "$DA_USER")"
|
||||
local line="action=notify&value=users&subject=$(urlencode "$subject")&message=$(urlencode "$message")&users=${users}"
|
||||
printf "%s\n" "$line" >> "$task_queue"
|
||||
}
|
||||
|
||||
db_lock_path_for_db() {
|
||||
local db_name="$1"
|
||||
local safe
|
||||
safe="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_.-' '_')"
|
||||
printf '%s/data/lock/%s.lock' "$PLUGIN_DIR" "$safe"
|
||||
}
|
||||
|
||||
release_db_lock() {
|
||||
local db_name lock_path
|
||||
db_name="${DB_NAME:-}"
|
||||
if [ -z "$db_name" ] || [ -z "${PLUGIN_DIR:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
lock_path="$(db_lock_path_for_db "$db_name")"
|
||||
if [ -f "$lock_path" ]; then
|
||||
rm -f "$lock_path" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
on_exit() {
|
||||
local code="$1"
|
||||
cleanup_tmp_db_user
|
||||
notify_user "$code"
|
||||
}
|
||||
trap 'on_exit $?' EXIT
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$JOB_FILE"
|
||||
|
||||
DA_USER="${DA_USER:-}"
|
||||
DB_NAME="${DB_NAME:-}"
|
||||
DATE_DIR="${DATE_DIR:-}"
|
||||
|
||||
if [ -z "$DA_USER" ] || [ -z "$DB_NAME" ]; then
|
||||
echo "Missing DA_USER or DB_NAME in job file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
JOB_BASE="$(basename "$JOB_FILE")"
|
||||
JOB_ID="${JOB_BASE%%.*}"
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CONFIG_FILE="${PLUGIN_DIR}/plugin-settings.conf"
|
||||
LOG_DIR="${PLUGIN_DIR}/data/logs"
|
||||
LOG_FILE="${LOG_DIR}/${JOB_ID}.log"
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
fail() {
|
||||
local msg="$1"
|
||||
ERROR_MSG="$msg"
|
||||
echo "$msg" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
fail "Missing ${CONFIG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
USER_SQL_BACKUP_DIR="${USER_SQL_BACKUP_DIR:-/home/da_user/mysql_backups/user_backups}"
|
||||
SECURE_BACKUP="${SECURE_BACKUP:-true}"
|
||||
OWN_BACKUP_USER_RESTORE="${OWN_BACKUP_USER_RESTORE:-true}"
|
||||
SECURE_BACKUP="$(echo "$SECURE_BACKUP" | tr '[:upper:]' '[:lower:]')"
|
||||
OWN_BACKUP_USER_RESTORE="$(echo "$OWN_BACKUP_USER_RESTORE" | tr '[:upper:]' '[:lower:]')"
|
||||
if [ "$SECURE_BACKUP" = "1" ] || [ "$SECURE_BACKUP" = "yes" ]; then
|
||||
SECURE_BACKUP="true"
|
||||
fi
|
||||
if [ "$OWN_BACKUP_USER_RESTORE" = "1" ] || [ "$OWN_BACKUP_USER_RESTORE" = "yes" ]; then
|
||||
OWN_BACKUP_USER_RESTORE="true"
|
||||
fi
|
||||
|
||||
apply_da_user() {
|
||||
local value="$1"
|
||||
value="${value//DA_USER/$DA_USER}"
|
||||
value="${value//da_user/$DA_USER}"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
enforce_root_backup_tree_ownership() {
|
||||
local root_dir="$1"
|
||||
if [ "$(id -u)" -ne 0 ] || [ -z "$root_dir" ] || [ -L "$root_dir" ]; then
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$root_dir" 2>/dev/null || true
|
||||
find "$root_dir" \( -type d -o -type f \) -exec chown root:root {} + 2>/dev/null || true
|
||||
}
|
||||
|
||||
USER_SQL_BACKUP_DIR="$(apply_da_user "$USER_SQL_BACKUP_DIR")"
|
||||
TARGET_ROOT="$USER_SQL_BACKUP_DIR"
|
||||
MYSQL_BACKUPS_ROOT="$(apply_da_user "/home/da_user/mysql_backups")"
|
||||
enforce_root_backup_tree_ownership "$MYSQL_BACKUPS_ROOT"
|
||||
|
||||
if [[ "$DB_NAME" != "${DA_USER}_"* && "$DB_NAME" != "$DA_USER" ]]; then
|
||||
fail "DB_NAME does not belong to user"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
build_tmp_db_user() {
|
||||
local db_name="$1"
|
||||
local cleaned name
|
||||
cleaned="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_' '_')"
|
||||
name="dbtmp__${cleaned}"
|
||||
if [ "${#name}" -gt 32 ]; then
|
||||
name="${name:0:32}"
|
||||
fi
|
||||
if [ -z "$name" ]; then
|
||||
name="tmprestore"
|
||||
fi
|
||||
printf '%s' "$name"
|
||||
}
|
||||
|
||||
TMP_DB_USER="$(build_tmp_db_user "$DB_NAME")"
|
||||
|
||||
if [ -z "$DATE_DIR" ]; then
|
||||
DATE_DIR="$(date +%d-%m-%Y_%H_%M)"
|
||||
if [ -d "${TARGET_ROOT%/}/${DATE_DIR}" ]; then
|
||||
DATE_DIR="$(date +%d-%m-%Y_%H_%M_%S)"
|
||||
fi
|
||||
fi
|
||||
if [[ "$DATE_DIR" == *".."* || "$DATE_DIR" == */* ]]; then
|
||||
fail "Invalid DATE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TARGET_DIR="${TARGET_ROOT%/}/${DATE_DIR}"
|
||||
mkdir -p "$TARGET_DIR"
|
||||
chmod 700 "$TARGET_DIR" || true
|
||||
MARKER_FILE="${TARGET_DIR}/.db-manager-owned"
|
||||
{
|
||||
echo "OWNER=db-manager"
|
||||
echo "DA_USER=${DA_USER}"
|
||||
echo "DATE_DIR=${DATE_DIR}"
|
||||
echo "CREATED_TS=$(date +%s)"
|
||||
} > "$MARKER_FILE"
|
||||
chmod 600 "$MARKER_FILE" 2>/dev/null || true
|
||||
if [ "$(id -u)" -eq 0 ] && [ "$OWN_BACKUP_USER_RESTORE" = "true" ]; then
|
||||
chown root:root "$TARGET_DIR" "$MARKER_FILE" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
TARGET_FILE="${TARGET_DIR}/${DB_NAME}.sql.gz"
|
||||
if [ -e "$TARGET_FILE" ]; then
|
||||
IDX=1
|
||||
while [ -e "${TARGET_DIR}/${DB_NAME}_${IDX}.sql.gz" ]; do
|
||||
IDX=$((IDX+1))
|
||||
done
|
||||
TARGET_FILE="${TARGET_DIR}/${DB_NAME}_${IDX}.sql.gz"
|
||||
fi
|
||||
|
||||
MYSQL_CONF="/usr/local/directadmin/conf/mysql.conf"
|
||||
if [ ! -f "$MYSQL_CONF" ]; then
|
||||
fail "Missing ${MYSQL_CONF}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MYSQL_USER=""
|
||||
MYSQL_PASS=""
|
||||
MYSQL_HOST=""
|
||||
MYSQL_PORT=""
|
||||
MYSQL_SOCKET=""
|
||||
while IFS='=' read -r key val; do
|
||||
key="${key%%[[:space:]]*}"
|
||||
val="${val##[[:space:]]}"
|
||||
case "$key" in
|
||||
user) MYSQL_USER="$val" ;;
|
||||
passwd) MYSQL_PASS="$val" ;;
|
||||
host) MYSQL_HOST="$val" ;;
|
||||
port) MYSQL_PORT="$val" ;;
|
||||
socket) MYSQL_SOCKET="$val" ;;
|
||||
esac
|
||||
done < <(grep -v '^[[:space:]]*#' "$MYSQL_CONF" | grep -v '^[[:space:]]*$')
|
||||
|
||||
MYSQL_OPTS=()
|
||||
if [ -n "$MYSQL_USER" ]; then MYSQL_OPTS+=("-u${MYSQL_USER}"); fi
|
||||
if [ -n "$MYSQL_PASS" ]; then MYSQL_OPTS+=("-p${MYSQL_PASS}"); fi
|
||||
if [ -n "$MYSQL_HOST" ]; then MYSQL_OPTS+=("-h${MYSQL_HOST}"); fi
|
||||
if [ -n "$MYSQL_PORT" ] && [ "$MYSQL_PORT" != "0" ]; then MYSQL_OPTS+=("-P${MYSQL_PORT}"); fi
|
||||
if [ -n "$MYSQL_SOCKET" ]; then MYSQL_OPTS+=("--socket=${MYSQL_SOCKET}"); fi
|
||||
|
||||
PATH="/usr/local/mysql/bin:/usr/local/mariadb/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"
|
||||
export PATH
|
||||
|
||||
resolve_bin() {
|
||||
local name path
|
||||
for name in "$@"; do
|
||||
if path="$(command -v "$name" 2>/dev/null)"; then
|
||||
printf '%s' "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
sql_escape() {
|
||||
local v="$1"
|
||||
v="${v//\\/\\\\}"
|
||||
v="${v//\'/\\\'}"
|
||||
printf '%s' "$v"
|
||||
}
|
||||
|
||||
random_password() {
|
||||
rand_chars() {
|
||||
local charset="$1"
|
||||
local need="$2"
|
||||
local out="" chunk
|
||||
while [ "${#out}" -lt "$need" ]; do
|
||||
chunk="$(LC_ALL=C tr -dc "$charset" </dev/urandom 2>/dev/null | head -c $((need - ${#out})) || true)"
|
||||
if [ -z "$chunk" ]; then
|
||||
break
|
||||
fi
|
||||
out+="$chunk"
|
||||
done
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
local lower='abcdefghijklmnopqrstuvwxyz'
|
||||
local upper='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
local digits='0123456789'
|
||||
local special='-'
|
||||
local all="${lower}${upper}${digits}${special}"
|
||||
local seed extra mixed out tmp
|
||||
local -a chars=()
|
||||
local i j
|
||||
|
||||
seed="$(rand_chars "$lower" 1)$(rand_chars "$upper" 1)$(rand_chars "$digits" 1)$(rand_chars "$special" 1)"
|
||||
extra="$(rand_chars "$all" 20)"
|
||||
mixed="${seed}${extra}"
|
||||
|
||||
if [ "${#mixed}" -lt 24 ]; then
|
||||
mixed="aA1-"
|
||||
while [ "${#mixed}" -lt 24 ]; do
|
||||
mixed="${mixed}aA1-"
|
||||
done
|
||||
mixed="${mixed:0:24}"
|
||||
fi
|
||||
|
||||
for ((i=0; i<${#mixed}; i++)); do
|
||||
chars+=("${mixed:i:1}")
|
||||
done
|
||||
for ((i=${#chars[@]}-1; i>0; i--)); do
|
||||
j=$((RANDOM % (i + 1)))
|
||||
tmp="${chars[i]}"
|
||||
chars[i]="${chars[j]}"
|
||||
chars[j]="$tmp"
|
||||
done
|
||||
|
||||
out=""
|
||||
for tmp in "${chars[@]}"; do
|
||||
out+="$tmp"
|
||||
done
|
||||
printf '%s' "${out:0:24}"
|
||||
}
|
||||
|
||||
cleanup_tmp_db_user() {
|
||||
if [ "${TMP_DB_ACTIVE:-0}" -ne 1 ]; then
|
||||
return
|
||||
fi
|
||||
if [ -z "${MYSQL_BIN:-}" ]; then
|
||||
return
|
||||
fi
|
||||
local user_esc
|
||||
user_esc="$(sql_escape "$TMP_DB_USER")"
|
||||
local sql="
|
||||
DROP USER IF EXISTS '${user_esc}'@'localhost';
|
||||
DROP USER IF EXISTS '${user_esc}'@'127.0.0.1';
|
||||
FLUSH PRIVILEGES;"
|
||||
set +e
|
||||
"${MYSQL_BIN}" "${MYSQL_OPTS[@]}" -e "$sql" >>"$LOG_FILE" 2>&1
|
||||
set -e
|
||||
TMP_DB_ACTIVE=0
|
||||
}
|
||||
|
||||
create_tmp_db_user_for_backup() {
|
||||
TMP_DB_PASS="$(random_password)"
|
||||
local user_esc pass_esc db_esc sql
|
||||
user_esc="$(sql_escape "$TMP_DB_USER")"
|
||||
pass_esc="$(sql_escape "$TMP_DB_PASS")"
|
||||
db_esc="${DB_NAME//\`/\`\`}"
|
||||
sql="
|
||||
DROP USER IF EXISTS '${user_esc}'@'localhost';
|
||||
DROP USER IF EXISTS '${user_esc}'@'127.0.0.1';
|
||||
CREATE USER '${user_esc}'@'localhost' IDENTIFIED BY '${pass_esc}';
|
||||
CREATE USER '${user_esc}'@'127.0.0.1' IDENTIFIED BY '${pass_esc}';
|
||||
GRANT SELECT, SHOW VIEW, EVENT, TRIGGER, LOCK TABLES ON \`${db_esc}\`.* TO '${user_esc}'@'localhost';
|
||||
GRANT SELECT, SHOW VIEW, EVENT, TRIGGER, LOCK TABLES ON \`${db_esc}\`.* TO '${user_esc}'@'127.0.0.1';
|
||||
FLUSH PRIVILEGES;"
|
||||
set +e
|
||||
"${MYSQL_BIN}" "${MYSQL_OPTS[@]}" -e "$sql" >>"$LOG_FILE" 2>&1
|
||||
local rc=$?
|
||||
set -e
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
TMP_DB_ACTIVE=1
|
||||
return 0
|
||||
}
|
||||
|
||||
if ! MYSQLDUMP_BIN="$(resolve_bin mysqldump mariadb-dump)"; then
|
||||
fail "mysqldump client not found (checked: mysqldump, mariadb-dump; PATH=${PATH})"
|
||||
exit 1
|
||||
fi
|
||||
if ! MYSQL_BIN="$(resolve_bin mysql mariadb)"; then
|
||||
fail "mysql client not found (checked: mysql, mariadb; PATH=${PATH})"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MYSQL_ADMIN_CMD=("$MYSQL_BIN" "${MYSQL_OPTS[@]}" -N -B)
|
||||
MYSQL_TABLES_CMD=("${MYSQL_ADMIN_CMD[@]}")
|
||||
MYSQLDUMP_CONN_OPTS=("${MYSQL_OPTS[@]}")
|
||||
if [ "$SECURE_BACKUP" = "true" ]; then
|
||||
if ! create_tmp_db_user_for_backup; then
|
||||
fail "Nie udalo sie utworzyc tymczasowego uzytkownika bazy dla backupu."
|
||||
exit 1
|
||||
fi
|
||||
MYSQLDUMP_CONN_OPTS=("-u${TMP_DB_USER}" "-p${TMP_DB_PASS}")
|
||||
if [ -n "$MYSQL_SOCKET" ]; then
|
||||
MYSQLDUMP_CONN_OPTS+=("--socket=${MYSQL_SOCKET}" "-hlocalhost")
|
||||
else
|
||||
MYSQLDUMP_CONN_OPTS+=("-h127.0.0.1")
|
||||
if [ -n "$MYSQL_PORT" ] && [ "$MYSQL_PORT" != "0" ]; then
|
||||
MYSQLDUMP_CONN_OPTS+=("-P${MYSQL_PORT}")
|
||||
fi
|
||||
fi
|
||||
MYSQL_TABLES_CMD=("$MYSQL_BIN" "${MYSQLDUMP_CONN_OPTS[@]}" -N -B)
|
||||
fi
|
||||
MYSQLDUMP_CMD=("$MYSQLDUMP_BIN" "${MYSQLDUMP_CONN_OPTS[@]}" --single-transaction --routines --events --triggers --verbose --databases "$DB_NAME")
|
||||
|
||||
echo "Rozpoczynam backup bazy: ${DB_NAME}" >> "$LOG_FILE"
|
||||
echo "Tryb backupu: verbose" >> "$LOG_FILE"
|
||||
if [ "$SECURE_BACKUP" = "true" ]; then
|
||||
echo "Tryb bezpieczny backupu: ON (tymczasowy uzytkownik bazy)" >> "$LOG_FILE"
|
||||
else
|
||||
echo "Tryb bezpieczny backupu: OFF" >> "$LOG_FILE"
|
||||
fi
|
||||
DB_NAME_BT="${DB_NAME//\`/\`\`}"
|
||||
set +e
|
||||
TABLES_RAW="$("${MYSQL_TABLES_CMD[@]}" -e "SHOW TABLES FROM \`${DB_NAME_BT}\`" 2>>"$LOG_FILE")"
|
||||
TABLES_CODE=$?
|
||||
set -e
|
||||
if [ "$TABLES_CODE" -eq 0 ]; then
|
||||
if [ -n "$TABLES_RAW" ]; then
|
||||
echo "Tabele do backupu:" >> "$LOG_FILE"
|
||||
while IFS= read -r tbl; do
|
||||
[ -z "$tbl" ] && continue
|
||||
echo " - ${tbl}" >> "$LOG_FILE"
|
||||
done <<< "$TABLES_RAW"
|
||||
else
|
||||
echo "Baza nie zawiera tabel." >> "$LOG_FILE"
|
||||
fi
|
||||
else
|
||||
echo "Nie udalo sie pobrac listy tabel dla ${DB_NAME}." >> "$LOG_FILE"
|
||||
fi
|
||||
|
||||
set +e
|
||||
${MYSQLDUMP_CMD[@]} 2>>"$LOG_FILE" | gzip -c > "$TARGET_FILE"
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
if [ "$EXIT_CODE" -ne 0 ]; then
|
||||
rm -f "$TARGET_FILE"
|
||||
fail "Backup bazy ${DB_NAME} zakonczony bledem."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(id -u)" -eq 0 ] && [ "$OWN_BACKUP_USER_RESTORE" = "true" ]; then
|
||||
chmod 700 "$TARGET_DIR" 2>/dev/null || true
|
||||
chmod 600 "$TARGET_FILE" "$MARKER_FILE" 2>/dev/null || true
|
||||
chown root:root "$TARGET_DIR" "$TARGET_FILE" "$MARKER_FILE" 2>/dev/null || true
|
||||
else
|
||||
chown "${DA_USER}:${DA_USER}" "$TARGET_FILE" 2>/dev/null || true
|
||||
fi
|
||||
enforce_root_backup_tree_ownership "$MYSQL_BACKUPS_ROOT"
|
||||
echo "Backup zapisany: ${TARGET_FILE}" >> "$LOG_FILE"
|
||||
BACKUP_OK=1
|
||||
exit 0
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CPIF="/usr/local/directadmin/data/admin/custom_package_items.conf"
|
||||
CPIL='db_manager=type=checkbox&string=Włącz zarządzanie backupami baz danych&desc=Włącz zarządzanie backupami baz danych&default=no'
|
||||
|
||||
plugin_always_enabled() {
|
||||
local settings_file="$PLUGIN_DIR/plugin-settings.conf"
|
||||
local line key value
|
||||
|
||||
[ -r "$settings_file" ] || return 1
|
||||
while IFS= read -r line; do
|
||||
line="${line%%#*}"
|
||||
key="${line%%=*}"
|
||||
value="${line#*=}"
|
||||
key="$(printf '%s' "$key" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
||||
if [ "$key" != "always_enable" ]; then
|
||||
continue
|
||||
fi
|
||||
value="$(printf '%s' "$value" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]\"'"'"'')"
|
||||
case "$value" in
|
||||
1|true|yes|y|on) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
done < "$settings_file"
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
bootstrap_custom_package_item() {
|
||||
local install_cpif=0
|
||||
local count
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "db-manager: warning: not running as root, cannot update $CPIF" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -f "$CPIF" ]; then
|
||||
sed -i '/^db-manager=/d' "$CPIF" || true
|
||||
sed -i '/^db_manager=/d' "$CPIF" || true
|
||||
count="$(grep -m1 -c '^db_manager=' "$CPIF" || true)"
|
||||
if [ "${count:-0}" -eq 0 ] && ! plugin_always_enabled; then
|
||||
install_cpif=1
|
||||
fi
|
||||
else
|
||||
if ! plugin_always_enabled; then
|
||||
install_cpif=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$install_cpif" -eq 1 ]; then
|
||||
printf '%s\n' "$CPIL" >> "$CPIF"
|
||||
fi
|
||||
|
||||
if [ -f "$CPIF" ]; then
|
||||
chown diradmin:diradmin "$CPIF" 2>/dev/null || true
|
||||
chmod 640 "$CPIF" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "${DB_MANAGER_INSTALL_LIB_ONLY:-0}" = "1" ]; then
|
||||
return 0 2>/dev/null || exit 0
|
||||
fi
|
||||
|
||||
# Ensure expected permissions and ownership so DirectAdmin can update plugin.conf.
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
chown -R diradmin:diradmin "$PLUGIN_DIR" || true
|
||||
else
|
||||
echo "db-manager: warning: not running as root, cannot chown to diradmin" >&2
|
||||
fi
|
||||
|
||||
chmod 755 "$PLUGIN_DIR" "$PLUGIN_DIR/hooks" "$PLUGIN_DIR/user" "$PLUGIN_DIR/scripts" "$PLUGIN_DIR/images" || true
|
||||
chmod 755 "$PLUGIN_DIR/user/index.html" "$PLUGIN_DIR/scripts/install.sh" "$PLUGIN_DIR/scripts/uninstall.sh" "$PLUGIN_DIR/scripts/restore.sh" "$PLUGIN_DIR/scripts/backup.sh" "$PLUGIN_DIR/scripts/worker.sh" || true
|
||||
chmod 644 "$PLUGIN_DIR/plugin.conf" "$PLUGIN_DIR/hooks/user_txt.html" "$PLUGIN_DIR/plugin-settings.conf" "$PLUGIN_DIR/user/enhanced.css" "$PLUGIN_DIR/user/evolution.css" "$PLUGIN_DIR/images/user_icon.svg" || true
|
||||
|
||||
DATA_DIR="$PLUGIN_DIR/data"
|
||||
mkdir -p "$DATA_DIR/jobs/pending" "$DATA_DIR/jobs/processing" "$DATA_DIR/jobs/done" "$DATA_DIR/logs" "$DATA_DIR/pids" "$DATA_DIR/cancel" "$DATA_DIR/csrf" "$DATA_DIR/lock"
|
||||
chmod 700 "$DATA_DIR" "$DATA_DIR/jobs" "$DATA_DIR/jobs/pending" "$DATA_DIR/jobs/processing" "$DATA_DIR/jobs/done" "$DATA_DIR/logs" "$DATA_DIR/pids" "$DATA_DIR/cancel" "$DATA_DIR/csrf" "$DATA_DIR/lock" || true
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
chown -R diradmin:diradmin "$DATA_DIR" || true
|
||||
fi
|
||||
|
||||
CRON_FILE="/etc/cron.d/db-manager"
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
echo "* * * * * root /usr/local/directadmin/plugins/db-manager/scripts/worker.sh >/dev/null 2>&1" > "$CRON_FILE"
|
||||
chmod 644 "$CRON_FILE" || true
|
||||
else
|
||||
echo "db-manager: warning: not running as root, cannot install cron job" >&2
|
||||
fi
|
||||
|
||||
bootstrap_custom_package_item
|
||||
|
||||
echo "db-manager: install complete"
|
||||
Executable
+723
@@ -0,0 +1,723 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
ERROR_MSG=""
|
||||
SUCCESS_MSG=""
|
||||
RESTORE_OK=0
|
||||
DA_USER=""
|
||||
PRE_BACKUP_PATH=""
|
||||
LOG_FILE=""
|
||||
TMP_DB_USER="tmprestore"
|
||||
TMP_DB_PASS=""
|
||||
TMP_DB_ACTIVE=0
|
||||
SECURE_RESTORE="true"
|
||||
SECURE_BACKUP="true"
|
||||
OWN_BACKUP_USER_RESTORE="true"
|
||||
|
||||
JOB_FILE="${1:-}"
|
||||
if [ -z "$JOB_FILE" ] || [ ! -f "$JOB_FILE" ]; then
|
||||
echo "db-manager: missing job file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
urlencode() {
|
||||
local s="$1"
|
||||
local out=""
|
||||
local i c
|
||||
for ((i=0; i<${#s}; i++)); do
|
||||
c="${s:i:1}"
|
||||
case "$c" in
|
||||
[a-zA-Z0-9.~_-]) out+="$c" ;;
|
||||
' ') out+='%20' ;;
|
||||
*) printf -v c '%%%02X' "'$c"; out+="$c" ;;
|
||||
esac
|
||||
done
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
notify_user() {
|
||||
local code="$1"
|
||||
if [ -z "${DA_USER:-}" ]; then
|
||||
return
|
||||
fi
|
||||
local subject message
|
||||
if [ "$code" -eq 0 ] && [ "$RESTORE_OK" -eq 1 ]; then
|
||||
subject="Przywracanie bazy danych zakonczone pomyslnie"
|
||||
if [ -n "$PRE_BACKUP_PATH" ]; then
|
||||
message="Przywracanie bazy ${DB_NAME:-} z dumpa ${DATE_DIR:-} zakonczone. Backup przed przywroceniem: ${PRE_BACKUP_PATH}."
|
||||
else
|
||||
message="Przywracanie bazy ${DB_NAME:-} z dumpa ${DATE_DIR:-} zakonczone."
|
||||
fi
|
||||
else
|
||||
subject="Blad przywracania bazy danych"
|
||||
if [ -n "$ERROR_MSG" ]; then
|
||||
message="Blad przywracania: ${ERROR_MSG}"
|
||||
else
|
||||
message="Blad przywracania bazy ${DB_NAME:-}. Log: ${LOG_FILE:-brak}."
|
||||
fi
|
||||
if [ -n "${LOG_FILE:-}" ] && [ -f "$LOG_FILE" ]; then
|
||||
local log_content
|
||||
log_content="$(cat "$LOG_FILE")"
|
||||
message="${message}\n\nLog:\n${log_content}"
|
||||
fi
|
||||
fi
|
||||
local task_queue="/usr/local/directadmin/data/task.queue"
|
||||
local users="select1%3D$(urlencode "$DA_USER")"
|
||||
local line="action=notify&value=users&subject=$(urlencode "$subject")&message=$(urlencode "$message")&users=${users}"
|
||||
printf "%s\n" "$line" >> "$task_queue"
|
||||
}
|
||||
|
||||
db_lock_path_for_db() {
|
||||
local db_name="$1"
|
||||
local safe
|
||||
safe="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_.-' '_')"
|
||||
printf '%s/data/lock/%s.lock' "$PLUGIN_DIR" "$safe"
|
||||
}
|
||||
|
||||
release_db_lock() {
|
||||
local db_name lock_path
|
||||
db_name="${DB_NAME:-}"
|
||||
if [ -z "$db_name" ] || [ -z "${PLUGIN_DIR:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
lock_path="$(db_lock_path_for_db "$db_name")"
|
||||
if [ -f "$lock_path" ]; then
|
||||
rm -f "$lock_path" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
on_exit() {
|
||||
local code="$1"
|
||||
cleanup_tmp_db_user
|
||||
notify_user "$code"
|
||||
}
|
||||
trap 'on_exit $?' EXIT
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$JOB_FILE"
|
||||
|
||||
DA_USER="${DA_USER:-}"
|
||||
DB_NAME="${DB_NAME:-}"
|
||||
SOURCE_DB_NAME="${SOURCE_DB_NAME:-}"
|
||||
DATE_DIR="${DATE_DIR:-}"
|
||||
DUMP_FILE="${DUMP_FILE:-}"
|
||||
BACKUP_SOURCE="${BACKUP_SOURCE:-hosting}"
|
||||
if [ -z "$SOURCE_DB_NAME" ]; then
|
||||
SOURCE_DB_NAME="$DB_NAME"
|
||||
fi
|
||||
|
||||
if [ -z "$DA_USER" ] || [ -z "$DB_NAME" ] || [ -z "$DATE_DIR" ]; then
|
||||
echo "Missing DA_USER, DB_NAME or DATE_DIR in job file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
JOB_BASE="$(basename "$JOB_FILE")"
|
||||
JOB_ID="${JOB_BASE%%.*}"
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CONFIG_FILE="${PLUGIN_DIR}/plugin-settings.conf"
|
||||
LOG_DIR="${PLUGIN_DIR}/data/logs"
|
||||
LOG_FILE="${LOG_DIR}/${JOB_ID}.log"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
fail() {
|
||||
local msg="$1"
|
||||
ERROR_MSG="$msg"
|
||||
echo "$msg" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
fail "Missing ${CONFIG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
HITME_SQL_BACKUP_DIR="${HITME_SQL_BACKUP_DIR:-/home/admin/mysql_backups}"
|
||||
USER_SQL_BACKUP_DIR="${USER_SQL_BACKUP_DIR:-/home/da_user/mysql_backups/user_backups}"
|
||||
PRE_RESTORE_BACKUP_DIR="${PRE_RESTORE_BACKUP_DIR:-/home/da_user/mysql_backups/pre_restore_backups}"
|
||||
PRE_RESTORE_USER_BACKUP_DIR="${PRE_RESTORE_USER_BACKUP_DIR:-/home/da_user/mysql_backups/pre_restore_user_backups}"
|
||||
ENABLE_PRE_RESTORE_BACKUP="${ENABLE_PRE_RESTORE_BACKUP:-true}"
|
||||
ENABLE_USER_PRE_RESTORE_BACKUP="${ENABLE_USER_PRE_RESTORE_BACKUP:-true}"
|
||||
SECURE_RESTORE="${SECURE_RESTORE:-true}"
|
||||
SECURE_BACKUP="${SECURE_BACKUP:-true}"
|
||||
OWN_BACKUP_USER_RESTORE="${OWN_BACKUP_USER_RESTORE:-true}"
|
||||
ENABLE_PRE_RESTORE_BACKUP="$(echo "$ENABLE_PRE_RESTORE_BACKUP" | tr '[:upper:]' '[:lower:]')"
|
||||
ENABLE_USER_PRE_RESTORE_BACKUP="$(echo "$ENABLE_USER_PRE_RESTORE_BACKUP" | tr '[:upper:]' '[:lower:]')"
|
||||
SECURE_RESTORE="$(echo "$SECURE_RESTORE" | tr '[:upper:]' '[:lower:]')"
|
||||
SECURE_BACKUP="$(echo "$SECURE_BACKUP" | tr '[:upper:]' '[:lower:]')"
|
||||
OWN_BACKUP_USER_RESTORE="$(echo "$OWN_BACKUP_USER_RESTORE" | tr '[:upper:]' '[:lower:]')"
|
||||
if [ "$ENABLE_PRE_RESTORE_BACKUP" = "1" ] || [ "$ENABLE_PRE_RESTORE_BACKUP" = "yes" ]; then
|
||||
ENABLE_PRE_RESTORE_BACKUP="true"
|
||||
fi
|
||||
if [ "$ENABLE_USER_PRE_RESTORE_BACKUP" = "1" ] || [ "$ENABLE_USER_PRE_RESTORE_BACKUP" = "yes" ]; then
|
||||
ENABLE_USER_PRE_RESTORE_BACKUP="true"
|
||||
fi
|
||||
if [ "$SECURE_RESTORE" = "1" ] || [ "$SECURE_RESTORE" = "yes" ]; then
|
||||
SECURE_RESTORE="true"
|
||||
fi
|
||||
if [ "$SECURE_BACKUP" = "1" ] || [ "$SECURE_BACKUP" = "yes" ]; then
|
||||
SECURE_BACKUP="true"
|
||||
fi
|
||||
if [ "$OWN_BACKUP_USER_RESTORE" = "1" ] || [ "$OWN_BACKUP_USER_RESTORE" = "yes" ]; then
|
||||
OWN_BACKUP_USER_RESTORE="true"
|
||||
fi
|
||||
|
||||
apply_da_user() {
|
||||
local value="$1"
|
||||
value="${value//DA_USER/$DA_USER}"
|
||||
value="${value//da_user/$DA_USER}"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
enforce_root_backup_tree_ownership() {
|
||||
local root_dir="$1"
|
||||
if [ "$(id -u)" -ne 0 ] || [ -z "$root_dir" ] || [ -L "$root_dir" ]; then
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$root_dir" 2>/dev/null || true
|
||||
find "$root_dir" \( -type d -o -type f \) -exec chown root:root {} + 2>/dev/null || true
|
||||
}
|
||||
|
||||
quote_sh_value() {
|
||||
local value="$1"
|
||||
value="${value//\'/\'\\\'\'}"
|
||||
printf "'%s'" "$value"
|
||||
}
|
||||
|
||||
set_job_var() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
grep -v "^${key}=" "$JOB_FILE" > "$tmp" 2>/dev/null || true
|
||||
printf "%s=%s\n" "$key" "$(quote_sh_value "$value")" >> "$tmp"
|
||||
mv "$tmp" "$JOB_FILE"
|
||||
chmod 600 "$JOB_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
USER_SQL_BACKUP_DIR="$(apply_da_user "$USER_SQL_BACKUP_DIR")"
|
||||
PRE_RESTORE_BACKUP_DIR="$(apply_da_user "$PRE_RESTORE_BACKUP_DIR")"
|
||||
PRE_RESTORE_USER_BACKUP_DIR="$(apply_da_user "$PRE_RESTORE_USER_BACKUP_DIR")"
|
||||
MYSQL_BACKUPS_ROOT="$(apply_da_user "/home/da_user/mysql_backups")"
|
||||
enforce_root_backup_tree_ownership "$MYSQL_BACKUPS_ROOT"
|
||||
|
||||
PRE_RESTORE_TARGET_DIR="$PRE_RESTORE_BACKUP_DIR"
|
||||
if [ "$BACKUP_SOURCE" = "user" ]; then
|
||||
PRE_RESTORE_TARGET_DIR="$PRE_RESTORE_USER_BACKUP_DIR"
|
||||
fi
|
||||
|
||||
PRE_RESTORE_ENABLED="$ENABLE_PRE_RESTORE_BACKUP"
|
||||
if [ "$BACKUP_SOURCE" = "user" ]; then
|
||||
PRE_RESTORE_ENABLED="$ENABLE_USER_PRE_RESTORE_BACKUP"
|
||||
fi
|
||||
|
||||
if [[ "$DATE_DIR" == *".."* || "$DATE_DIR" == */* ]]; then
|
||||
fail "Invalid DATE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DB_NAME" == *".."* || "$DB_NAME" == */* ]]; then
|
||||
fail "Invalid DB_NAME"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DB_NAME" != "${DA_USER}_"* && "$DB_NAME" != "$DA_USER" ]]; then
|
||||
fail "DB_NAME does not belong to user"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$SOURCE_DB_NAME" != "${DA_USER}_"* && "$SOURCE_DB_NAME" != "$DA_USER" ]]; then
|
||||
fail "SOURCE_DB_NAME does not belong to user"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
build_tmp_db_user() {
|
||||
local db_name="$1"
|
||||
local cleaned name
|
||||
cleaned="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_' '_')"
|
||||
name="dbtmp__${cleaned}"
|
||||
if [ "${#name}" -gt 32 ]; then
|
||||
name="${name:0:32}"
|
||||
fi
|
||||
if [ -z "$name" ]; then
|
||||
name="tmprestore"
|
||||
fi
|
||||
printf '%s' "$name"
|
||||
}
|
||||
|
||||
TMP_DB_USER="$(build_tmp_db_user "$DB_NAME")"
|
||||
|
||||
resolve_dump_path() {
|
||||
local source_name="${SOURCE_DB_NAME:-$DB_NAME}"
|
||||
local base_path="${HITME_SQL_BACKUP_DIR%/}/${DATE_DIR}/${source_name}"
|
||||
local candidate=""
|
||||
|
||||
if [ -n "${DUMP_FILE:-}" ]; then
|
||||
candidate="$DUMP_FILE"
|
||||
if [ -f "$candidate" ]; then
|
||||
printf '%s' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
for candidate in "${base_path}.sql.gz" "${base_path}.gz" "${base_path}.sql"; do
|
||||
if [ -f "$candidate" ]; then
|
||||
printf '%s' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
path_in_dir() {
|
||||
local path="$1"
|
||||
local dir="$2"
|
||||
local real_path real_dir
|
||||
real_path="$(realpath "$path" 2>/dev/null || true)"
|
||||
real_dir="$(realpath "$dir" 2>/dev/null || true)"
|
||||
if [ -z "$real_path" ] || [ -z "$real_dir" ]; then
|
||||
return 1
|
||||
fi
|
||||
if [ "$real_path" = "$real_dir" ]; then
|
||||
return 0
|
||||
fi
|
||||
case "$real_path" in
|
||||
"$real_dir"/*) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
path_in_any_dir() {
|
||||
local path="$1"
|
||||
shift
|
||||
local dir
|
||||
for dir in "$@"; do
|
||||
if [ -n "$dir" ] && path_in_dir "$path" "$dir"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
is_trusted_user_backup_dir() {
|
||||
local slot_dir="$1"
|
||||
local marker owner user uid
|
||||
marker="${slot_dir%/}/.db-manager-owned"
|
||||
if [ -f "$marker" ]; then
|
||||
owner="$(grep -E '^OWNER=' "$marker" 2>/dev/null | tail -n1 | cut -d= -f2- || true)"
|
||||
user="$(grep -E '^DA_USER=' "$marker" 2>/dev/null | tail -n1 | cut -d= -f2- || true)"
|
||||
if [ "$owner" = "db-manager" ] && [ "$user" = "$DA_USER" ]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
uid="$(stat -c '%u' "$slot_dir" 2>/dev/null || stat -f '%u' "$slot_dir" 2>/dev/null || echo '')"
|
||||
if [ "$uid" = "0" ]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
if ! DUMP_PATH="$(resolve_dump_path)"; then
|
||||
fail "Dump file not found for database ${SOURCE_DB_NAME} in ${HITME_SQL_BACKUP_DIR%/}/${DATE_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ALLOWED_DUMP_DIRS=()
|
||||
if [ "$BACKUP_SOURCE" = "user" ]; then
|
||||
ALLOWED_DUMP_DIRS+=("$USER_SQL_BACKUP_DIR" "$PRE_RESTORE_USER_BACKUP_DIR")
|
||||
else
|
||||
ALLOWED_DUMP_DIRS+=("$HITME_SQL_BACKUP_DIR" "$PRE_RESTORE_BACKUP_DIR")
|
||||
fi
|
||||
if ! path_in_any_dir "$DUMP_PATH" "${ALLOWED_DUMP_DIRS[@]}"; then
|
||||
fail "Dump file path is outside allowed directories."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$BACKUP_SOURCE" = "user" ] && [ "$OWN_BACKUP_USER_RESTORE" = "true" ]; then
|
||||
if ! is_trusted_user_backup_dir "$(dirname "$DUMP_PATH")"; then
|
||||
fail "Nie mozna przywrocic tej kopii zapasowej, poniewaz nie zostala utworzona przez plugin DB-Manager."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
MYSQL_CONF="/usr/local/directadmin/conf/mysql.conf"
|
||||
if [ ! -f "$MYSQL_CONF" ]; then
|
||||
fail "Missing ${MYSQL_CONF}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MYSQL_USER=""
|
||||
MYSQL_PASS=""
|
||||
MYSQL_HOST=""
|
||||
MYSQL_PORT=""
|
||||
MYSQL_SOCKET=""
|
||||
while IFS='=' read -r key val; do
|
||||
key="${key%%[[:space:]]*}"
|
||||
val="${val##[[:space:]]}"
|
||||
case "$key" in
|
||||
user) MYSQL_USER="$val" ;;
|
||||
passwd) MYSQL_PASS="$val" ;;
|
||||
host) MYSQL_HOST="$val" ;;
|
||||
port) MYSQL_PORT="$val" ;;
|
||||
socket) MYSQL_SOCKET="$val" ;;
|
||||
esac
|
||||
done < <(grep -v '^[[:space:]]*#' "$MYSQL_CONF" | grep -v '^[[:space:]]*$')
|
||||
|
||||
MYSQL_OPTS=()
|
||||
if [ -n "$MYSQL_USER" ]; then
|
||||
MYSQL_OPTS+=("-u${MYSQL_USER}")
|
||||
fi
|
||||
if [ -n "$MYSQL_PASS" ]; then
|
||||
MYSQL_OPTS+=("-p${MYSQL_PASS}")
|
||||
fi
|
||||
if [ -n "$MYSQL_HOST" ]; then
|
||||
MYSQL_OPTS+=("-h${MYSQL_HOST}")
|
||||
fi
|
||||
if [ -n "$MYSQL_PORT" ] && [ "$MYSQL_PORT" != "0" ]; then
|
||||
MYSQL_OPTS+=("-P${MYSQL_PORT}")
|
||||
fi
|
||||
if [ -n "$MYSQL_SOCKET" ]; then
|
||||
MYSQL_OPTS+=("--socket=${MYSQL_SOCKET}")
|
||||
fi
|
||||
|
||||
# Cron from /etc/cron.d may run with a minimal PATH.
|
||||
PATH="/usr/local/mysql/bin:/usr/local/mariadb/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"
|
||||
export PATH
|
||||
|
||||
resolve_bin() {
|
||||
local name path
|
||||
for name in "$@"; do
|
||||
if path="$(command -v "$name" 2>/dev/null)"; then
|
||||
printf '%s' "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
sql_escape() {
|
||||
local v="$1"
|
||||
v="${v//\\/\\\\}"
|
||||
v="${v//\'/\\\'}"
|
||||
printf '%s' "$v"
|
||||
}
|
||||
|
||||
random_password() {
|
||||
rand_chars() {
|
||||
local charset="$1"
|
||||
local need="$2"
|
||||
local out="" chunk
|
||||
while [ "${#out}" -lt "$need" ]; do
|
||||
chunk="$(LC_ALL=C tr -dc "$charset" </dev/urandom 2>/dev/null | head -c $((need - ${#out})) || true)"
|
||||
if [ -z "$chunk" ]; then
|
||||
break
|
||||
fi
|
||||
out+="$chunk"
|
||||
done
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
local lower='abcdefghijklmnopqrstuvwxyz'
|
||||
local upper='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
local digits='0123456789'
|
||||
local special='-'
|
||||
local all="${lower}${upper}${digits}${special}"
|
||||
local seed extra mixed out tmp
|
||||
local -a chars=()
|
||||
local i j
|
||||
|
||||
seed="$(rand_chars "$lower" 1)$(rand_chars "$upper" 1)$(rand_chars "$digits" 1)$(rand_chars "$special" 1)"
|
||||
extra="$(rand_chars "$all" 20)"
|
||||
mixed="${seed}${extra}"
|
||||
|
||||
if [ "${#mixed}" -lt 24 ]; then
|
||||
mixed="aA1-"
|
||||
while [ "${#mixed}" -lt 24 ]; do
|
||||
mixed="${mixed}aA1-"
|
||||
done
|
||||
mixed="${mixed:0:24}"
|
||||
fi
|
||||
|
||||
for ((i=0; i<${#mixed}; i++)); do
|
||||
chars+=("${mixed:i:1}")
|
||||
done
|
||||
for ((i=${#chars[@]}-1; i>0; i--)); do
|
||||
j=$((RANDOM % (i + 1)))
|
||||
tmp="${chars[i]}"
|
||||
chars[i]="${chars[j]}"
|
||||
chars[j]="$tmp"
|
||||
done
|
||||
|
||||
out=""
|
||||
for tmp in "${chars[@]}"; do
|
||||
out+="$tmp"
|
||||
done
|
||||
printf '%s' "${out:0:24}"
|
||||
}
|
||||
|
||||
cleanup_tmp_db_user() {
|
||||
if [ "${TMP_DB_ACTIVE:-0}" -ne 1 ]; then
|
||||
return
|
||||
fi
|
||||
if [ -z "${MYSQL_BIN:-}" ]; then
|
||||
return
|
||||
fi
|
||||
local user_esc sql
|
||||
user_esc="$(sql_escape "$TMP_DB_USER")"
|
||||
sql="
|
||||
DROP USER IF EXISTS '${user_esc}'@'localhost';
|
||||
DROP USER IF EXISTS '${user_esc}'@'127.0.0.1';
|
||||
FLUSH PRIVILEGES;"
|
||||
set +e
|
||||
"${MYSQL_BIN}" "${MYSQL_OPTS[@]}" -e "$sql" >>"$LOG_FILE" 2>&1
|
||||
set -e
|
||||
TMP_DB_ACTIVE=0
|
||||
}
|
||||
|
||||
create_tmp_db_user_for_restore() {
|
||||
TMP_DB_PASS="$(random_password)"
|
||||
local user_esc pass_esc db_esc sql
|
||||
user_esc="$(sql_escape "$TMP_DB_USER")"
|
||||
pass_esc="$(sql_escape "$TMP_DB_PASS")"
|
||||
db_esc="${DB_NAME//\`/\`\`}"
|
||||
sql="
|
||||
DROP USER IF EXISTS '${user_esc}'@'localhost';
|
||||
DROP USER IF EXISTS '${user_esc}'@'127.0.0.1';
|
||||
CREATE USER '${user_esc}'@'localhost' IDENTIFIED BY '${pass_esc}';
|
||||
CREATE USER '${user_esc}'@'127.0.0.1' IDENTIFIED BY '${pass_esc}';
|
||||
GRANT ALL PRIVILEGES ON \`${db_esc}\`.* TO '${user_esc}'@'localhost';
|
||||
GRANT ALL PRIVILEGES ON \`${db_esc}\`.* TO '${user_esc}'@'127.0.0.1';
|
||||
FLUSH PRIVILEGES;"
|
||||
set +e
|
||||
"${MYSQL_BIN}" "${MYSQL_OPTS[@]}" -e "$sql" >>"$LOG_FILE" 2>&1
|
||||
local rc=$?
|
||||
set -e
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
TMP_DB_ACTIVE=1
|
||||
return 0
|
||||
}
|
||||
|
||||
sanitize_sql_stream_for_target_db() {
|
||||
local src_db="$1"
|
||||
local dst_db="$2"
|
||||
local marker_file="${3:-}"
|
||||
awk -v src_db="$src_db" -v dst_db="$dst_db" -v marker_file="$marker_file" '
|
||||
function escape_ere(str, out, i, ch) {
|
||||
out = ""
|
||||
for (i = 1; i <= length(str); i++) {
|
||||
ch = substr(str, i, 1)
|
||||
if (ch ~ /[][(){}.^$*+?|\\]/) {
|
||||
out = out "\\" ch
|
||||
} else {
|
||||
out = out ch
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
BEGIN {
|
||||
src_bt = "`" src_db "`"
|
||||
dst_bt = "`" dst_db "`"
|
||||
src_bt_ere = escape_ere(src_bt)
|
||||
fq_pattern = src_bt_ere "[[:space:]]*\\.[[:space:]]*`"
|
||||
changed = 0
|
||||
needs_db_replace = (src_db != dst_db)
|
||||
}
|
||||
{
|
||||
line = $0
|
||||
lower = tolower(line)
|
||||
if (lower ~ /^[[:space:]]*create[[:space:]]+database[[:space:]]+/) {
|
||||
changed = 1
|
||||
next
|
||||
}
|
||||
if (lower ~ /^[[:space:]]*\/\*![0-9]+[[:space:]]+create[[:space:]]+database[[:space:]]+/) {
|
||||
changed = 1
|
||||
next
|
||||
}
|
||||
if (lower ~ /^[[:space:]]*drop[[:space:]]+database([[:space:]]+if[[:space:]]+exists)?[[:space:]]+/) {
|
||||
changed = 1
|
||||
next
|
||||
}
|
||||
if (lower ~ /^[[:space:]]*\/\*![0-9]+[[:space:]]+drop[[:space:]]+database([[:space:]]+if[[:space:]]+exists)?[[:space:]]+/) {
|
||||
changed = 1
|
||||
next
|
||||
}
|
||||
if (lower ~ /^[[:space:]]*use[[:space:]]+/ || lower ~ /^[[:space:]]*\/\*![0-9]+[[:space:]]+use[[:space:]]+/) {
|
||||
changed = 1
|
||||
print "USE " dst_bt ";"
|
||||
next
|
||||
}
|
||||
if (needs_db_replace) {
|
||||
if (gsub(fq_pattern, dst_bt ".`", line) > 0) {
|
||||
changed = 1
|
||||
}
|
||||
}
|
||||
print line
|
||||
}
|
||||
END {
|
||||
if (marker_file != "") {
|
||||
if (changed) {
|
||||
print "1" > marker_file
|
||||
} else {
|
||||
print "0" > marker_file
|
||||
}
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
if ! MYSQL_BIN="$(resolve_bin mysql mariadb)"; then
|
||||
fail "mysql client not found (checked: mysql, mariadb; PATH=${PATH})"
|
||||
exit 1
|
||||
fi
|
||||
MYSQL_ADMIN_CMD=("$MYSQL_BIN" "${MYSQL_OPTS[@]}")
|
||||
MYSQL_RESTORE_CMD=("${MYSQL_ADMIN_CMD[@]}")
|
||||
|
||||
MYSQLDUMP_BIN=""
|
||||
MYSQL_PREBACKUP_DUMP_CMD=()
|
||||
if [ "$PRE_RESTORE_ENABLED" = "true" ]; then
|
||||
if ! MYSQLDUMP_BIN="$(resolve_bin mysqldump mariadb-dump)"; then
|
||||
fail "mysqldump client not found (checked: mysqldump, mariadb-dump; PATH=${PATH})"
|
||||
exit 1
|
||||
fi
|
||||
MYSQL_PREBACKUP_DUMP_CMD=("$MYSQLDUMP_BIN" "${MYSQL_OPTS[@]}" --single-transaction --routines --events --triggers)
|
||||
fi
|
||||
|
||||
NEED_TMP_USER=0
|
||||
if [ "$SECURE_RESTORE" = "true" ]; then
|
||||
NEED_TMP_USER=1
|
||||
fi
|
||||
if [ "$PRE_RESTORE_ENABLED" = "true" ] && [ "$SECURE_BACKUP" = "true" ]; then
|
||||
NEED_TMP_USER=1
|
||||
fi
|
||||
if [ "$NEED_TMP_USER" -eq 1 ]; then
|
||||
if ! create_tmp_db_user_for_restore; then
|
||||
fail "Nie udalo sie utworzyc tymczasowego uzytkownika bazy."
|
||||
exit 1
|
||||
fi
|
||||
TMP_CONN_OPTS=("-u${TMP_DB_USER}" "-p${TMP_DB_PASS}")
|
||||
if [ -n "$MYSQL_SOCKET" ]; then
|
||||
TMP_CONN_OPTS+=("--socket=${MYSQL_SOCKET}" "-hlocalhost")
|
||||
else
|
||||
TMP_CONN_OPTS+=("-h127.0.0.1")
|
||||
if [ -n "$MYSQL_PORT" ] && [ "$MYSQL_PORT" != "0" ]; then
|
||||
TMP_CONN_OPTS+=("-P${MYSQL_PORT}")
|
||||
fi
|
||||
fi
|
||||
if [ "$SECURE_RESTORE" = "true" ]; then
|
||||
MYSQL_RESTORE_CMD=("$MYSQL_BIN" "${TMP_CONN_OPTS[@]}")
|
||||
fi
|
||||
if [ "$PRE_RESTORE_ENABLED" = "true" ] && [ "$SECURE_BACKUP" = "true" ]; then
|
||||
MYSQL_PREBACKUP_DUMP_CMD=("$MYSQLDUMP_BIN" "${TMP_CONN_OPTS[@]}" --single-transaction --routines --events --triggers)
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$SECURE_RESTORE" = "true" ]; then
|
||||
echo "Tryb bezpieczny przywracania: ON (tymczasowy uzytkownik bazy)" >> "$LOG_FILE"
|
||||
else
|
||||
echo "Tryb bezpieczny przywracania: OFF" >> "$LOG_FILE"
|
||||
fi
|
||||
if [ "$PRE_RESTORE_ENABLED" = "true" ]; then
|
||||
if [ "$SECURE_BACKUP" = "true" ]; then
|
||||
echo "Tryb bezpieczny backupu przed przywroceniem: ON (tymczasowy uzytkownik bazy)" >> "$LOG_FILE"
|
||||
else
|
||||
echo "Tryb bezpieczny backupu przed przywroceniem: OFF" >> "$LOG_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$PRE_RESTORE_ENABLED" = "true" ]; then
|
||||
DB_NAME_SQL="${DB_NAME//\'/\\\'}"
|
||||
set +e
|
||||
DB_EXISTS="$(${MYSQL_ADMIN_CMD[@]} -N -B -e "SHOW DATABASES LIKE '${DB_NAME_SQL}'" 2>>"$LOG_FILE")"
|
||||
EXISTS_CODE=$?
|
||||
set -e
|
||||
if [ "$EXISTS_CODE" -ne 0 ]; then
|
||||
fail "Nie udalo sie sprawdzic istnienia bazy ${DB_NAME}."
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$DB_EXISTS" ]; then
|
||||
mkdir -p "$PRE_RESTORE_TARGET_DIR"
|
||||
chmod 700 "$PRE_RESTORE_TARGET_DIR" || true
|
||||
BACKUP_DATE="$(date +%d-%m-%Y)"
|
||||
BASE_NAME="${DB_NAME}_przed_przywroceniem_${BACKUP_DATE}"
|
||||
TARGET_PATH="${PRE_RESTORE_TARGET_DIR%/}/${BASE_NAME}.sql.gz"
|
||||
IDX=1
|
||||
while [ -e "$TARGET_PATH" ]; do
|
||||
TARGET_PATH="${PRE_RESTORE_TARGET_DIR%/}/${BASE_NAME}_${IDX}.sql.gz"
|
||||
IDX=$((IDX+1))
|
||||
done
|
||||
|
||||
set +e
|
||||
${MYSQL_PREBACKUP_DUMP_CMD[@]} --databases "$DB_NAME" 2>>"$LOG_FILE" | gzip -c > "$TARGET_PATH"
|
||||
DUMP_CODE=$?
|
||||
set -e
|
||||
|
||||
if [ "$DUMP_CODE" -ne 0 ]; then
|
||||
rm -f "$TARGET_PATH"
|
||||
fail "Nie udalo sie wykonac backupu bazy ${DB_NAME}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
chown root:root "$PRE_RESTORE_TARGET_DIR" "$TARGET_PATH" 2>/dev/null || true
|
||||
chmod 700 "$PRE_RESTORE_TARGET_DIR" 2>/dev/null || true
|
||||
chmod 600 "$TARGET_PATH" 2>/dev/null || true
|
||||
else
|
||||
chown "${DA_USER}:${DA_USER}" "$TARGET_PATH" 2>/dev/null || true
|
||||
fi
|
||||
enforce_root_backup_tree_ownership "$MYSQL_BACKUPS_ROOT"
|
||||
PRE_BACKUP_PATH="$TARGET_PATH"
|
||||
set_job_var "PRE_BACKUP_PATH" "$PRE_BACKUP_PATH"
|
||||
echo "Wykonano backup przed przywroceniem: ${PRE_BACKUP_PATH}" >> "$LOG_FILE"
|
||||
else
|
||||
echo "Baza ${DB_NAME} nie istnieje - pomijam backup przed przywroceniem." >> "$LOG_FILE"
|
||||
fi
|
||||
else
|
||||
echo "Backup przed przywroceniem jest wylaczony dla tego typu przywracania." >> "$LOG_FILE"
|
||||
fi
|
||||
|
||||
DB_NAME_BT="${DB_NAME//\`/\`\`}"
|
||||
${MYSQL_ADMIN_CMD[@]} -e "CREATE DATABASE IF NOT EXISTS \`${DB_NAME_BT}\`" >> "$LOG_FILE" 2>&1
|
||||
|
||||
set +e
|
||||
SANITIZE_MARKER_FILE="$(mktemp)"
|
||||
printf "0\n" > "$SANITIZE_MARKER_FILE"
|
||||
if [ "$SOURCE_DB_NAME" != "$DB_NAME" ]; then
|
||||
echo "Sanityzacja dumpa przed importem: ${SOURCE_DB_NAME} -> ${DB_NAME}" >> "$LOG_FILE"
|
||||
fi
|
||||
if [[ "$DUMP_PATH" == *.gz ]]; then
|
||||
gunzip -c "$DUMP_PATH" \
|
||||
| sanitize_sql_stream_for_target_db "$SOURCE_DB_NAME" "$DB_NAME" "$SANITIZE_MARKER_FILE" \
|
||||
| ${MYSQL_RESTORE_CMD[@]} --database="$DB_NAME" --verbose >> "$LOG_FILE" 2>&1
|
||||
RESTORE_CODE=$?
|
||||
else
|
||||
cat "$DUMP_PATH" \
|
||||
| sanitize_sql_stream_for_target_db "$SOURCE_DB_NAME" "$DB_NAME" "$SANITIZE_MARKER_FILE" \
|
||||
| ${MYSQL_RESTORE_CMD[@]} --database="$DB_NAME" --verbose >> "$LOG_FILE" 2>&1
|
||||
RESTORE_CODE=$?
|
||||
fi
|
||||
set -e
|
||||
|
||||
SANITIZATION_REQUIRED="Nie"
|
||||
if [ -r "$SANITIZE_MARKER_FILE" ]; then
|
||||
if grep -q '^1$' "$SANITIZE_MARKER_FILE"; then
|
||||
SANITIZATION_REQUIRED="Tak"
|
||||
fi
|
||||
fi
|
||||
rm -f "$SANITIZE_MARKER_FILE"
|
||||
set_job_var "SANITIZATION_REQUIRED" "$SANITIZATION_REQUIRED"
|
||||
echo "Koniecznosc Sanityzacji: ${SANITIZATION_REQUIRED}" >> "$LOG_FILE"
|
||||
|
||||
if [ "$RESTORE_CODE" -ne 0 ]; then
|
||||
fail "Przywracanie bazy ${DB_NAME} zakonczone bledem."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RESTORE_OK=1
|
||||
SUCCESS_MSG="Przywracanie bazy ${DB_NAME} zakonczone pomyslnie."
|
||||
echo "$SUCCESS_MSG" >> "$LOG_FILE"
|
||||
|
||||
exit 0
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
CRON_FILE="/etc/cron.d/db-manager"
|
||||
CPIF="/usr/local/directadmin/data/admin/custom_package_items.conf"
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
rm -f "$CRON_FILE"
|
||||
if [ -f "$CPIF" ]; then
|
||||
sed -i '/^db-manager=/d' "$CPIF" || true
|
||||
sed -i '/^db_manager=/d' "$CPIF" || true
|
||||
chown diradmin:diradmin "$CPIF" 2>/dev/null || true
|
||||
chmod 640 "$CPIF" 2>/dev/null || true
|
||||
fi
|
||||
else
|
||||
echo "db-manager: warning: not running as root, cannot remove cron job or update custom package items" >&2
|
||||
fi
|
||||
|
||||
echo "db-manager: uninstall complete"
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA_DIR="$PLUGIN_DIR/data"
|
||||
JOB_DIR="$DATA_DIR/jobs/pending"
|
||||
PROCESSING_DIR="$DATA_DIR/jobs/processing"
|
||||
DONE_DIR="$DATA_DIR/jobs/done"
|
||||
WORKER_LOCK_DIR="$DATA_DIR/worker.lock"
|
||||
DB_LOCK_DIR="$DATA_DIR/lock"
|
||||
PID_DIR="$DATA_DIR/pids"
|
||||
CANCEL_DIR="$DATA_DIR/cancel"
|
||||
|
||||
mkdir -p "$JOB_DIR" "$PROCESSING_DIR" "$DONE_DIR" "$PID_DIR" "$CANCEL_DIR" "$DB_LOCK_DIR"
|
||||
|
||||
if ! mkdir "$WORKER_LOCK_DIR" 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
rmdir "$WORKER_LOCK_DIR" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
db_lock_path_for_db() {
|
||||
local db_name="$1"
|
||||
local safe
|
||||
safe="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_.-' '_')"
|
||||
printf '%s/%s.lock' "$DB_LOCK_DIR" "$safe"
|
||||
}
|
||||
|
||||
release_db_lock_later() {
|
||||
local lock_path="$1"
|
||||
if [ -z "$lock_path" ]; then
|
||||
return 0
|
||||
fi
|
||||
(
|
||||
sleep 10
|
||||
rm -f "$lock_path" 2>/dev/null || true
|
||||
) >/dev/null 2>&1 &
|
||||
}
|
||||
|
||||
while true; do
|
||||
JOB_FILE=$(ls -1 "$JOB_DIR"/*.env 2>/dev/null | head -n 1)
|
||||
if [ -z "${JOB_FILE:-}" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
BASE_NAME="$(basename "$JOB_FILE")"
|
||||
RUN_FILE="$PROCESSING_DIR/$BASE_NAME"
|
||||
mv "$JOB_FILE" "$RUN_FILE"
|
||||
JOB_ID="${BASE_NAME%.env}"
|
||||
PID_FILE="$PID_DIR/${JOB_ID}.pid"
|
||||
|
||||
JOB_TYPE="restore"
|
||||
DB_NAME=""
|
||||
DB_LOCK_FILE=""
|
||||
# shellcheck disable=SC1090
|
||||
source "$RUN_FILE" || true
|
||||
JOB_TYPE="${JOB_TYPE:-restore}"
|
||||
DB_NAME="${DB_NAME:-}"
|
||||
DB_LOCK_FILE="${DB_LOCK_FILE:-}"
|
||||
|
||||
LOCK_PATH=""
|
||||
if [ -n "$DB_LOCK_FILE" ]; then
|
||||
case "$DB_LOCK_FILE" in
|
||||
"$DB_LOCK_DIR"/*.lock) LOCK_PATH="$DB_LOCK_FILE" ;;
|
||||
*) LOCK_PATH="" ;;
|
||||
esac
|
||||
fi
|
||||
if [ -z "$LOCK_PATH" ] && [ -n "$DB_NAME" ]; then
|
||||
LOCK_PATH="$(db_lock_path_for_db "$DB_NAME")"
|
||||
fi
|
||||
|
||||
RUNNER="$PLUGIN_DIR/scripts/restore.sh"
|
||||
if [ "$JOB_TYPE" = "backup" ]; then
|
||||
RUNNER="$PLUGIN_DIR/scripts/backup.sh"
|
||||
fi
|
||||
|
||||
set +e
|
||||
"$RUNNER" "$RUN_FILE" &
|
||||
RUN_PID=$!
|
||||
echo "PID=${RUN_PID}" >> "$RUN_FILE"
|
||||
echo "${RUN_PID}" > "$PID_FILE"
|
||||
wait "$RUN_PID"
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
rm -f "$PID_FILE"
|
||||
|
||||
CANCEL_FLAG="${CANCEL_DIR}/${JOB_ID}.flag"
|
||||
if [ -f "$CANCEL_FLAG" ]; then
|
||||
rm -f "$CANCEL_FLAG"
|
||||
mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.cancel"
|
||||
elif [ "$EXIT_CODE" -eq 0 ]; then
|
||||
mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.ok"
|
||||
else
|
||||
mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.fail"
|
||||
fi
|
||||
|
||||
release_db_lock_later "$LOCK_PATH"
|
||||
done
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$index_path = __DIR__ . '/../user/index.html';
|
||||
$source = file_get_contents($index_path);
|
||||
if ($source === false) {
|
||||
fwrite(STDERR, "Cannot read user/index.html\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$marker = "\n\$params = read_params();";
|
||||
$pos = strpos($source, $marker);
|
||||
if ($pos === false) {
|
||||
fwrite(STDERR, "Cannot find main script marker\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$functions = substr($source, 0, $pos);
|
||||
$functions = preg_replace('/^#!.*\n/', '', $functions);
|
||||
$functions = preg_replace('/^<\?php\s*/', '', $functions);
|
||||
eval($functions);
|
||||
|
||||
function assert_same($expected, $actual, string $message): void
|
||||
{
|
||||
if ($expected !== $actual) {
|
||||
fwrite(STDERR, $message . "\nExpected: " . var_export($expected, true) . "\nActual: " . var_export($actual, true) . "\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$tmp_settings = tempnam(sys_get_temp_dir(), 'dbm-settings-');
|
||||
if ($tmp_settings === false) {
|
||||
fwrite(STDERR, "Cannot create temp settings file\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
file_put_contents($tmp_settings, "");
|
||||
$defaults = load_settings($tmp_settings);
|
||||
assert_same('0', $defaults['Always_enable'] ?? null, 'Always_enable defaults to 0');
|
||||
|
||||
file_put_contents($tmp_settings, "Always_enable=1\n");
|
||||
$settings = load_settings($tmp_settings);
|
||||
assert_same('1', $settings['Always_enable'] ?? null, 'Always_enable is loaded from plugin-settings.conf');
|
||||
|
||||
assert_same(true, is_plugin_enabled_for_user('__db_manager_missing_user__', 'db-manager', $settings), 'Always_enable=1 enables plugin without per-user checkbox state');
|
||||
} finally {
|
||||
@unlink($tmp_settings);
|
||||
}
|
||||
|
||||
echo "OK\n";
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
DB_MANAGER_INSTALL_LIB_ONLY=1 source "$ROOT_DIR/scripts/install.sh"
|
||||
|
||||
id() {
|
||||
echo 0
|
||||
}
|
||||
|
||||
PLUGIN_DIR="$TMP_DIR/plugin"
|
||||
CPIF="$TMP_DIR/custom_package_items.conf"
|
||||
mkdir -p "$PLUGIN_DIR"
|
||||
|
||||
printf 'Always_enable=1\n' > "$PLUGIN_DIR/plugin-settings.conf"
|
||||
bootstrap_custom_package_item
|
||||
if [ -e "$CPIF" ]; then
|
||||
echo "custom_package_items.conf should not be created when Always_enable=1" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'Always_enable=0\n' > "$PLUGIN_DIR/plugin-settings.conf"
|
||||
bootstrap_custom_package_item
|
||||
if ! grep -q '^db_manager=' "$CPIF"; then
|
||||
echo "db_manager checkbox should be installed when Always_enable=0" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "OK"
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SCRIPT="$PLUGIN_DIR/scripts/uninstall.sh"
|
||||
|
||||
if [ ! -x "$SCRIPT" ]; then
|
||||
chmod 755 "$SCRIPT" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
exec "$SCRIPT"
|
||||
@@ -0,0 +1,569 @@
|
||||
:root {
|
||||
--br-bg: #f4f6f8;
|
||||
--br-card: #ffffff;
|
||||
--br-text: #1f2a37;
|
||||
--br-muted: #6b7280;
|
||||
--br-border: #d9dee5;
|
||||
--br-accent: #0ea5a4;
|
||||
--br-accent-dark: #0f766e;
|
||||
--br-danger: #dc2626;
|
||||
--br-warn: #f59e0b;
|
||||
--br-shadow: 0 8px 24px rgba(31, 41, 55, 0.12);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--br-bg);
|
||||
color: var(--br-text);
|
||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
#content,
|
||||
#content .container,
|
||||
#content .container-fluid,
|
||||
#content .main-content,
|
||||
#content .page-content,
|
||||
.main-content,
|
||||
.page-content {
|
||||
max-width: none !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
.br-container {
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
.br-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.br-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
.br-card {
|
||||
background: var(--br-card);
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
box-shadow: var(--br-shadow);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.br-muted { color: var(--br-muted); }
|
||||
.br-alert {
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
margin: 8px 0;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.br-alert-error { background: #fff1f2; border-color: #fecdd3; color: #9f1239; }
|
||||
.br-alert-warn { background: #fffbeb; border-color: #fde68a; color: #92400e; }
|
||||
.br-alert-success { background: #ecfdf3; border-color: #bbf7d0; color: #166534; }
|
||||
.br-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
.br-modal {
|
||||
width: min(560px, 92vw);
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
border: 2px solid var(--br-border);
|
||||
box-shadow: var(--br-shadow);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 90vh;
|
||||
}
|
||||
.br-modal-log {
|
||||
width: 90vw;
|
||||
background: #ffffff;
|
||||
}
|
||||
.br-modal-danger {
|
||||
border-color: var(--br-danger);
|
||||
}
|
||||
.br-modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.br-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.br-modal-title {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
.br-modal-body {
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.br-modal-scroll {
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.br-log-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
.br-log-header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.br-log-meta {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin: 10px 0 12px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 10px;
|
||||
background: #f8fafc;
|
||||
font-size: 13px;
|
||||
}
|
||||
.br-text-danger {
|
||||
color: var(--br-danger);
|
||||
font-weight: 700;
|
||||
}
|
||||
.br-log-actions {
|
||||
margin: 8px 0 10px;
|
||||
}
|
||||
.br-picker-path {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.br-picker-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 10px;
|
||||
max-height: 45vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.br-picker-create {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.br-picker-create input {
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.br-picker-list li {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--br-border);
|
||||
}
|
||||
.br-picker-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.br-picker-list li:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.br-picker-list li.active {
|
||||
background: #e0f2fe;
|
||||
}
|
||||
.br-btn-small {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.br-actions-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.br-actions-row form {
|
||||
margin: 0;
|
||||
}
|
||||
.br-icon-btn {
|
||||
border: 1px solid var(--br-border);
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
.br-icon-btn:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.br-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
table-layout: auto;
|
||||
}
|
||||
.br-table th {
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--br-muted);
|
||||
background: #f3f4f6;
|
||||
padding: 10px;
|
||||
}
|
||||
.br-table td {
|
||||
padding: 10px;
|
||||
border-top: 1px solid var(--br-border);
|
||||
vertical-align: top;
|
||||
}
|
||||
.br-table tr:nth-child(even) td { background: #f9fafb; }
|
||||
.br-col-select { width: 50px; text-align: center; }
|
||||
.br-col-status { width: 120px; }
|
||||
.br-col-progress { width: 90px; }
|
||||
.br-col-mode { width: 180px; }
|
||||
.br-col-user { width: 140px; }
|
||||
.br-col-backup { width: 220px; overflow-wrap: anywhere; }
|
||||
.br-col-path { width: 260px; overflow-wrap: anywhere; }
|
||||
.br-col-target { width: 220px; overflow-wrap: anywhere; }
|
||||
.br-col-time { width: 150px; }
|
||||
.br-col-actions { width: 120px; }
|
||||
.br-table input[type="checkbox"] {
|
||||
accent-color: var(--br-accent);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.br-actions { display: flex; gap: 8px; align-items: center; margin: 10px 0; }
|
||||
.br-btn {
|
||||
background: var(--br-accent);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
.br-btn:hover { background: var(--br-accent-dark); }
|
||||
.br-btn:disabled,
|
||||
.br-btn[disabled] {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.br-btn:disabled:hover,
|
||||
.br-btn[disabled]:hover {
|
||||
background: var(--br-accent);
|
||||
}
|
||||
.br-btn-ghost {
|
||||
background: transparent;
|
||||
color: var(--br-text);
|
||||
border: 1px solid var(--br-border);
|
||||
}
|
||||
.br-btn-ghost:hover { background: #eef2f7; }
|
||||
.br-btn-ghost:disabled,
|
||||
.br-btn-ghost[disabled] {
|
||||
background: #f8fafc;
|
||||
color: var(--br-muted);
|
||||
border-color: var(--br-border);
|
||||
}
|
||||
.br-btn-ghost:disabled:hover,
|
||||
.br-btn-ghost[disabled]:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.br-btn-fogged {
|
||||
background: var(--br-accent);
|
||||
border: 1px solid var(--br-accent);
|
||||
color: #ffffff;
|
||||
opacity: 0.58;
|
||||
}
|
||||
.br-btn-fogged:hover {
|
||||
background: var(--br-accent);
|
||||
}
|
||||
.br-btn-fogged:disabled,
|
||||
.br-btn-fogged[disabled] {
|
||||
background: var(--br-accent);
|
||||
border-color: var(--br-accent);
|
||||
color: #ffffff;
|
||||
opacity: 0.58;
|
||||
}
|
||||
.br-btn-fogged:disabled:hover,
|
||||
.br-btn-fogged[disabled]:hover {
|
||||
background: var(--br-accent);
|
||||
}
|
||||
.br-btn-danger {
|
||||
background: var(--br-danger);
|
||||
}
|
||||
.br-btn-danger:hover { background: #b91c1c; }
|
||||
.br-link-danger { color: var(--br-danger); text-decoration: none; }
|
||||
.br-link-danger:hover { text-decoration: underline; }
|
||||
.br-inline-form { display: inline; }
|
||||
.br-link-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
.br-link-button:hover { text-decoration: underline; }
|
||||
.br-link-button.br-link-success { color: #15803d; }
|
||||
.br-link-button.br-link-danger { color: var(--br-danger); }
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
max-width: 680px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
background: #fff;
|
||||
}
|
||||
pre {
|
||||
background: #0f172a;
|
||||
color: #e2e8f0;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
overflow: auto;
|
||||
}
|
||||
a { color: var(--br-accent-dark); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.br-toast {
|
||||
position: fixed;
|
||||
bottom: 18px;
|
||||
right: 18px;
|
||||
background: #16a34a;
|
||||
color: #fff;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 12px 28px rgba(17, 24, 39, 0.25);
|
||||
z-index: 9999;
|
||||
font-weight: 600;
|
||||
}
|
||||
.br-toast-stack {
|
||||
position: fixed;
|
||||
top: 18px;
|
||||
right: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
z-index: 9999;
|
||||
}
|
||||
.br-toast-stack .br-toast {
|
||||
position: static;
|
||||
top: auto;
|
||||
bottom: auto;
|
||||
}
|
||||
.br-toast-error {
|
||||
background: #dc2626;
|
||||
top: 18px;
|
||||
bottom: auto;
|
||||
}
|
||||
.br-toast-success {
|
||||
background: #16a34a;
|
||||
}
|
||||
.br-status-line {
|
||||
margin: 6px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.br-progress {
|
||||
height: 8px;
|
||||
background: #e5e7eb;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
margin: 6px 0 12px;
|
||||
}
|
||||
.br-progress-bar {
|
||||
height: 100%;
|
||||
background: var(--br-accent);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.br-restore-layout {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: minmax(320px, 420px) minmax(0, 1fr);
|
||||
margin-top: 12px;
|
||||
}
|
||||
.br-restore-calendar,
|
||||
.br-restore-picker {
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
.br-calendar-controls {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.br-month-nav {
|
||||
display: grid;
|
||||
grid-template-columns: 36px 1fr 36px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.br-month-nav form {
|
||||
margin: 0;
|
||||
}
|
||||
.br-month-label {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
.br-month-btn {
|
||||
width: 36px;
|
||||
height: 32px;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
}
|
||||
.br-month-btn:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.br-calendar-grid {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.br-calendar-grid th,
|
||||
.br-calendar-grid td {
|
||||
border: 1px solid var(--br-border);
|
||||
text-align: center;
|
||||
padding: 4px;
|
||||
width: 14.28%;
|
||||
height: 38px;
|
||||
}
|
||||
.br-cal-day {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
border: 1px solid var(--br-border);
|
||||
background: #f8fafc;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.br-cal-day.is-selected {
|
||||
background: var(--br-accent);
|
||||
color: #fff;
|
||||
border-color: var(--br-accent);
|
||||
}
|
||||
.br-cal-day-disabled {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
color: var(--br-muted);
|
||||
opacity: 0.45;
|
||||
}
|
||||
.br-cal-empty {
|
||||
background: #f9fafb;
|
||||
}
|
||||
.br-time-list {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.br-time-item {
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 999px;
|
||||
padding: 6px 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: #fff;
|
||||
}
|
||||
.br-time-item input {
|
||||
margin: 0;
|
||||
}
|
||||
@media (max-width: 980px) {
|
||||
.br-restore-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
.br-tab-header {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.br-main-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.br-main-sections {
|
||||
position: relative;
|
||||
}
|
||||
.br-main-sections.is-animating {
|
||||
overflow: hidden;
|
||||
}
|
||||
.br-main-sections.is-animating .br-main-section {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.br-main-tab.is-active {
|
||||
background: var(--br-accent);
|
||||
color: #fff;
|
||||
border-color: var(--br-accent);
|
||||
}
|
||||
.br-main-section.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
.br-user-tab.is-active {
|
||||
background: var(--br-accent);
|
||||
color: #fff;
|
||||
border-color: var(--br-accent);
|
||||
}
|
||||
#user-tab-content {
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.br-anim-enter-from-left {
|
||||
animation: br-slide-in-from-left 0.24s ease both;
|
||||
}
|
||||
.br-anim-enter-from-right {
|
||||
animation: br-slide-in-from-right 0.24s ease both;
|
||||
}
|
||||
.br-anim-leave-to-left {
|
||||
animation: br-slide-out-to-left 0.24s ease both;
|
||||
}
|
||||
.br-anim-leave-to-right {
|
||||
animation: br-slide-out-to-right 0.24s ease both;
|
||||
}
|
||||
@keyframes br-slide-in-from-left {
|
||||
from { opacity: 0.2; transform: translateX(-24px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
@keyframes br-slide-in-from-right {
|
||||
from { opacity: 0.2; transform: translateX(24px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
@keyframes br-slide-out-to-left {
|
||||
from { opacity: 1; transform: translateX(0); }
|
||||
to { opacity: 0; transform: translateX(-24px); }
|
||||
}
|
||||
@keyframes br-slide-out-to-right {
|
||||
from { opacity: 1; transform: translateX(0); }
|
||||
to { opacity: 0; transform: translateX(24px); }
|
||||
}
|
||||
.br-path-legend {
|
||||
margin-top: 12px;
|
||||
border-top: 1px dashed var(--br-border);
|
||||
padding-top: 10px;
|
||||
}
|
||||
.br-path-legend p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
@@ -0,0 +1,569 @@
|
||||
:root {
|
||||
--br-bg: #f5f8ff;
|
||||
--br-card: #ffffff;
|
||||
--br-text: #111827;
|
||||
--br-muted: #64748b;
|
||||
--br-border: #e2e8f0;
|
||||
--br-accent: #2563eb;
|
||||
--br-accent-dark: #1d4ed8;
|
||||
--br-danger: #dc2626;
|
||||
--br-warn: #f59e0b;
|
||||
--br-shadow: 0 10px 28px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--br-bg);
|
||||
color: var(--br-text);
|
||||
font-family: "IBM Plex Sans", "Segoe UI", Arial, sans-serif;
|
||||
}
|
||||
#content,
|
||||
#content .container,
|
||||
#content .container-fluid,
|
||||
#content .main-content,
|
||||
#content .page-content,
|
||||
.main-content,
|
||||
.page-content {
|
||||
max-width: none !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
.br-container {
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
.br-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.br-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
.br-card {
|
||||
background: var(--br-card);
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: var(--br-shadow);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.br-muted { color: var(--br-muted); }
|
||||
.br-alert {
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
margin: 8px 0;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.br-alert-error { background: #fff1f2; border-color: #fecdd3; color: #9f1239; }
|
||||
.br-alert-warn { background: #fffbeb; border-color: #fde68a; color: #92400e; }
|
||||
.br-alert-success { background: #ecfdf3; border-color: #bbf7d0; color: #166534; }
|
||||
.br-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
.br-modal {
|
||||
width: min(560px, 92vw);
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
border: 2px solid var(--br-border);
|
||||
box-shadow: var(--br-shadow);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 90vh;
|
||||
}
|
||||
.br-modal-log {
|
||||
width: 90vw;
|
||||
background: #ffffff;
|
||||
}
|
||||
.br-modal-danger {
|
||||
border-color: var(--br-danger);
|
||||
}
|
||||
.br-modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.br-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.br-modal-title {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
.br-modal-body {
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.br-modal-scroll {
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.br-log-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
.br-log-header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.br-log-meta {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin: 10px 0 12px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 10px;
|
||||
background: #f8fafc;
|
||||
font-size: 13px;
|
||||
}
|
||||
.br-text-danger {
|
||||
color: var(--br-danger);
|
||||
font-weight: 700;
|
||||
}
|
||||
.br-log-actions {
|
||||
margin: 8px 0 10px;
|
||||
}
|
||||
.br-picker-path {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.br-picker-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 10px;
|
||||
max-height: 45vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.br-picker-create {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.br-picker-create input {
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.br-picker-list li {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--br-border);
|
||||
}
|
||||
.br-picker-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.br-picker-list li:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.br-picker-list li.active {
|
||||
background: #d1fae5;
|
||||
}
|
||||
.br-btn-small {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.br-actions-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.br-actions-row form {
|
||||
margin: 0;
|
||||
}
|
||||
.br-icon-btn {
|
||||
border: 1px solid var(--br-border);
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
.br-icon-btn:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.br-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
table-layout: auto;
|
||||
}
|
||||
.br-table th {
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--br-muted);
|
||||
background: #f1f5f9;
|
||||
padding: 10px;
|
||||
}
|
||||
.br-table td {
|
||||
padding: 10px;
|
||||
border-top: 1px solid var(--br-border);
|
||||
vertical-align: top;
|
||||
}
|
||||
.br-table tr:nth-child(even) td { background: #f8fafc; }
|
||||
.br-col-select { width: 50px; text-align: center; }
|
||||
.br-col-status { width: 120px; }
|
||||
.br-col-progress { width: 90px; }
|
||||
.br-col-mode { width: 180px; }
|
||||
.br-col-user { width: 140px; }
|
||||
.br-col-backup { width: 220px; overflow-wrap: anywhere; }
|
||||
.br-col-path { width: 260px; overflow-wrap: anywhere; }
|
||||
.br-col-target { width: 220px; overflow-wrap: anywhere; }
|
||||
.br-col-time { width: 150px; }
|
||||
.br-col-actions { width: 120px; }
|
||||
.br-table input[type="checkbox"] {
|
||||
accent-color: var(--br-accent);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.br-actions { display: flex; gap: 8px; align-items: center; margin: 10px 0; }
|
||||
.br-btn {
|
||||
background: var(--br-accent);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
.br-btn:hover { background: var(--br-accent-dark); }
|
||||
.br-btn:disabled,
|
||||
.br-btn[disabled] {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.br-btn:disabled:hover,
|
||||
.br-btn[disabled]:hover {
|
||||
background: var(--br-accent);
|
||||
}
|
||||
.br-btn-ghost {
|
||||
background: transparent;
|
||||
color: var(--br-text);
|
||||
border: 1px solid var(--br-border);
|
||||
}
|
||||
.br-btn-ghost:hover { background: #e2e8f0; }
|
||||
.br-btn-ghost:disabled,
|
||||
.br-btn-ghost[disabled] {
|
||||
background: #f8fafc;
|
||||
color: var(--br-muted);
|
||||
border-color: var(--br-border);
|
||||
}
|
||||
.br-btn-ghost:disabled:hover,
|
||||
.br-btn-ghost[disabled]:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.br-btn-fogged {
|
||||
background: var(--br-accent);
|
||||
border: 1px solid var(--br-accent);
|
||||
color: #ffffff;
|
||||
opacity: 0.58;
|
||||
}
|
||||
.br-btn-fogged:hover {
|
||||
background: var(--br-accent);
|
||||
}
|
||||
.br-btn-fogged:disabled,
|
||||
.br-btn-fogged[disabled] {
|
||||
background: var(--br-accent);
|
||||
border-color: var(--br-accent);
|
||||
color: #ffffff;
|
||||
opacity: 0.58;
|
||||
}
|
||||
.br-btn-fogged:disabled:hover,
|
||||
.br-btn-fogged[disabled]:hover {
|
||||
background: var(--br-accent);
|
||||
}
|
||||
.br-btn-danger {
|
||||
background: var(--br-danger);
|
||||
}
|
||||
.br-btn-danger:hover { background: #b91c1c; }
|
||||
.br-link-danger { color: var(--br-danger); text-decoration: none; }
|
||||
.br-link-danger:hover { text-decoration: underline; }
|
||||
.br-inline-form { display: inline; }
|
||||
.br-link-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
.br-link-button:hover { text-decoration: underline; }
|
||||
.br-link-button.br-link-success { color: #15803d; }
|
||||
.br-link-button.br-link-danger { color: var(--br-danger); }
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
max-width: 680px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
background: #fff;
|
||||
}
|
||||
pre {
|
||||
background: #0b1020;
|
||||
color: #e2e8f0;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
overflow: auto;
|
||||
}
|
||||
a { color: var(--br-accent-dark); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.br-toast {
|
||||
position: fixed;
|
||||
bottom: 18px;
|
||||
right: 18px;
|
||||
background: #16a34a;
|
||||
color: #fff;
|
||||
padding: 12px 16px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 12px 28px rgba(16, 24, 40, 0.25);
|
||||
z-index: 9999;
|
||||
font-weight: 600;
|
||||
}
|
||||
.br-toast-stack {
|
||||
position: fixed;
|
||||
top: 18px;
|
||||
right: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
z-index: 9999;
|
||||
}
|
||||
.br-toast-stack .br-toast {
|
||||
position: static;
|
||||
top: auto;
|
||||
bottom: auto;
|
||||
}
|
||||
.br-toast-error {
|
||||
background: #dc2626;
|
||||
top: 18px;
|
||||
bottom: auto;
|
||||
}
|
||||
.br-toast-success {
|
||||
background: #16a34a;
|
||||
}
|
||||
.br-status-line {
|
||||
margin: 6px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.br-progress {
|
||||
height: 8px;
|
||||
background: #e5e7eb;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
margin: 6px 0 12px;
|
||||
}
|
||||
.br-progress-bar {
|
||||
height: 100%;
|
||||
background: var(--br-accent);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.br-restore-layout {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: minmax(320px, 420px) minmax(0, 1fr);
|
||||
margin-top: 12px;
|
||||
}
|
||||
.br-restore-calendar,
|
||||
.br-restore-picker {
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
.br-calendar-controls {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.br-month-nav {
|
||||
display: grid;
|
||||
grid-template-columns: 36px 1fr 36px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.br-month-nav form {
|
||||
margin: 0;
|
||||
}
|
||||
.br-month-label {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
.br-month-btn {
|
||||
width: 36px;
|
||||
height: 32px;
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
}
|
||||
.br-month-btn:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.br-calendar-grid {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.br-calendar-grid th,
|
||||
.br-calendar-grid td {
|
||||
border: 1px solid var(--br-border);
|
||||
text-align: center;
|
||||
padding: 4px;
|
||||
width: 14.28%;
|
||||
height: 38px;
|
||||
}
|
||||
.br-cal-day {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
border: 1px solid var(--br-border);
|
||||
background: #f8fafc;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.br-cal-day.is-selected {
|
||||
background: var(--br-accent);
|
||||
color: #fff;
|
||||
border-color: var(--br-accent);
|
||||
}
|
||||
.br-cal-day-disabled {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
color: var(--br-muted);
|
||||
opacity: 0.45;
|
||||
}
|
||||
.br-cal-empty {
|
||||
background: #f9fafb;
|
||||
}
|
||||
.br-time-list {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.br-time-item {
|
||||
border: 1px solid var(--br-border);
|
||||
border-radius: 999px;
|
||||
padding: 6px 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: #fff;
|
||||
}
|
||||
.br-time-item input {
|
||||
margin: 0;
|
||||
}
|
||||
@media (max-width: 980px) {
|
||||
.br-restore-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
.br-tab-header {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.br-main-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.br-main-sections {
|
||||
position: relative;
|
||||
}
|
||||
.br-main-sections.is-animating {
|
||||
overflow: hidden;
|
||||
}
|
||||
.br-main-sections.is-animating .br-main-section {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.br-main-tab.is-active {
|
||||
background: var(--br-accent);
|
||||
color: #fff;
|
||||
border-color: var(--br-accent);
|
||||
}
|
||||
.br-main-section.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
.br-user-tab.is-active {
|
||||
background: var(--br-accent);
|
||||
color: #fff;
|
||||
border-color: var(--br-accent);
|
||||
}
|
||||
#user-tab-content {
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.br-anim-enter-from-left {
|
||||
animation: br-slide-in-from-left 0.24s ease both;
|
||||
}
|
||||
.br-anim-enter-from-right {
|
||||
animation: br-slide-in-from-right 0.24s ease both;
|
||||
}
|
||||
.br-anim-leave-to-left {
|
||||
animation: br-slide-out-to-left 0.24s ease both;
|
||||
}
|
||||
.br-anim-leave-to-right {
|
||||
animation: br-slide-out-to-right 0.24s ease both;
|
||||
}
|
||||
@keyframes br-slide-in-from-left {
|
||||
from { opacity: 0.2; transform: translateX(-24px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
@keyframes br-slide-in-from-right {
|
||||
from { opacity: 0.2; transform: translateX(24px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
@keyframes br-slide-out-to-left {
|
||||
from { opacity: 1; transform: translateX(0); }
|
||||
to { opacity: 0; transform: translateX(-24px); }
|
||||
}
|
||||
@keyframes br-slide-out-to-right {
|
||||
from { opacity: 1; transform: translateX(0); }
|
||||
to { opacity: 0; transform: translateX(24px); }
|
||||
}
|
||||
.br-path-legend {
|
||||
margin-top: 12px;
|
||||
border-top: 1px dashed var(--br-border);
|
||||
padding-top: 10px;
|
||||
}
|
||||
.br-path-legend p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
Executable
+4795
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user