'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'}",
--- /dev/null
+#!/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 " </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'}: <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();
'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',
'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',