Web manažér na zálohu a obnovu webov

Keď som začínal so servermi, tak ako asi každého ma zaujímali predovšetkým webové servery. Určite je to viac ako 15 rokov a vtedy som na holý kov nasadil Ubuntu 9. Už počas inštalácie mi inštalátor ponúkal inštaláciu LAMP a ja som neváhal a LAMP som nainštaloval. Dnes fungujem s LXC (Linux Containers) a čuduj sa svete vo vnútri LXC (Debian) beží stále LAMP, avšak do internetu je to vystavené skrz reverzný proxy server nginx

Obsah

Moje začiatky s webom

Začal som sa učiť html a css, ale po čase som zistil, že existuje mnoho open source projektov, ktoré sa venujú tvorbe webov ako napr. Drupal, Joojmla, PHPFusion a asi najznámejší WordPress na ktorom je postavený aj tento web. Často som nasadzoval rôzne webové projekty a ak sa niečo nepodarilo, tak som adresár z webom vymazal, odstránil databázu a začal som nanovo. Nie raz sa stalo, že večer som na webe spravil nejaké zmeny a ráno to už nefungovalo (resp. som to nevedel dať do pôvodného stavu). Vtedy som začal uvažovať o zálohách a postupne som si vytvoril jednoduché shell skripty.

Prvé zálohy a obnovy webov

Takto som zálohoval webový adresár a databázu mySQL resp. MariaDB.

#!/bin/bash
mysqldump db_name > ~/my-web.com-$(date +%Y-%m-%d-%H.%M.%S).sql
tar -cvzf ~/backup_my-web.com-$(date +%Y-%m-%d-%H.%M.%S).tar.gz  /var/www/www.my-web.com

Ak som chcel následne web obnoviť, tak obsah pôvodného adresára som vymazal a rozbalil som adresár, ktorý som mal uložený ako zálohu.

sudo rm -rf /var/www/www.my-web.com/{*,.*}
sudo tar xvzf /home/jany/backup_my-web.com-2025-02-01-18.38.55.tar.gz -C /

Obnova databázy prebiehala o niečo zložitejšie. V terminále som použil jednoduchý príkaz na výpis obsahu adresára ls, kde som skopíroval názov mySQL zálohy. Tento názov bol vždy iný, pretože pri zálohovaní som použil časovú značku. Následne som otvoril skript na obnovu a do riadku 4 (viď nižšie) som musel nakopírovať názov súboru. Potom som skript mohol spustiť a databáza bola v niekoľkých sekundách obnovená.

#!/bin/bash
mysql -e 'DROP DATABASE IF EXISTS `my-web`'
mysql -e 'CREATE DATABASE `my-web`'
mysql  my-web < my-web.com-2025-02-01-18.38.55.sql

Vždy som uvažoval ako tento proces zjednoduším, pretože weby na servery pribúdali a aj zálohovať bolo treba častejšie.

Záloha a obnova po novom

Prvou myšlienkou zefektívnenia tohto procesu bolo vytvoriť nové shellové skripty, kde by som nemusel ručne prepisovať názvy súborov. Potom ma napadla myšlienka, vyriešiť tento problém pomocou webového rozhrania. Vzhľadom k tomu, že nie som programátor, tak som sa rozhodol využiť umelú inteligenciu. Začal som s Čínskou Deepseek, pokračoval Claude AI od Sonnet a skončil som na GitHube s ChatGPT 4o od Azure OpenAI. Bol to dosť boj, ale nakoniec sa to podarilo. Musím sa priznať, že ja som nenapísal ani jeden riadok kódu. Ako ne-programátor nedokážem posúdiť či je kód napísaní čisto, správne a efektívne, ale viem, že funguje výborne.

Na začiatku som chcel použiť Python, ale nakoniec som sa rozhodol pre PHP. Na základe mojich požiadaviek začala AI generovať kód, ktorý sa postupne dolaďoval.

Záloha (backup)

Najprv som začal riešiť čisto proces zálohovania. Čiže, potrebujem vylistovať obsah, aké adresáre sa nachádzajú na ceste /var/www/. Ďalej potrebujem z mySQL, pomocou SHOW DATABASES vyčítať zoznam všetkých databáz. To znamená, že webová aplikácia bude mať 2x DropDown menu, kde si vyberiem aký adresár chcem zálohovať a k nemu priradím databázu. V mojom prípade vznikol problém, pretože názov adresára sa nezhoduje s názvom databázy a skript musí vyriešiť aj toto priradenie (dôležité pri obnove). Ďalej pomocou funkcie exec vieme vykonávať príkazy (zálohovanie web adresára a databázy) ako keby sme ich vykonávali priamo v terminály. Tieto funkcie sú v niektorých prípadoch veľmi nebezpečné a mnoho krát sa vyžaduje použitie sudo.

Poznámka: Proces zálohovania (resp. obnovenia) bude realizovaný pod užívateľom www-data. Ja som sa však rozhodol zálohy ukladať v domovskom adresári /home/<user>/backups/, aby sa mi jednoduchšie kopírovali na lokálne úložisko. Pre tento účel som potreboval zmeniť vlastníka adresára a nastaviť práva na čítanie a zápis. Aj keď všetko bolo nastavené správne, tak ukladanie do /home/<user>/backups/ nefungovalo. Preto som zvolil alternatívu, ACL (Accass Conroll List), viď ďalej.

Týmto postupom by sme mali vyriešené zálohovanie web adresárov a databáz. V zálohovacom skripte je ešte možnosť, že ak web nepoužíva databázu, tak je možné zálohovať len samotný web adresár.

Obnova (restore)

Obnova funguje analogicky. Zobrazíme si obsah adresára zo zálohami /home/<user>/backups/. Ak sme v predošlom kroku zálohovali web + databáza, tak v DropDown menu to bude viditeľné (už nemusíme hľadať k web adresáru tu správnu databázu).

Poznámka: V zálohovacom skripte, pri vytváraní zálohy web adresára a príslušnej databázy k nemu sa každému súboru priradí náhodne vygenerované číslo a tým sa zabezpečí, že web adresár a databáza patria k sebe.

Ak zálohovaný web nepoužíva databázu, tak v DropDown menu to uvidíme (ak web databázu používa, tak to uvidíme tiež). Skript (pomocou exec) vymaže pôvodný adresár a na jeho miesto rozbalí dáta zo zálohy a to isté spraví s databázou (ak DB existuje, tak ju odstráni, vytvorí novú a naplní údajmi zo zálohy). Proces obnovy znie jednoducho, ale nebola to triviálna záležitosť.

Skripty bolo potrebné ošetriť na chybové hlášky, pridať prihlásenie, upraviť práva na súbory, adresáre, config atď. Priznám sa, že som strávil nad tým pár dní a čistého času možno aj 5-6 hod.

Príprava prostredia

Na tento účel som zriadil doménu pod názvom, webshell.tt. Vygeneroval som lokálny certifikát SSL pomocou mkcert, nastavil som config pre web server apache2, reverzný proxy server nginx a lokálny DNS. Toto už samozrejme nie je obsahom článku, pretože to by bolo na ďalšiu samotnú kapitolu. Avšak treba povedať, že nič z toho nie je povinné a skripty, ktoré si predstavíme neskôr, môžeme umiestniť kdekoľvek na LAMP server.

Dôležité je aby sme tento (kvázi) web, kde bude bežať aplikácia nevystavovali do internetu. Ja ho mám prístupný len v LAN (DMZ) a z vonka sa viem k nemu dostať aj skrz VPN wireguard. Web aplikácia je responzívna a hneď na začiatok si dovolím ukázať ako to vyzerá v praxi na mobilnom zariadení (ospravedlňujem sa, ale rozhodol som sa v aplikácii nepoužívať diakritiku).

Jednoduché použitie aj v mobile

Ďalej nasleduje príprava prostredia pre samotnú aplikáciu. Vytvorime si 6 súborov (plus adresár na css a samotný súbor, kde bude css kód).

sudo touch /var/www/html/webshell.tt/{backup.php,restore.php,main.php,config.php,login.php,logout.php}
sudo mkdir /var/www/html/webshell.tt/css && sudo touch /var/www/html/webshell.tt/css/styles.css

Pre config.php, kde budú uložené dôležité dáta na prihlásenie k DB a do samotnej aplikácie, nastavíme prísne privilégia 600 (rw pre vlastníka a zvyšok si neškrtne).

sudo chmod 600 /var/www/html/webshell.tt/config.php

Zmeníme vlastníka na www-data (keďže sme to vytvárali pod rootom).

sudo chown -R www-data:www-data /var/www/html/webshell.tt

Ako som už spomínal, tak zálohy budeme smerovať do nášho home adresára (v mojom prípade).

mkdir -p /home/jany/backups

Z bezpečnostného hľadiska nastavíme ACL pre adresár, kde budeme ukladať zálohy.

sudo apt-get install acl
sudo setfacl -m u:www-data:rx /home/jany
sudo setfacl -R -m u:www-data:rwx /home/jany/backups

Môžeme preveriť či je to v poriadku.

getfacl /home/jany
# file: home/jany
# owner: jany
# group: jany
user::rwx
user:www-data:rx
group::r-x
mask::r-x
other::---
getfacl /home/jany/backups
# file: home/jany/backups
# owner: jany
# group: jany
user::rwx
user:www-data:rwx
group::r-x
mask::rwx
other::r-x

Ďalšou dôležitou vecou sú nastavenia aby užívateľ www-data mohol spúšťať príkazy, ktoré vyžadujú privilégia roota. Toto je mimoriadne nebezpečné, ale aplikácia je ošetrená prihlasovacím heslom a je prístupná len z LAN siete.

sudo visudo

Preto povolíme užívateľovi aby mohol vykonávať niektoré príkazy ako root.

www-data ALL=(ALL) NOPASSWD: /bin/rm, /bin/tar, /usr/bin/find, /usr/bin/mysql

Vytvárame aplikáciu

Aplikácia je rozdelená na niekoľko súborov aby bola prehľadnejšia. PHP súbory boli generované AI a boli aj vynikajúco okomentované. Ako prvý si nastavíme konfiguračný súbor v ktorom sú uložené heslá k databáze a na prihlásenie do aplikácie.

sudo nano /var/www/html/webshell.tt/config.php
<?php
// config.php
return [
    'db_password' => 'silne_heslo_na_pripojenie_k_databaze',
    'users' => [
        'admin' => password_hash('silne_heslo_na_prihlasenie_k_aplikacii', PASSWORD_DEFAULT),
    ],
];

Nasleduje hlavný súbor (main.php) do ktorého budú vložené aj zálohovací a obnovovací skript pomocou funkcie include.

sudo nano /var/www/html/webshell.tt/main.php
<?php
// main.php
session_start();
 
// Skontrolujte, ci je pouzivatel prihlaseny
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
    header('Location: login.php');
    exit;
}
 
// Kontrola, ci bola odoslana akcia na zalohovanie alebo obnovu
$action = $_GET['action'] ?? 'backup'; // Predvolene nastavime na zalohovanie ('backup')
?>
 
<!DOCTYPE html>
<html lang="sk">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Manazer pre zalohu a obnovu webov</title>
    <link rel="stylesheet" href="css/styles.css">
    <style>
        .nav-buttons {
            display: flex;
            justify-content: center;
            gap: 10px;
            margin-bottom: 20px;
        }
        .nav-buttons button {
            width: auto; /* Override the button width from the external CSS file */
            flex: 1; /* Ensure buttons are the same size */
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Manazer pre zalohu a obnovu webov</h1>
        <p>Vitajte, <?php echo htmlspecialchars($_SESSION['username']); ?>!</p>
        <div class="nav-buttons">
            <button onclick="window.location.href='main.php?action=backup'">Zalohovat</button>
            <button onclick="window.location.href='main.php?action=restore'">Obnovit</button>
            <button onclick="window.location.href='logout.php'">Odhlasit</button>
        </div>
        <div class="content">
            <?php
            switch ($action) {
                case 'backup':
                    include 'backup.php';
                    break;
                case 'restore':
                    include 'restore.php';
                    break;
                default:
                    echo '<p>Vyberte akciu, ktoru chcete vykonat.</p>';
                    break;
            }
            ?>
        </div>
    </div>
</body>
</html>

Skript zodpovedný za prihlásenie sa do aplikácie.

sudo nano /var/www/html/webshell.tt/login.php
<?php
// login.php
session_start();
 
$config = require 'config.php';
 
$error_message = '';
 
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = $_POST['username'] ?? '';
    $password = $_POST['password'] ?? '';
 
    if (empty($username) || empty($password)) {
        $error_message = 'Vyplňte všetky polia.';
    } else {
        if (isset($config['users'][$username]) && password_verify($password, $config['users'][$username])) {
            $_SESSION['loggedin'] = true;
            $_SESSION['username'] = $username;
            header('Location: main.php');
            exit;
        } else {
            $error_message = 'Nespravne prihlasovacie udaje.';
        }
    }
}
?>
 
<!DOCTYPE html>
<html lang="sk">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
    <link rel="stylesheet" href="css/styles.css">
</head>
<body>
    <div class="container">
        <h1>Prihlasenie</h1>
        <?php if ($error_message): ?>
            <div class="error"><?php echo htmlspecialchars($error_message); ?></div>
        <?php endif; ?>
        <form method="POST" action="">
            <label for="username">Pouzivatelske meno:</label>
            <input type="text" id="username" name="username" required>
            <label for="password">Heslo:</label>
            <input type="password" id="password" name="password" required>
            <button type="submit">Prihlasit</button>
        </form>
    </div>
</body>
</html>

Skript zodpovedný za odhlásenie sa z aplikácie.

sudo nano /var/www/html/webshell.tt/logout.php
<?php
// logout.php
session_start();
session_destroy();
header('Location: login.php');
exit;

Nasleduje samotný skript zodpovedný za zálohu web adresárov a databáz.

sudo nano /var/www/html/webshell.tt/backup.php
<?php
// backup.php
session_start();
 
// Skontrolujte, ci je pouzivatel prihlaseny
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
    header('Location: login.php');
    exit;
}
 
// Zapnutie zobrazovania chyb
error_reporting(E_ALL);
ini_set('display_errors', 1);
 
// Nacitanie hesla z konfiguracneho suboru
$config = require 'config.php';
$db_password = $config['db_password'];
if (!$db_password) {
    die('Chybajuce heslo pre databazu.');
}
 
// Funkcia na ziskanie zoznamu podadresarov v /var/www/
function get_web_directories($base_dir) {
    $directories = [];
    $items = scandir($base_dir);
    foreach ($items as $item) {
        if ($item !== '.' && $item !== '..' && is_dir("$base_dir/$item")) {
            $directories[] = $item;
        }
    }
    return $directories;
}
 
// Funkcia na ziskanie zoznamu databaz
function get_databases() {
    global $db_password;
    $databases = [];
    try {
        $mysqli = new mysqli('localhost', 'root', $db_password);
        if ($mysqli->connect_error) {
            throw new Exception('Chyba pripojenia k databaze: ' . $mysqli->connect_error);
        }
 
        $result = $mysqli->query("SHOW DATABASES");
        if (!$result) {
            throw new Exception('Chyba pri vykonavani dotazu: ' . $mysqli->error);
        }
 
        while ($row = $result->fetch_assoc()) {
            $databases[] = $row['Database'];
        }
 
        $mysqli->close();
    } catch (Exception $e) {
        die($e->getMessage());
    }
    return $databases;
}
 
// Spracovanie formulara
$success_message = '';
$error_message = '';
 
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $website_name = $_POST['website_name'] ?? '';
    $db_name = $_POST['db_name'] ?? '';
 
    if (empty($website_name)) {
        $error_message = 'Chyba: Vyplnte pole pre webovy adresar.';
    } else {
        $backup_dir = '/home/jany/backups';
        if (!is_dir($backup_dir)) {
            if (!mkdir($backup_dir, 0755, true)) {
                $error_message = 'Chyba pri vytvarani adresara pre zalohy.';
            }
        }
 
        if (empty($error_message)) {
            // Generovanie nahodneho cisla
            $random_number = rand(100000, 999999);
            // Generovanie casovej znacky
            $timestamp = date('Y-m-d-H.i.s', time() + 3600); // Pridanie hodiny
 
            // Zaloha weboveho adresara
            $web_backup_file = "$backup_dir/{$website_name}_{$random_number}_web-$timestamp.tar.gz";
            $web_dir = "/var/www/$website_name";
            exec("tar -cvzf $web_backup_file $web_dir 2>&1", $output, $return_var);
 
            if ($return_var !== 0) {
                $error_message = "Chyba pri zalohovani weboveho adresara: " . implode("\n", $output);
            } else {
                $success_message = "Zaloha weboveho adresara bola uspesne vytvorena:\n- Webovy adresar: $web_backup_file";
 
                // Zaloha databazy, ak je vybrana
                if (!empty($db_name) && $db_name !== 'none') {
                    $db_backup_file = "$backup_dir/{$website_name}_{$random_number}_db-$timestamp.sql";
                    $escaped_password = escapeshellarg($db_password);
                    exec("mysqldump -u root -p$escaped_password $db_name > $db_backup_file 2>&1", $output, $return_var);
 
                    if ($return_var !== 0) {
                        $error_message .= "\nChyba pri zalohovani databazy: " . implode("\n", $output);
                    } else {
                        $success_message .= "\n- Databaza: $db_backup_file";
                    }
                }
            }
        }
    }
}
 
// Ziskanie zoznamu webovych adresarov a databaz
$web_directories = get_web_directories('/var/www');
$databases = array_merge(['none' => 'Ziadna databaza'], get_databases());
?>
 
<!DOCTYPE html>
<html lang="sk">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Zalohovanie webu a databazy</title>
    <link rel="stylesheet" href="css/styles.css">
</head>
<body>
    <div class="container">
        <h1>Zalohovanie webu a databazy</h1>
        <p>Vyberte webovy adresar a databazu (volitelne).</p>
        <form method="POST" action="">
            <label for="website_name">Webovy adresar:</label>
            <select id="website_name" name="website_name" required>
                <option value="">-- Vyberte webovy adresar --</option>
                <?php foreach ($web_directories as $dir): ?>
                    <option value="<?php echo htmlspecialchars($dir); ?>"><?php echo htmlspecialchars($dir); ?></option>
                <?php endforeach; ?>
            </select>
 
            <label for="db_name">Databaza (volitelne):</label>
            <select id="db_name" name="db_name">
                <option value="none">Ziadna databaza</option>
                <?php foreach ($databases as $db): ?>
                    <option value="<?php echo htmlspecialchars($db); ?>"><?php echo htmlspecialchars($db); ?></option>
                <?php endforeach; ?>
            </select>
             
            <button type="submit">Spustit zalohovanie</button>
        </form>
 
        <?php if (!empty($success_message)): ?>
            <div class="output success">
                <?php echo nl2br(htmlspecialchars($success_message)); ?>
            </div>
        <?php elseif (!empty($error_message)): ?>
            <div class="output error">
                <?php echo nl2br(htmlspecialchars($error_message)); ?></div>
        <?php endif; ?>
    </div>
</body>
</html>

Skript zodpovedný za obnovu web adresárov a databáz

sudo nano /var/www/html/webshell.tt/restore.php
<?php
// restore.php
session_start();
 
// Skontrolujte, či je používateľ prihlásený
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
    header('Location: login.php');
    exit;
}
 
// Zapnutie zobrazovania chyb
error_reporting(E_ALL);
ini_set('display_errors', 1);
 
// Adresar so zalohami
$backup_dir = '/home/jany/backups/';
 
// Nacitanie hesla z konfiguracneho suboru
$config = require 'config.php';
$db_password = $config['db_password'];
if (!$db_password) {
    die('Chybajuce heslo pre databazu.');
}
 
// Funkcia na extrahovanie casovej znacky zo suboru
function extract_timestamp($filename, $type) {
    if (preg_match("/_{$type}-(\d{4}-\d{2}-\d{2}-\d{2}\.\d{2}\.\d{2})/", $filename, $matches)) {
        return $matches[1];
    }
    return null;
}
 
// Funkcia na ziskanie nazvu databazy zo .sql suboru
function get_database_name_from_sql($file_path) {
    $handle = fopen($file_path, "r");
    if ($handle) {
        // Preskocime prve dva riadky a precitame treti riadok
        fgets($handle); // Preskocime prvy riadok
        fgets($handle); // Preskocime druhy riadok
        $line = fgets($handle); // Precitanie tretieho riadku
        fclose($handle);
 
        if (strpos($line, 'Database:') !== false) {
            // Extrahovanie nazvu databazy po "Database:"
            $parts = explode('Database:', $line);
            if (isset($parts[1])) {
                return trim($parts[1]);
            }
        }
    }
    return null;
}
 
// Ziskanie zoznamu suborov .tar.gz a .sql
$backup_files = scandir($backup_dir);
 
$web_backups = array_filter($backup_files, function($file) {
    return preg_match('/_web-\d{4}-\d{2}-\d{2}-\d{2}\.\d{2}\.\d{2}\.tar\.gz$/', $file);
});
$db_backups = array_filter($backup_files, function($file) {
    return preg_match('/_db-\d{4}-\d{2}-\d{2}-\d{2}\.\d{2}\.\d{2}\.sql$/', $file);
});
 
// Sparovanie zaloh podla nahodneho cisla
$paired_backups = [];
foreach ($web_backups as $web_file) {
    if (preg_match('/(.*)_([0-9]{6})_web-\d{4}-\d{2}-\d{2}-\d{2}\.\d{2}\.\d{2}\.tar\.gz$/', $web_file, $matches)) {
        $web_name = $matches[1];
        $random_number = $matches[2];
        $timestamp = extract_timestamp($web_file, 'web');
        // Hladanie zodpovedajucej databazovej zalohy
        $db_file = "{$web_name}_{$random_number}_db-{$timestamp}.sql";
 
        if (in_array($db_file, $db_backups)) {
            $paired_backups[] = [
                'web' => $web_file,
                'db' => $db_file,
                'timestamp' => $timestamp,
                'web_name' => $web_name,
                'random_number' => $random_number
            ];
        } else {
            // Pridanie zaloh bez databazy
            $paired_backups[] = [
                'web' => $web_file,
                'db' => 'none',
                'timestamp' => $timestamp,
                'web_name' => $web_name,
                'random_number' => $random_number
            ];
        }
    }
}
 
// Spracovanie formulara
$success_message = '';
$error_message = '';
 
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $selected_backup = $_POST['backup'];
    $web_dir = '/var/www/';
 
    // Najdenie vybratej zalohy
    $selected_pair = array_filter($paired_backups, function($pair) use ($selected_backup) {
        return $pair['web'] === $selected_backup;
    });
 
    if (!empty($selected_pair)) {
        $selected_pair = array_values($selected_pair)[0]; // Ziskanie prvej hodnoty
        $web_name = $selected_pair['web_name'];
        $web_dir .= $web_name;
 
        // Obnova weboveho adresara
        exec("sudo rm -rf $web_dir/*");
        exec("sudo tar --strip-components=3 -xzf $backup_dir{$selected_pair['web']} -C $web_dir");
 
        // Nastavenie prav pre subory a adresare
        exec("sudo find $web_dir -type f -exec chmod 644 {} \\;");
        exec("sudo find $web_dir -type d -exec chmod 755 {} \\;");
        exec("sudo find $web_dir -type f -name 'wp-config.php' -exec chmod 400 {} \\;");
 
        // Obnova databazy, ak existuje
        if ($selected_pair['db'] !== 'none') {
            // Načítanie názvu databázy zo súboru .sql
            $db_file_path = $backup_dir . $selected_pair['db'];
            $db_name = get_database_name_from_sql($db_file_path);
            if ($db_name) {
                error_log("Obnovujem databazu: $db_name");
                exec("mysql -u root -p" . escapeshellarg($db_password) . " -e 'DROP DATABASE IF EXISTS `$db_name`;'");
                exec("mysql -u root -p" . escapeshellarg($db_password) . " -e 'CREATE DATABASE `$db_name`;'");
                exec("mysql -u root -p" . escapeshellarg($db_password) . " $db_name < $db_file_path 2>&1", $output, $return_var);
                if ($return_var !== 0) {
                    $error_message .= "Chyba pri importe databazy: " . implode("\n", $output);
                }
            } else {
                $error_message = "Chyba pri nacitani nazvu databazy zo suboru {$selected_pair['db']}.";
            }
        }
 
        if (empty($error_message)) {
            $success_message = "Obnova bola uspesne dokoncena.";
        }
    } else {
        $error_message = "Chyba: Vybrata zaloha nebola najdena.";
    }
}
?>
 
<!DOCTYPE html>
<html lang="sk">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Obnova zalohy</title>
    <link rel="stylesheet" href="css/styles.css">
</head>
<body>
    <div class="container">
        <h1>Obnova zalohy</h1>
        <p>Vyberte zalohu na obnovenie (web + databaza alebo len web).</p>
        <form method="POST">
            <label for="backup">Vyberte zalohu (web + databaza alebo len web):</label>
            <select name="backup" id="backup" required>
                <option value="">-- Vyberte zalohu --</option>
                <?php if (empty($paired_backups)) : ?>
                    <option disabled>Nie su dostupne ziadne zalohy</option>
                <?php else : ?>
                    <?php foreach ($paired_backups as $pair): ?>
                        <option value="<?php echo htmlspecialchars($pair['web']); ?>">
                            <?php echo htmlspecialchars($pair['web'] . ($pair['db'] !== 'none' ? ' + ' . $pair['db'] : ' (len web)')); ?>
                        </option>
                    <?php endforeach; ?>
                <?php endif; ?>
            </select>
            <button type="submit">Obnovit</button>
        </form>
 
        <?php if (!empty($success_message)): ?>
            <div class="output success">
                <?php echo htmlspecialchars($success_message); ?>
            </div>
        <?php elseif (!empty($error_message)): ?>
            <div class="output error">
                <?php echo htmlspecialchars($error_message); ?>
            </div>
        <?php endif; ?>
    </div>
</body>
</html>

Aby web aplikácia vyzerala na dnešné pomery aspoň trocha slušne, tak potrebujeme web trocha naštýlovať.

sudo nano /var/www/html/webshell.tt/css/styles.css
body {
    font-family: Arial, sans-serif;
    background-color: #f9f9f9;
    margin: 0;
    padding: 20px;
}
 
.container {
    max-width: 800px; /* Zväčšená maximálna šírka */
    margin: 0 auto;
    padding: 20px;
    background-color: #fff;
    border: 1px solid #ddd;
    border-radius: 10px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    box-sizing: border-box; /* Ensures padding is included in the total width */
}
 
h1 {
    text-align: center;
    color: #333;
}
 
label {
    display: block;
    margin: 10px 0 5px;
    font-weight: bold;
    color: #555;
}
 
select, input {
    width: 100%;
    padding: 10px;
    margin-bottom: 15px;
    border: 1px solid #ccc;
    border-radius: 5px;
    font-size: 16px;
    box-sizing: border-box; /* Ensures padding is included in the total width */
}
 
button {
    display: block;
    width: 100%;
    padding: 10px;
    font-size: 16px;
    background-color: #28a745;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    transition: background-color 0.3s;
    box-sizing: border-box; /* Ensures padding is included in the total width */
}
 
button:hover {
    background-color: #218838;
}
 
.output {
    margin-top: 20px;
    padding: 15px;
    border-radius: 5px;
    font-size: 16px;
    box-sizing: border-box; /* Ensures padding is included in the total width */
}
 
.success {
    background-color: #d4edda;
    color: #155724;
    border: 1px solid #c3e6cb;
}
 
.error {
    background-color: #f8d7da;
    color: #721c24;
    border: 1px solid #f5c6cb;
}
 
@media (max-width: 800px) {
    .container {
        padding: 15px;
    }
    h1 {
        font-size: 24px;
    }
    select, input {
        font-size: 14px;
    }
    button {
        font-size: 14px;
    }
    .output {
        font-size: 14px;
    }
}

Predstavenie a ovládanie aplikácie

Môžeme sa na web prihlásiť. V mojom prípade do url zadáme.

https://webshell.tt/main.php

Budeme presmerovaný do

https://webshell.tt/login.php
prihlasovací formulár

Po úspešnom prihlásení sa dostanem do aplikácie a hneď môžeme zálohovať. Na obrázku vidíme 4 buttony. Prvý zľava (Zalohovat) slúži na prepnutie zálohovacieho modulu. Button (Obnovit) sa prepneme na modul obnovenia. Button (Odhlásiť) je jasný a taktiež button (Spustit zalohovanie). Z prvého menu DropDown si môžeme vybrať web adresár, ktorý chceme zálohovať. Ak Web nepoužíva databázu, tak v druhom DropDown menu je predvolene nastavená možnosť, „Ziadna databaza“. V takom prípade sa zálohuje len web adresár.

Okno pre zálohovanie

Vyberiem si zálohovať napr. svoj osobný web, ktorý používa aj databázu. V DropDown menu vyhľadám správny adresár, tiež databázu a kliknem na „Spustit zalohovanie“.

Pripravené pre zálohovací proces

V závislosti od veľkosti webu a databázy, môže proces zálohovania trvať radovo jednotky až desiatky sekúnd. V mojom prípade som zálohoval web, ktorý po zbalení .gz zaberal na disku 134MB + databáza 4.7MB a celé to trvalo 6 sekúnd.

Výsledok po zálohovaní

Zálohovanie prebehlo úspešne, kde vytvorená záloha dostala aj časovú značku a náhodne generované číslo (874521) podľa ktorého sa pri obnove priradí web k databáze. Ak by sme chceli hneď web + databázu obnoviť, tak klikneme na button „Obnovit“ a vyberieme z DropDown menu web + DB. Na obrázku je vidieť, že v zozname je momentálne práve jedna možnosť.

Vyberáme súbory pre obnovu webu

Klikneme na „Obnovit“.

Poznámka: Až teraz som si všimol, že button, ktorý je pod DropDown menu má rovnaký názov ako button vo vrchnej časti aplikácie. Spodný button musím premenovať na „Obnovit zo zalohy“ aby to bolo jasné.

Pripravené pre obnovu webu

Takže už som to stihol opraviť a po obnove budeme informovaný, že záloha bola úspešne dokončená. Vždy po obnove si web prehliadnem, či je všetko v poriadku.

Úspech po obnovení webu

Záver

Tento (ako som ho nazval) web manažér mi veľmi uľahčuje proces zálohovania a obnovú webov. Predtým som sa musel prihlásiť cez SSH do terminálu (aj keď používam SSH key a odkaz). Zálohovanie nebol problém, pretože som spustil skript a do niekoľkých sekúnd to bolo hotové. Viac práce predstavovalo obnovenie webu, pretože som musel upravovať skript ručne. Teraz nevidím problém ani s obnovou.

AI je veľmi dobrý pomocník (nie len) pri programovaní. Ak dostane zrozumiteľné vstupy, tak generuje zrozumiteľné a použiteľné výstupy. Ak by som chcel riešiť aplikáciu pre zálohu a obnovu webov pred 5-10 rokmi, kedy AI neboli (resp. neboli verejne dostupné), tak by som si musel zaplatiť programátora. Netrúfam si vyčísliť cenu, ale určite by to bolo min. v desiatkach ak nie stovkách eur. Dnes stačí byť dobrým analytikom a kód vychrlí AI v jednotkách sekúnd. Tým nechcem povedať, že programátor už nebude mať uplatnenie, pretože jazyky sa stále vyvíjajú a to čo generuje AI sa potrebuje od niekoho naučiť. Aj keď si musíme priznať, že AI sa hypoteticky približuje k stavu singularity a vedci už nediskutujú o tom či sa to stane, ale kedy sa to stane.

Leave a Reply

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *