]> git.ipfire.org Git - people/stevee/ipfire-2.x.git/commitdiff
Introduce snapshots.cgi page
authorStefan Schantl <stefan.schantl@ipfire.org>
Thu, 3 Jul 2025 16:51:42 +0000 (18:51 +0200)
committerStefan Schantl <stefan.schantl@ipfire.org>
Thu, 3 Jul 2025 16:51:42 +0000 (18:51 +0200)
This new CGI page can be used to create and deal with BTRFS snapshots.

The access to this page will be in the "System" menu in case you have
chossesn BTRFS during setup.

Signed-off-by: Stefan Schantl <stefan.schantl@ipfire.org>
config/menu/10-system.menu
config/rootfiles/common/web-user-interface
html/cgi-bin/snapshots.cgi [new file with mode: 0644]
langs/de/cgi-bin/de.pl
langs/en/cgi-bin/en.pl

index b142bfbacc3cee7aa8ab975d779928fd570da301..9c37591cd0c09271fe2c78775fb42d142f2c93ab 100644 (file)
                                'title' => "$Lang::tr{'gui settings'}",
                                'enabled' => 1,
                                };
-    $subsystem->{'40.backup'} = {
+    $subsystem->{'50.backup'} = {
                                'caption' => $Lang::tr{'backup'},
                                'uri' => '/cgi-bin/backup.cgi',
                                'title' => "$Lang::tr{'backup'}",
                                'enabled' => 1,
                                };
-    $subsystem->{'41.fireinfo'} = {
+    $subsystem->{'60.snapshots'} = {
+                               'caption' => $Lang::tr{'snapshots'},
+                               'uri' => '/cgi-bin/snapshots.cgi',
+                               'title' => "$Lang::tr{'snapshots'}"
+                               'enabled' => (`stat -f --format="%T" / | grep btrfs` ? 1 : 0),
+                               };
+    $subsystem->{'70.fireinfo'} = {
                                'caption' => $Lang::tr{'system information'},
                                'uri' => '/cgi-bin/fireinfo.cgi',
                                'title' => "$Lang::tr{'system information'}",
                                'enabled' => 1,
                                };
-    $subsystem->{'42.hwvuln'} = {
+    $subsystem->{'80.hwvuln'} = {
                                'caption' => $Lang::tr{'hardware vulnerabilities'},
                                'uri' => '/cgi-bin/vulnerabilities.cgi',
                                'title' => "$Lang::tr{'hardware vulnerabilities'}",
                                'enabled' => 1,
                                };
-    $subsystem->{'43.shutdown'} = {
+    $subsystem->{'90.shutdown'} = {
                                'caption' => $Lang::tr{'shutdown'},
                                'uri' => '/cgi-bin/shutdown.cgi',
                                'title' => "$Lang::tr{'shutdown'}",
index aa31491d24e1022febaf1ff4fdc2db45eece1ec5..941199c81787fc0a0a7093c006ed77100e246607 100644 (file)
@@ -71,6 +71,7 @@ srv/web/ipfire/cgi-bin/remote.cgi
 srv/web/ipfire/cgi-bin/routing.cgi
 #srv/web/ipfire/cgi-bin/samba.cgi
 srv/web/ipfire/cgi-bin/services.cgi
+srv/web/ipfire/cgi-bin/snapshots.cgi
 srv/web/ipfire/cgi-bin/shutdown.cgi
 srv/web/ipfire/cgi-bin/speed.cgi
 srv/web/ipfire/cgi-bin/system.cgi
diff --git a/html/cgi-bin/snapshots.cgi b/html/cgi-bin/snapshots.cgi
new file mode 100644 (file)
index 0000000..029682f
--- /dev/null
@@ -0,0 +1,223 @@
+#!/usr/bin/perl
+###############################################################################
+#                                                                             #
+# IPFire.org - A linux based firewall                                         #
+# Copyright (C) 2007-2025  IPFire Team  <info@ipfire.org>                     #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+use strict;
+# enable only the following on debugging purpose
+#use warnings;
+#use CGI::Carp 'fatalsToBrowser';
+
+# Load function from posix module to format time strings.
+use POSIX qw (strftime);
+
+# Load module to call stat on files/directories.
+use File::stat;
+
+require '/var/ipfire/general-functions.pl';
+require "${General::swroot}/filesystem-functions.pl";
+require "${General::swroot}/lang.pl";
+require "${General::swroot}/header.pl";
+
+my $error;
+
+my %color = ();
+my %mainsettings = ();
+my %settings = ();
+my %cgiparams = ();
+
+&General::readhash("${General::swroot}/main/settings", \%mainsettings);
+&General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", \%color);
+
+&Header::showhttpheaders();
+
+# Get GUI values
+&Header::getcgihash(\%cgiparams);
+
+# Get all available snapshots in case root is running as BTRFS.
+my %snapshots = &Filesystem::btrfs_get_snapshots() if &Filesystem::is_btrfs("/");
+
+# If any kind of id is given, check if it is valid/known.
+if (exists($cgiparams{'ID'}) and not exists($snapshots{$cgiparams{'ID'}})) {
+       $error = "$Lang::tr{'snapshot error unknown id'}";
+
+       # Reset the cgiparams
+       %cgiparams;
+}
+
+if ($cgiparams{'SNAPSHOT'} eq "create") {
+       # Reverse the snapshots hash and store in new hash.
+       my %known_snapshot_names = reverse(%snapshots);
+
+       # Store the given snapshot name in a nice acessible variable.
+       my $snapshot_name = $cgiparams{'SNAPSHOT_NAME'} if ($cgiparams{'SNAPSHOT_NAME'});
+
+       # Abort if the filesystem does not contain a btrfs.
+       if ( ! &Filesystem::is_btrfs("/")) {
+               $error = "$Lang::tr{'snapshot error no btrfs'}";
+       }
+
+       # Check for empty input.
+       elsif ( ! $snapshot_name) {
+               $error = "$Lang::tr{'snapshot error empty input'}";
+       }
+
+       # Check allowed characters.
+       elsif ($snapshot_name !~ /^[A-Za-z0-9_\-]+$/) {
+               $error = "$Lang::tr{'snapshot error unallowed chars'}";
+       }
+       
+       # Check, if the give snapshot name allready exists.
+       elsif (exists($known_snapshot_names{$snapshot_name})) {
+               $error = "$Lang::tr{'snapshot error snapshot exists'}";
+       }
+
+       # Go furter if no error occurs.
+       unless ($error) {
+               # Create the snapshot.
+               my $ret = &Filesystem::btrfs_create_snapshot($snapshot_name);
+
+               # Return error if the snapshot could not be created.
+               $error = "ERROR: $ret" if ($ret);
+       }
+
+} elsif ($cgiparams{'SNAPSHOT'} eq "boot") {
+       # Call function and boot into the the given snapshot.
+       my $ret = &Filesystem::btrfs_restore_snapshot($cgiparams{'ID'});
+
+       # Return error if the snapshot can not be used to boot.
+       $error = "ERROR: $ret" if ($ret);
+       
+
+} elsif ($cgiparams{'SNAPSHOT'} eq "restore") {
+       # Call function to restore the selected snapshot.
+       my $ret = &Filesystem::btrfs_restore_snapshot($cgiparams{'ID'});
+
+       # Return error if the snapshot can not be restored.
+       $error = "ERROR: $ret" if ($ret); 
+
+} elsif ($cgiparams{'SNAPSHOT'} eq "delete") {
+       # Call function and delete the snapshot.
+       my $ret = &Filesystem::btrfs_remove_snapshot($cgiparams{'ID'});
+
+       # Return error if the snapshot could not be deleted.
+       $error = "Error: $ret" if ($ret);
+}
+
+# Reload known snapshots if neccessary.
+%snapshots = &Filesystem::btrfs_get_snapshots() if ( exists($cgiparams{'SNAPSHOT'}) and &Filesystem::is_btrfs("/"));
+
+&Header::openpage($Lang::tr{'snapshots'}, 1, '');
+
+# Display error message, in case there is one.
+if ($error) {
+       &Header::openbox('100%', 'left', $Lang::tr{'error messages'});
+               print "<class name='base'>$error\n";
+               print "&nbsp;</class>\n";
+       &Header::closebox();
+}
+
+print"<form method='POST' action='$ENV{'SCRIPT_NAME'}'>\n";
+
+&Header::openbox('100%', 'center', $Lang::tr{'snapshot create'});
+print <<END;
+       <table width='100%'>
+               <tr>
+                       <td width='75%' class='base'>
+                               $Lang::tr{'name'}:&nbsp;&nbsp;<input type='text' name='SNAPSHOT_NAME'>
+                       </td>
+                       <td align='right'>
+                               <input type='hidden' name='SNAPSHOT' value='create'>
+                               <input type='submit' value='$Lang::tr{'create'}'>
+                       </td>
+               </tr>
+       </table>
+</form>
+END
+
+&Header::closebox();
+
+&Header::openbox('100%', 'center', $Lang::tr{'snapshots available'});
+
+print"<table class='tbl' id='snapshots'>\n";
+
+my $lines;
+my $col;
+foreach my $snapshot_id (sort { $b <=> $a } keys %snapshots) {
+       # Get snapshot name.
+       my $snapshot_name = $snapshots{$snapshot_id};
+
+       # Generate full snapshot directory.
+       my $snapshot_dir = "$Filesystem::btrfs_snapshot_dir" . "$snapshot_name";
+
+       # Use stat to get the creation date of the snapshot.
+       my $stats = stat($snapshot_dir) if (-e $snapshot_dir);
+
+       # Convert timestamp into human-readable format.
+       my $snapshot_date = strftime('%Y-%m-%d %H:%M:%S', localtime($stats->mtime));
+
+       # Colour lines.
+       if ($lines % 2) {
+               $col="bgcolor='$color{'color20'}'";
+       } else {
+               $col="bgcolor='$color{'color22'}'";
+       }
+
+       print"<tr>\n";
+               print "<td $col width='20%' $col>$snapshot_date</td>\n";
+               print "<td $col>$snapshot_name</td>\n";
+
+               print "<td $col width='5%' align='center'>\n";
+                       print "<form method='post' action='$ENV{'SCRIPT_NAME'}'>\n";
+                               print "<input type='hidden' name='ID' value='$snapshot_id'>\n";
+                               print "<input type='hidden' name='SNAPSHOT' value='boot'>\n";
+                               print "<input type='image' alt='$Lang::tr{'snapshot boot into'}' title='$Lang::tr{'snapshot boot into'}' src='/images/forward.gif'>\n";
+                       print "</form>\n";
+               print "</td>\n";
+
+               print "<td $col width='5%' align='center'>\n";
+                       print "<form method='post' action='$ENV{'SCRIPT_NAME'}'>\n";
+                               print "<input type='hidden' name='ID' value='$snapshot_id'>\n";
+                               print "<input type='hidden' name='SNAPSHOT' value='restore'>\n";
+                               print "<input type='image' alt='$Lang::tr{'snapshot restore'}' title='$Lang::tr{'snapshot restore'}' src='/images/media-repeat.png'>\n";
+                       print "</form>\n";
+               print "</td>\n";
+
+               print "<td $col width='5%' align='center'>\n";
+                       print "<form method='post' action='$ENV{'SCRIPT_NAME'}'>\n";
+                               print "<input type='hidden' name='ID' value='$snapshot_id'>\n";
+                               print "<input type='hidden' name='SNAPSHOT' value='delete'>\n";
+                               print "<input type='image' alt='$Lang::tr{'snapshot delete'}' title='$Lang::tr{'snapshot delete'}' src='/images/delete.gif'>\n";
+                       print "</form>\n";
+               print "</td>\n";
+       print "</tr>\n";
+
+$lines++;
+}
+
+print <<END;
+</table>
+
+END
+
+&Header::closebox();
+               print "</form>\n";
+
+&Header::closebigbox();
+&Header::closepage();
index 4df95fdaf6022f7d5b618323b397f97223796dab..4094b96e2d0047688f4e6b2f644dcbefdec735bb 100644 (file)
 'smt not supported' => 'Simultanes Multi-Threading nicht unterstützt',
 'smtphost' => 'Smtp Host',
 'smtpport' => 'Smtp Port',
+'snapshot boot into' => 'Neustarten und in den Snapshot booten',
+'snapshot create' => 'Neuen Snapshot erstellen',
+'snapshot delete' => 'Snapshot löschen',
+'snapshot error empty input' => 'Kein Snapshot Name angegeben.',
+'snapshot error no btrfs' => 'Der Datenträger enthält kein gültiges BTRFS Dateisystem.',
+'snapshot error snapshot exists' => 'Ein Snapshot mit diesem Namen existiert bereits.',
+'snapshot error unallowed chars' => 'Unerlaubte Zeichen verwendet - Erlaubt sind Buchstaben, Nummern, Bindestriche und Unterstriche.',
+'snapshot error unknown id' => 'Diese Snapshot ID existiert nicht.',
+'snapshot restore' => 'Neustarten und den Snapshot wiederherstellen',
+'snapshots' => 'Snapshots',
+'snapshots available' => 'Verfügbare Snapshots',
 'snat new source ip address' => 'Neue Quell-IP-Adresse',
 'socket options' => 'Socket Options',
 'software version' => 'Software-Version',
index 647fcc8da64f19c6ebfec4ac0cb4eeb7bc9add97..e66a5e6e4f0a186799cf9da7a91598c51ebca545 100644 (file)
 'smt not supported' => 'Simultaneous Multi-Threading (SMT) is not supported',
 'smtphost' => 'SMTP host',
 'smtpport' => 'SMTP port',
+'snapshot boot into' => 'Restart and boot into snapshot',
+'snapshot create' => 'Create a new snapshot',
+'snapshot delete' => 'Delete snapshot',
+'snapshot error empty input' => 'Empty snapshot name.',
+'snapshot error no btrfs' => 'The filesystem does not contain a valid BTRFS.',
+'snapshot error snapshot exists' => 'A snapshot with this name allready exists.',
+'snapshot error unallowed chars' => 'Unallowed characters used - Allowed are letters, numbers, minus and underscores.',
+'snapshot error unknown id' => 'The given snapshot id does not exist.',
+'snapshot restore' => 'Restart and restore snapshot',
+'snapshots' => 'Snapshots',
+'snapshots available' => 'Available snapshots',
 'snat new source ip address' => 'New source IP address',
 'socket options' => 'Socket options',
 'software version' => 'Software Version',