Add GRUB 2 boot loader module

Adds a Webmin GRUB 2 module for inspecting boot entries, editing defaults, custom entries, themes, password protection, BLS-aware kernel options, safe menu regeneration, boot loader installation, manual config editing, status reporting, ACLs, backups, logging, and tests.
This commit is contained in:
Ilia Ross
2026-05-28 02:20:53 +02:00
parent 04efe99340
commit 523d68c67a
85 changed files with 7836 additions and 0 deletions

48
grub2/acl_security.pl Normal file
View File

@@ -0,0 +1,48 @@
use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
do 'grub2-lib.pl';
our (%in, %text);
# acl_security_form(&options)
# Outputs HTML for editing security options for the GRUB 2 module.
sub acl_security_form
{
my ($o) = @_;
print &ui_table_span(&ui_tag('b', &html_escape($text{'acl_section_view'})));
# View is separated because it controls whether the module can be entered.
foreach my $a (qw(view)) {
print &ui_table_row($text{'acl_'.$a},
&ui_yesno_radio($a, &grub2_check_acl($a, $o)), 3);
}
print &ui_table_hr();
print &ui_table_span(&ui_tag('b', &html_escape($text{'acl_section_change'})));
# Change permissions modify GRUB configuration without granting install rights.
foreach my $a (qw(edit security apply runtime)) {
print &ui_table_row($text{'acl_'.$a},
&ui_yesno_radio($a, &grub2_check_acl($a, $o)), 3);
}
print &ui_table_hr();
print &ui_table_span(&ui_tag('b', &html_escape($text{'acl_section_admin'})));
# Admin permissions expose direct file editing, backup, and boot-loader install.
foreach my $a (qw(manual install backup)) {
print &ui_table_row($text{'acl_'.$a},
&ui_yesno_radio($a, &grub2_check_acl($a, $o)), 3);
}
}
# acl_security_save(&options)
# Parses the form for security options for the GRUB 2 module.
sub acl_security_save
{
my ($o) = @_;
foreach my $a (&grub2_acl_keys()) {
# Missing checkbox/radio values fall back to denied.
$o->{$a} = $in{$a} || 0;
}
}
1;

13
grub2/backup_config.pl Normal file
View File

@@ -0,0 +1,13 @@
use strict;
use warnings;
do 'grub2-lib.pl';
# backup_config_files()
# Returns GRUB 2 files and directories that can be backed up.
sub backup_config_files
{
return &grub2_config_files();
}
1;

19
grub2/config Normal file
View File

@@ -0,0 +1,19 @@
default_file=/etc/default/grub
grub_cfg=/boot/grub/grub.cfg
grub_dir=/etc/grub.d
custom_file=/etc/grub.d/40_custom
password_file=/etc/grub.d/01_webmin_password
color_file=/etc/grub.d/06_webmin_colors
theme_dir=/boot/grub/themes
background_dir=/boot/grub/backgrounds
grubenv_file=/boot/grub/grubenv
bls_dir=/boot/loader/entries
mkconfig_cmd=/usr/sbin/grub-mkconfig
install_cmd=/usr/sbin/grub-install
set_default_cmd=/usr/sbin/grub-set-default
reboot_once_cmd=/usr/sbin/grub-reboot
editenv_cmd=/usr/bin/grub-editenv
script_check_cmd=/usr/bin/grub-script-check
mkpasswd_cmd=/usr/bin/grub-mkpasswd-pbkdf2
grubby_cmd=/usr/sbin/grubby
shell_cmd=/bin/sh

19
grub2/config-debian-linux Normal file
View File

@@ -0,0 +1,19 @@
default_file=/etc/default/grub
grub_cfg=/boot/grub/grub.cfg
grub_dir=/etc/grub.d
custom_file=/etc/grub.d/40_custom
password_file=/etc/grub.d/01_webmin_password
color_file=/etc/grub.d/06_webmin_colors
theme_dir=/boot/grub/themes
background_dir=/boot/grub/backgrounds
grubenv_file=/boot/grub/grubenv
bls_dir=/boot/loader/entries
mkconfig_cmd=/usr/sbin/grub-mkconfig
install_cmd=/usr/sbin/grub-install
set_default_cmd=/usr/sbin/grub-set-default
reboot_once_cmd=/usr/sbin/grub-reboot
editenv_cmd=/usr/bin/grub-editenv
script_check_cmd=/usr/bin/grub-script-check
mkpasswd_cmd=/usr/bin/grub-mkpasswd-pbkdf2
grubby_cmd=/usr/sbin/grubby
shell_cmd=/bin/sh

View File

@@ -0,0 +1,19 @@
default_file=/etc/default/grub
grub_cfg=/boot/grub2/grub.cfg
grub_dir=/etc/grub.d
custom_file=/etc/grub.d/40_custom
password_file=/etc/grub.d/01_webmin_password
color_file=/etc/grub.d/06_webmin_colors
theme_dir=/boot/grub2/themes
background_dir=/boot/grub2/backgrounds
grubenv_file=/boot/grub2/grubenv
bls_dir=/boot/loader/entries
mkconfig_cmd=/usr/sbin/grub2-mkconfig
install_cmd=/usr/sbin/grub2-install
set_default_cmd=/usr/sbin/grub2-set-default
reboot_once_cmd=/usr/sbin/grub2-reboot
editenv_cmd=/usr/bin/grub2-editenv
script_check_cmd=/usr/bin/grub2-script-check
mkpasswd_cmd=/usr/bin/grub2-mkpasswd-pbkdf2
grubby_cmd=/usr/sbin/grubby
shell_cmd=/bin/sh

19
grub2/config-redhat-linux Normal file
View File

@@ -0,0 +1,19 @@
default_file=/etc/default/grub
grub_cfg=/boot/grub2/grub.cfg
grub_dir=/etc/grub.d
custom_file=/etc/grub.d/40_custom
password_file=/etc/grub.d/01_webmin_password
color_file=/etc/grub.d/06_webmin_colors
theme_dir=/boot/grub2/themes
background_dir=/boot/grub2/backgrounds
grubenv_file=/boot/grub2/grubenv
bls_dir=/boot/loader/entries
mkconfig_cmd=/usr/sbin/grub2-mkconfig
install_cmd=/usr/sbin/grub2-install
set_default_cmd=/usr/sbin/grub2-set-default
reboot_once_cmd=/usr/sbin/grub2-reboot
editenv_cmd=/usr/bin/grub2-editenv
script_check_cmd=/usr/bin/grub2-script-check
mkpasswd_cmd=/usr/bin/grub2-mkpasswd-pbkdf2
grubby_cmd=/usr/sbin/grubby
shell_cmd=/bin/sh

19
grub2/config-suse-linux Normal file
View File

@@ -0,0 +1,19 @@
default_file=/etc/default/grub
grub_cfg=/boot/grub2/grub.cfg
grub_dir=/etc/grub.d
custom_file=/etc/grub.d/40_custom
password_file=/etc/grub.d/01_webmin_password
color_file=/etc/grub.d/06_webmin_colors
theme_dir=/boot/grub2/themes
background_dir=/boot/grub2/backgrounds
grubenv_file=/boot/grub2/grubenv
bls_dir=/boot/loader/entries
mkconfig_cmd=/usr/sbin/grub2-mkconfig
install_cmd=/usr/sbin/grub2-install
set_default_cmd=/usr/sbin/grub2-set-default
reboot_once_cmd=/usr/sbin/grub2-reboot
editenv_cmd=/usr/bin/grub2-editenv
script_check_cmd=/usr/bin/grub2-script-check
mkpasswd_cmd=/usr/bin/grub2-mkpasswd-pbkdf2
grubby_cmd=/usr/sbin/grubby
shell_cmd=/bin/sh

21
grub2/config.info Normal file
View File

@@ -0,0 +1,21 @@
line1=Configuration files,11
default_file=GRUB default settings file,0
grub_cfg=Generated GRUB menu file,0
grub_dir=GRUB script directory,0
custom_file=Custom GRUB menu entries file,3,None
password_file=Webmin-managed GRUB password script,3,None
color_file=Webmin-managed GRUB color script,3,None
theme_dir=GRUB theme installation directory,3,None
background_dir=GRUB background image installation directory,3,None
grubenv_file=GRUB environment file,3,None
bls_dir=Boot Loader Specification entries directory,3,None
line2=Executables,11
mkconfig_cmd=Command to generate the GRUB menu file,0
install_cmd=Command to install the GRUB boot loader,3,None
set_default_cmd=Command to set the saved default entry,3,None
reboot_once_cmd=Command to set the next boot entry,3,None
editenv_cmd=Command to inspect the GRUB environment,3,None
script_check_cmd=Command to validate GRUB scripts,3,None
mkpasswd_cmd=Command to generate GRUB PBKDF2 password hashes,3,None
grubby_cmd=Command to update BLS kernel options,3,None
shell_cmd=Shell used to validate the default settings file,0

54
grub2/custom_action.cgi Executable file
View File

@@ -0,0 +1,54 @@
#!/usr/local/bin/perl
# Apply an action to selected custom GRUB 2 entries.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%in, %text);
&ReadParse();
&error_setup($text{'custom_err'});
&grub2_assert_acl('manual');
# Per-row up/down links post an index and direction directly.
if (defined($in{'idx'}) || defined($in{'dir'})) {
defined($in{'idx'}) && $in{'idx'} =~ /^\d+\z/ ||
&error($text{'custom_eentry'});
# Keep movement inside one submenu by leaving it to the library helper.
defined($in{'dir'}) && $in{'dir'} =~ /^(up|down)\z/ ||
&error($text{'custom_emove'});
my $err = &grub2_move_custom_entry($in{'idx'}, $in{'dir'});
&error($err) if ($err);
&grub2_mark_regenerate_needed();
&webmin_log("custom_move", undef, $in{'idx'});
&redirect("index.cgi?mode=custom");
}
# Checked-table actions can receive duplicate browser values; collapse them.
my @selected = split(/\0/, defined($in{'d'}) ? $in{'d'} : "");
my %seen;
@selected = grep { defined($_) && $_ ne '' && !$seen{$_}++ } @selected;
my $err;
# Delete accepts multiple selected entries and removes them in one rewrite.
if ($in{'delete'}) {
@selected || &error($text{'delete_enone'});
$err = &grub2_delete_custom_entry_indexes(@selected);
&error($err) if ($err);
&grub2_mark_regenerate_needed();
&webmin_log("custom_delete", undef, scalar(@selected));
}
elsif ($in{'move_up'} || $in{'move_down'}) {
# Bulk move buttons are only safe for one entry at a time.
@selected == 1 || &error($text{'custom_eone'});
$err = &grub2_move_custom_entry($selected[0],
$in{'move_up'} ? "up" : "down");
&error($err) if ($err);
&grub2_mark_regenerate_needed();
&webmin_log("custom_move", undef, $selected[0]);
}
else {
&error($text{'runtime_eaction'});
}
&redirect("index.cgi?mode=custom");

8
grub2/defaultacl Normal file
View File

@@ -0,0 +1,8 @@
view=1
edit=1
security=1
apply=1
runtime=1
manual=1
install=1
backup=1

45
grub2/edit_custom.cgi Executable file
View File

@@ -0,0 +1,45 @@
#!/usr/local/bin/perl
# Show a form for adding or editing a custom GRUB 2 menu entry.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%in, %text);
&ReadParse();
&error_setup($text{'custom_err'});
&grub2_assert_acl('manual');
my $is_new = !defined($in{'idx'}) || $in{'idx'} eq '';
my ($title, $id, $body) =
("", "", "echo 'Custom entry not configured'\ntrue\n");
# Existing entries are looked up by parsed custom-entry index, not line number.
if (!$is_new) {
$in{'idx'} =~ /^\d+\z/ || &error($text{'custom_eentry'});
my $entry = &grub2_custom_entry_by_index($in{'idx'});
&error($text{'custom_eentry'}) if (!$entry);
$title = $entry->{'title'} || "";
$id = $entry->{'id'} || "";
$body = &grub2_custom_entry_body($entry);
}
&ui_print_header(undef, $is_new ? $text{'custom_title_new'} :
$text{'custom_title_edit'}, "");
print &ui_form_start("save_custom.cgi", "post");
print &ui_hidden("idx", $in{'idx'}) if (!$is_new);
print &ui_table_start($text{'custom_header'}, "width=100%", 2);
print &ui_table_row(&hlink($text{'custom_entry_title'}, "custom_title"),
&ui_textbox("title", $title, 60));
print &ui_table_row(&hlink($text{'custom_entry_id'}, "custom_id"),
&ui_textbox("id", $id, 60).
&ui_tag('div', &ui_note($text{'custom_id_note'}, 0)));
print &ui_table_hr();
# The body is stored as GRUB script text inside a generated menuentry wrapper.
print &ui_table_row(&hlink($text{'custom_entry_body'}, "custom_body"),
&ui_textarea("body", $body, 16, 100), 2);
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'save'} ] ]);
&ui_print_footer("index.cgi?mode=custom", $text{'index_return'});

155
grub2/edit_defaults.cgi Executable file
View File

@@ -0,0 +1,155 @@
#!/usr/local/bin/perl
# Show a form for editing common GRUB 2 defaults.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%text);
&ReadParse();
&error_setup($text{'defaults_err'});
&grub2_assert_acl('edit');
my $parsed = &read_grub_defaults();
my $values = $parsed->{'values'};
my @entries = &grub2_boot_entries();
&ui_print_header(undef, $text{'defaults_title'}, "");
# BLS warnings explain when edits also need grubby or existing entry updates.
foreach my $warning (&grub2_bls_kernel_option_warnings(\@entries)) {
print &ui_alert($warning, 'warning');
}
print &ui_form_start("save_defaults.cgi", "post");
print &ui_table_start($text{'defaults_header'}, "width=100%", 2);
print &ui_table_row(
&hlink($text{'defaults_default'}, "default"),
&default_entry_input(&field_value($values->{'GRUB_DEFAULT'}, "0"),
\@entries),
2, undef, undef, 1);
print &ui_table_hr();
print &ui_table_row(
&hlink($text{'defaults_timeout_style'}, "timeout_style"),
&ui_select("timeout_style", &field_value($values->{'GRUB_TIMEOUT_STYLE'}),
[
[ "", $text{'defaults_keep'} ],
[ "menu", $text{'defaults_menu'} ],
[ "hidden", $text{'defaults_hidden'} ],
[ "countdown", $text{'defaults_countdown'} ],
])
);
print &ui_table_row(
&hlink($text{'defaults_timeout'}, "timeout"),
&ui_textbox("timeout", &field_value($values->{'GRUB_TIMEOUT'}), 8)
);
print &ui_table_row(
&hlink($text{'defaults_kernelopts_source'}, "kernelopts_source"),
&html_escape(&grub2_kernel_options_source_text(\@entries))
);
# Recovery is special on BLS systems because rescue entries are separate files.
print &ui_table_row(
&hlink($text{'defaults_disable_recovery'}, "disable_recovery"),
&bool_select("disable_recovery", $values->{'GRUB_DISABLE_RECOVERY'}).
(&grub2_has_bls_rescue_entries() ?
&ui_tag('div', &ui_note($text{'defaults_disable_recovery_bls'}, 0)) :
"")
);
print &ui_table_row(
&hlink($text{'defaults_disable_os_prober'}, "disable_os_prober"),
&bool_select("disable_os_prober", $values->{'GRUB_DISABLE_OS_PROBER'})
);
print &ui_table_hr();
print &ui_table_row(
&hlink($text{'defaults_cmdline_default'}, "cmdline_default"),
&ui_textbox("cmdline_default",
&field_value($values->{'GRUB_CMDLINE_LINUX_DEFAULT'}), 30,
undef, undef, undef, "w-100")
);
print &ui_table_row(
&hlink($text{'defaults_cmdline'}, "cmdline"),
&ui_textbox("cmdline", &field_value($values->{'GRUB_CMDLINE_LINUX'}), 70,
undef, undef, undef, "w-100")
);
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'save'} ] ]);
&ui_print_footer("index.cgi", $text{'index_return'});
# default_entry_input(value, &entries)
# Returns a selector for known boot entries.
sub default_entry_input
{
my ($value, $entries) = @_;
my ($select_value, $options) = &default_entry_options($value, $entries);
return &ui_select("default", $select_value, $options, undef, undef, undef,
undef, 'style="field-sizing: content; max-width: 100%;"');
}
# default_entry_options(value, &entries)
# Returns select value and options for the default entry field.
sub default_entry_options
{
my ($current, $entries) = @_;
$current = '0' if (!defined($current) || $current eq '');
my @options;
my %seen;
my $add = sub {
my ($value, $label) = @_;
# Avoid duplicate selectors when GRUB_DEFAULT already names an entry.
return if (!defined($value) || $value eq '' || $seen{$value}++);
push(@options, [ $value, $label ]);
};
$add->('saved', $text{'defaults_default_saved'});
if ($current =~ /^\d+\z/ && $entries->[$current]) {
# Keep numeric defaults meaningful by showing the currently indexed entry.
$add->($current, &text('defaults_default_current_entry', $current,
&default_entry_label($entries->[$current])));
}
foreach my $entry (@$entries) {
my $selector = &grub2_entry_selector($entry);
next if (!defined($selector) || $selector eq '');
$add->($selector, &default_entry_label($entry));
}
if ($current ne '' && !$seen{$current}) {
# Preserve unusual existing values without allowing arbitrary new input.
$add->($current, &text('defaults_default_current', $current));
}
my $select_value = $seen{$current} ? $current : $options[0]->[0];
return ($select_value, \@options);
}
# default_entry_label(&entry)
# Returns a concise label for one parsed generated boot entry.
sub default_entry_label
{
my ($entry) = @_;
my @path = @{$entry->{'path'} || []};
my $label = join(' > ', (@path, $entry->{'title'} || ''));
return &text('defaults_default_entry_id', $label, $entry->{'id'})
if (defined($entry->{'id'}) && $entry->{'id'} ne '');
return $label;
}
# bool_select(name, value)
# Returns a tri-state selector for GRUB true/false settings.
sub bool_select
{
my ($name, $value) = @_;
$value = '' if (!defined($value) || $value !~ /^(true|false)\z/);
return &ui_select($name, $value,
[
[ "", $text{'defaults_keep'} ],
[ "true", $text{'defaults_true'} ],
[ "false", $text{'defaults_false'} ],
]);
}
# field_value(value, [default])
# Returns a form value without treating the string 0 as empty.
sub field_value
{
my ($value, $default) = @_;
return defined($value) ? $value : ($default || '');
}

103
grub2/edit_install.cgi Executable file
View File

@@ -0,0 +1,103 @@
#!/usr/local/bin/perl
# Show a form for installing the GRUB 2 boot loader.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%text);
&ReadParse();
&error_setup($text{'install_err'});
&grub2_assert_acl('install');
my $cmd = &grub2_command('install_cmd');
my $platform = &grub2_default_platform_target();
my $module_dir = $platform ? &grub2_platform_module_dir($platform) : '';
my $efi_dir = &grub2_default_efi_directory();
my $bootloader_id = &grub2_default_bootloader_id($efi_dir);
&ui_print_header(undef, $text{'install_title'}, "", "install_target");
# Installation is unavailable unless the configured grub-install is runnable.
if (!$cmd) {
print &ui_alert($text{'install_ecmd'}, 'warning');
&ui_print_footer("index.cgi", $text{'index_return'});
exit;
}
if ($platform && !$module_dir) {
# Missing module directories usually mean the platform package is absent.
print &ui_alert(&text('install_warn_modules', $platform), 'warning');
}
print &ui_form_start("install.cgi", "post");
print &ui_table_start($text{'install_header'}, "width=100%", 2);
print &ui_table_row($text{'install_command'},
&ui_tag('tt', &html_escape($cmd)));
print &ui_table_row($text{'index_boot_mode'}, &install_boot_mode_cell());
print &ui_table_row($text{'index_secure_boot'}, &install_secure_boot_cell());
print &ui_table_row(
&hlink($text{'install_target'}, "install_target"),
&ui_filebox("target", "", 45)
);
print &ui_table_row(
&hlink($text{'install_efi_dir'}, "install_efi_dir"),
&ui_filebox("efi_dir", $efi_dir, 45, 0, undef, undef, 1)
);
print &ui_table_row(
&hlink($text{'install_platform'}, "install_platform"),
&ui_textbox("platform", $platform, 25)
);
print &ui_table_row(
&hlink($text{'install_directory'}, "install_directory"),
&ui_filebox("directory", $module_dir, 45, 0, undef, undef, 1)
);
# Keep --boot-directory opt-in because it changes install layout.
print &ui_table_row(
&hlink($text{'install_boot_directory'}, "install_boot_directory"),
&ui_filebox("boot_directory", "/boot", 45, 0, undef, undef, 1).
&ui_tag('div',
&ui_checkbox("use_boot_directory", 1,
$text{'install_boot_directory_enable'}, 0),
{ 'style' => 'margin-left: 2px' })
);
print &ui_table_row(
&hlink($text{'install_bootloader_id'}, "install_bootloader_id"),
&ui_textbox("bootloader_id", $bootloader_id, 30)
);
print &ui_table_hr();
print &ui_table_row(
$text{'install_options'},
&ui_div(&ui_checkbox("recheck", 1, $text{'install_recheck'}, 0)).
&ui_div(&ui_checkbox("removable", 1, $text{'install_removable'}, 0)).
&ui_div(&ui_checkbox("no_nvram", 1, $text{'install_no_nvram'}, 0)).
&ui_div(&ui_checkbox("force", 1,
&hlink($text{'install_force'}, "install_force"),
0))
);
print &ui_table_hr();
print &ui_table_row(
&hlink($text{'install_confirm'}, "install_confirm"),
&ui_checkbox("confirm", 1, $text{'install_confirm_label'}, 0)
);
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'install_submit'} ] ]);
&ui_print_footer("index.cgi", $text{'index_return'});
# install_boot_mode_cell()
# Returns the detected firmware boot mode for display.
sub install_boot_mode_cell
{
my $mode = &grub2_boot_mode();
return $text{'index_boot_mode_uefi'} if ($mode eq 'uefi');
return $text{'index_boot_mode_bios'};
}
# install_secure_boot_cell()
# Returns the detected Secure Boot state for display.
sub install_secure_boot_cell
{
my $state = &grub2_secure_boot_status();
return $text{'index_secure_boot_'.$state} || $text{'index_secure_boot_unknown'};
}

44
grub2/edit_manual.cgi Executable file
View File

@@ -0,0 +1,44 @@
#!/usr/local/bin/perl
# Show a page for manually editing allowed GRUB 2 files.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%in, %text);
&ReadParse();
&error_setup($text{'manual_err'});
&grub2_assert_acl('manual');
# The manual editor is restricted to discovered GRUB-related files only.
my @files = &grub2_manual_files();
@files || &error($text{'manual_enofile'});
my @paths = map { $_->{'file'} } @files;
my $file = $in{'file'} || $paths[0];
&grub2_manual_file($file) || &error($text{'manual_efile'});
&ui_print_header(undef, $text{'manual_title'}, "");
print &ui_form_start("edit_manual.cgi");
print &ui_tag('b', &html_escape($text{'manual_select'})),"\n";
print &ui_select("file", $file, \@paths),"\n";
print &ui_submit($text{'manual_ok'});
print &ui_form_end();
# Lock while reading so the text shown matches the file validation target.
my $data = "";
if (-r $file) {
&lock_file($file);
$data = &read_file_contents($file);
&unlock_file($file);
}
print &ui_form_start("save_manual.cgi", "form-data");
print &ui_hidden("file", $file);
print &ui_table_start(undef, undef, 2);
print &ui_table_row(undef, &ui_textarea("data", $data, 24, 100), 2);
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'save'} ] ]);
&ui_print_footer("index.cgi", $text{'index_return'});

75
grub2/edit_security.cgi Executable file
View File

@@ -0,0 +1,75 @@
#!/usr/local/bin/perl
# Show a form for editing GRUB 2 password protection.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%text);
&ReadParse();
&error_setup($text{'security_err'});
&grub2_assert_acl('security');
my $state = &grub2_read_security_config();
&ui_print_header(undef, $text{'security_title'}, "", "security_current");
# Refuse to edit administrator-owned password scripts we cannot safely merge.
if ($state->{'exists'} && !$state->{'managed'}) {
print &ui_alert($text{'security_unmanaged'}, 'warning');
&ui_print_footer("index.cgi", $text{'index_return'});
exit;
}
print &ui_form_start("save_security.cgi", "post");
print &ui_table_start($text{'security_header'}, "width=100%", 2);
print &ui_table_row(
$text{'security_current_state'},
$state->{'enabled'} ?
&text('security_current_enabled',
&html_escape($state->{'user'} || 'root')) :
$text{'security_current_disabled'}
);
print &ui_table_row(
$text{'security_current_hash'},
$state->{'hash'} ? $text{'security_current_hash_set'} :
$text{'security_current_hash_missing'}
);
print &ui_table_hr();
print &ui_table_row(
&hlink($text{'security_enable'}, "security_enable"),
&ui_yesno_radio("enabled", $state->{'enabled'} ? 1 : 0)
);
print &ui_table_row(
&hlink($text{'security_user'}, "security_user"),
&ui_textbox("user", $state->{'user'} || "root", 30)
);
print &ui_table_hr();
print &ui_table_row(
$text{'security_password_status'},
$state->{'enabled'} && $state->{'hash'} ?
$text{'security_password_keep'} :
$text{'security_password_required'}
);
# Password fields are optional when keeping the existing PBKDF2 hash.
print &ui_table_row(
&hlink($text{'security_newpass'}, "security_password"),
&ui_password("password", "", 30)
);
print &ui_table_row(
&hlink($text{'security_newpass2'}, "security_password"),
&ui_password("password2", "", 30)
);
print &ui_table_hr();
# Existing hashes are shown because GRUB stores hashes, not clear text.
print &ui_table_row(
&hlink($text{'security_hash'}, "security_hash"),
&ui_textbox("hash", $state->{'hash'} || "", 30, undef, undef, undef,
"w-100").
&ui_tag('div', &ui_note($text{'security_hash_note'}, 0)),
2, undef, undef, 1);
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'save'} ] ]);
&ui_print_footer("index.cgi", $text{'index_return'});

208
grub2/edit_theme.cgi Executable file
View File

@@ -0,0 +1,208 @@
#!/usr/local/bin/perl
# Show a form for editing GRUB 2 theme and appearance settings.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%text);
&ReadParse();
&error_setup($text{'theme_err'});
&grub2_assert_acl('edit');
my $parsed = &read_grub_defaults();
my $values = $parsed->{'values'};
my $current_theme = &field_value($values->{'GRUB_THEME'});
my $theme_action = $current_theme ne '' ? 'keep' : 'clear';
&ui_print_header(undef, $text{'theme_title'}, "", "theme_mode");
# Theme source is only used when the action selector requests installation.
print &ui_form_start("save_theme.cgi", "post");
print &ui_table_start($text{'defaults_theme_header'}, "width=100%", 2);
print &ui_table_row(
&hlink($text{'defaults_theme_current'}, "theme"),
$current_theme ne '' ?
&ui_tag('tt', &html_escape($current_theme)) :
$text{'defaults_theme_none'}
);
print &ui_table_row(
&hlink($text{'defaults_theme_action'}, "theme_mode"),
&ui_select("theme_mode", $theme_action,
[
[ "keep", $text{'defaults_theme_keep'} ],
[ "install", $text{'defaults_theme_install'} ],
[ "clear", $text{'defaults_theme_clear'} ],
])
);
print &ui_table_row(
&hlink($text{'defaults_theme_source'}, "theme_source"),
&ui_textbox("theme_source", "", 60).
" ".&file_chooser_button("theme_source").
&ui_tag('div', &ui_note($text{'defaults_theme_note'}, 0))
);
print &ui_table_row(
&hlink($text{'defaults_terminal_output'}, "terminal_output"),
&ui_select("terminal_output",
&field_value($values->{'GRUB_TERMINAL_OUTPUT'}),
[
[ "", $text{'defaults_keep'} ],
[ "console", $text{'defaults_terminal_console'} ],
[ "gfxterm", $text{'defaults_terminal_gfxterm'} ],
[ "gfxterm console",
$text{'defaults_terminal_gfxterm_console'} ],
[ "serial", $text{'defaults_terminal_serial'} ],
])
);
print &ui_table_row(
&hlink($text{'defaults_gfxmode'}, "gfxmode"),
&gfxmode_select(&field_value($values->{'GRUB_GFXMODE'}))
);
print &ui_table_row(
&hlink($text{'defaults_background'}, "background"),
&ui_textbox("background", &field_value($values->{'GRUB_BACKGROUND'}), 60).
" ".&file_chooser_button("background").
&ui_tag('div', &ui_note($text{'defaults_background_note'}, 0))
);
print &ui_table_row(
&hlink($text{'defaults_color_normal'}, "color_normal"),
&color_pair_select("color_normal", $values->{'GRUB_COLOR_NORMAL'},
"white", "black")
);
print &ui_table_row(
&hlink($text{'defaults_color_highlight'}, "color_highlight"),
&color_pair_select("color_highlight",
$values->{'GRUB_COLOR_HIGHLIGHT'},
"black", "light-gray")
);
print &ui_table_end();
print &ui_form_end([ [ undef, $text{'save'} ] ]);
print &color_mode_script();
&ui_print_footer("index.cgi", $text{'index_return'});
# color_pair_select(name, value, default-foreground, default-background)
# Returns foreground/background selectors for GRUB menu colors.
sub color_pair_select
{
my ($name, $value, $default_fg, $default_bg) = @_;
my ($fg, $bg) = ($default_fg, $default_bg);
my $mode = "default";
if (defined($value) && $value =~ /^([^\/]+)\/([^\/]+)\z/) {
# Existing GRUB colors are stored as foreground/background pairs.
($fg, $bg) = ($1, $2);
$mode = "set";
}
return &ui_select($name."_mode", $mode, [
[ "default", $text{'defaults_color_default'} ],
[ "set", $text{'defaults_color_custom'} ],
]).
&ui_tag('span',
" ".&html_escape($text{'defaults_color_text'})." ".
&ui_select($name."_fg", $fg, &color_options())." ".
" ".&html_escape($text{'defaults_color_background'}).
" ".&ui_select($name."_bg", $bg, &color_options()),
{
'id' => $name."_custom_colors",
'style' => 'white-space: nowrap; visibility: '.
($mode eq 'set' ? 'visible' : 'hidden').';',
});
}
# color_mode_script()
# Shows foreground/background selectors only for custom color pairs.
sub color_mode_script
{
return &ui_tag('script', <<'EOF', { 'type' => 'application/javascript' });
function grub2_color_mode_select(name) {
// Theme reloads may replace IDs; fall back to the stable field name.
return document.getElementById(name + '_mode') ||
document.querySelector('select[name="' + name + '_mode"]');
}
function grub2_color_mode_changed(name) {
const mode = grub2_color_mode_select(name);
const custom = document.getElementById(name + '_custom_colors');
if (!mode || !custom) {
return;
}
// Visibility avoids layout jumps when a custom color pair is enabled.
custom.style.visibility = mode.value === 'set' ? 'visible' : 'hidden';
}
function grub2_color_modes_refresh() {
grub2_color_mode_changed('color_normal');
grub2_color_mode_changed('color_highlight');
}
document.addEventListener('change', function(event) {
const target = event.target;
if (!target || !target.name) {
return;
}
if (target.name === 'color_normal_mode') {
grub2_color_mode_changed('color_normal');
}
else if (target.name === 'color_highlight_mode') {
grub2_color_mode_changed('color_highlight');
}
});
grub2_color_modes_refresh();
document.addEventListener('DOMContentLoaded', grub2_color_modes_refresh);
if (window.MutationObserver && document.body) {
// Re-apply after theme JavaScript re-renders form controls.
new MutationObserver(grub2_color_modes_refresh).observe(document.body, {
childList: true,
subtree: true
});
}
EOF
}
# gfxmode_select(value)
# Returns a dropdown of common GRUB graphical resolutions.
sub gfxmode_select
{
my ($value) = @_;
return &ui_select("gfxmode", $value, &gfxmode_options(), undef, undef, 1);
}
# gfxmode_options()
# Returns common GRUB graphical resolution choices.
sub gfxmode_options
{
return [
[ "", $text{'defaults_gfxmode_default'} ],
[ "auto", $text{'defaults_gfxmode_auto'} ],
map { [ $_, $_ ] } qw(
640x480
800x600
1024x768
1280x720
1280x800
1366x768
1440x900
1600x900
1680x1050
1920x1080
1920x1200
2560x1440
3840x2160
),
];
}
# color_options()
# Returns GRUB color choices.
sub color_options
{
return [
map { [ $_, $text{'color_'.$_} || $_ ] } &grub2_color_names()
];
}
# field_value(value, [default])
# Returns a form value without treating the string 0 as empty.
sub field_value
{
my ($value, $default) = @_;
return defined($value) ? $value : ($default || '');
}

143
grub2/generate.cgi Executable file
View File

@@ -0,0 +1,143 @@
#!/usr/local/bin/perl
# Generate the GRUB 2 menu file after a successful test generation.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%in, %text);
&ReadParse();
&error_setup($text{'generate_err'});
&grub2_assert_acl('apply');
my $return_url = $in{'redir'} || "index.cgi";
my ($current_step, $failed_printed, $failure_output_shown, $command_output) =
('', 0, 0, '');
&ui_print_unbuffered_header(undef, $text{'generate_title'}, "");
# The generator emits coarse events so failures can show the right phase.
my $callback = sub {
my ($event, $value) = @_;
if ($event eq 'command') {
# Capture the command text and all output for a single disclosure block.
$current_step = 'command';
$command_output = $value."\n";
&print_step_start($text{'generate_regenerating'});
return;
}
if ($event eq 'output') {
$command_output .= $value;
return;
}
if ($event eq 'command_done') {
# Successful generation keeps noisy mkconfig output collapsed.
&print_step_output($text{'generate_done'}, $command_output);
$current_step = '';
return;
}
if ($event eq 'command_failed') {
# On command failure the captured output is the most useful detail.
&print_step_output($text{'generate_failed_status'},
$command_output);
$current_step = '';
$failed_printed = 1;
$failure_output_shown = 1;
return;
}
if ($event eq 'check') {
# The generated temporary grub.cfg is syntax-checked before replace.
$current_step = 'check';
&print_step_start($text{'generate_check'});
return;
}
if ($event eq 'check_done') {
&print_step_done(\$current_step);
return;
}
if ($event eq 'check_failed') {
&print_step_failed(\$current_step, \$failed_printed);
return;
}
if ($event eq 'replace') {
# Replacement only happens after a successful generation and check.
$current_step = 'replace';
&print_step_start($text{'generate_replace'});
return;
}
if ($event eq 'replace_done') {
&print_step_done(\$current_step);
return;
}
};
my $err = &grub2_generate_config($callback);
if ($err) {
# If the command output was not already shown, print the returned error.
&print_step_failed(\$current_step, \$failed_printed)
if ($current_step);
&print_step_output($text{'generate_failed_status'}, $err)
if (!$failure_output_shown);
}
else {
&grub2_mark_generated();
&webmin_log("generate", undef, &grub2_config_value('grub_cfg'));
}
&ui_print_footer($return_url, $text{'generate_return'});
# print_step_start(text)
# Prints the first progress line for one generation step.
sub print_step_start
{
my ($msg) = @_;
print &ui_tag('span', &html_escape($msg." .."),
{ 'data-first-print' => undef });
print "<br>\n";
return;
}
# print_step_output(status, output)
# Prints command output inside an inline details disclosure.
sub print_step_output
{
my ($status, $output) = @_;
$output = '' if (!defined($output));
print &ui_details({
'html' => 1,
'title' => &ui_tag('span', &html_escape(".. ".$status),
{ 'data-second-print' => undef }),
'content' => &ui_tag('pre', &html_escape($output),
{ 'style' => 'margin-left: 10px;' }),
'class' => 'inline inlined',
});
print "<div data-x-br=\"\"></div>\n";
return;
}
# print_step_done(&current-step)
# Prints a successful progress line and clears the active step.
sub print_step_done
{
my ($current) = @_;
print &ui_tag('span', &html_escape(".. ".$text{'generate_done'}),
{ 'data-second-print' => undef });
print "<br><div data-x-br=\"\"></div>\n";
$$current = '';
return;
}
# print_step_failed(&current-step, &printed-flag)
# Prints a failed progress line once and clears the active step.
sub print_step_failed
{
my ($current, $printed) = @_;
return if ($$printed);
print &ui_tag('span', &html_escape(".. ".$text{'generate_failed_status'}),
{ 'data-second-print' => undef });
print "<br><div data-x-br=\"\"></div>\n";
$$current = '';
$$printed = 1;
return;
}

3347
grub2/grub2-lib.pl Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
<header>Background image</header>
<p>Sets the image used as the GRUB menu background. Select a PNG, JPEG, or TGA
file. Webmin copies it under the configured GRUB background directory in
<tt>/boot</tt> so GRUB can read it at boot time.</p>

6
grub2/help/bls_dir.html Normal file
View File

@@ -0,0 +1,6 @@
<header>BLS entries directory</header>
<p>Directory containing Boot Loader Specification entry files, usually
<tt>/boot/loader/entries</tt> on Enterprise Linux style systems.</p>
<p>Each <tt>.conf</tt> file describes one boot entry. Kernel options may be
stored directly in these files or read from <tt>kernelopts</tt> in the GRUB
environment.</p>

View File

@@ -0,0 +1,6 @@
<header>Boot mode</header>
<p>Shows whether the running system appears to have booted through UEFI
firmware or legacy BIOS firmware.</p>
<p>This matters because GRUB is installed differently in each mode. UEFI
systems normally use an EFI system partition and a boot loader ID, while BIOS
systems normally install GRUB boot code to a disk device.</p>

12
grub2/help/cmdline.html Normal file
View File

@@ -0,0 +1,12 @@
<header>Kernel options for all Linux entries</header>
<p>Edits <tt>GRUB_CMDLINE_LINUX</tt>. GRUB menu generators normally add this
value to every generated Linux menu entry, including recovery or rescue
entries.</p>
<p>Use this for options that must be present whenever Linux boots, such as
storage, root filesystem, LVM, filesystem flag, security, mitigation, or
<tt>crashkernel</tt> options.</p>
<p>On BLS-based systems, existing boot entries may store options directly in
BLS entry files, or reference <tt>$kernelopts</tt>, a GRUB environment value
stored in <tt>grubenv</tt>. Webmin does not edit <tt>grubenv</tt> directly;
when <tt>grubby</tt> is available, Webmin asks it to apply changes here to all
existing BLS entries.</p>

View File

@@ -0,0 +1,12 @@
<header>Kernel options for regular Linux entries</header>
<p>Edits <tt>GRUB_CMDLINE_LINUX_DEFAULT</tt>. GRUB menu generators normally add
this value to regular Linux menu entries, but not to generated recovery or
rescue entries.</p>
<p>Use this for options that make normal boots quieter or more convenient, such
as <tt>quiet</tt>, <tt>splash</tt>, or console and graphics workarounds that
are not needed for recovery boots.</p>
<p>On BLS-based systems, existing boot entries may store options directly in
BLS entry files, or reference <tt>$kernelopts</tt>, a GRUB environment value
stored in <tt>grubenv</tt>. Webmin does not edit <tt>grubenv</tt> directly;
when <tt>grubby</tt> is available, Webmin asks it to apply changes here to
existing non-rescue BLS entries.</p>

View File

@@ -0,0 +1,3 @@
<header>Selected menu colors</header>
<p>Sets the foreground and background colors for the currently selected GRUB
menu entry. Leave both values unset to remove this setting.</p>

View File

@@ -0,0 +1,3 @@
<header>Normal menu colors</header>
<p>Sets the foreground and background colors for normal GRUB menu entries.
Leave both values unset to remove this setting.</p>

View File

@@ -0,0 +1,3 @@
<header>GRUB commands</header>
<p>Commands to run inside the custom menu entry. Do not include the outer
menuentry line; Webmin writes that from the title and ID fields.</p>

View File

@@ -0,0 +1,4 @@
<header>Entry ID</header>
<p>An optional stable identifier used by GRUB commands such as
grub-set-default and grub-reboot. Leave this blank if the entry does not need a
stable ID.</p>

View File

@@ -0,0 +1,2 @@
<header>Menu title</header>
<p>The title shown in the GRUB boot menu for this custom entry.</p>

5
grub2/help/default.html Normal file
View File

@@ -0,0 +1,5 @@
<header>Default menu entry</header>
<p>Edits <tt>GRUB_DEFAULT</tt>, the GRUB menu entry booted automatically.
Choose a detected entry, or choose <tt>Saved environment entry</tt> to use the
entry stored in the GRUB environment. Webmin's Set as default action updates
that saved environment entry.</p>

View File

@@ -0,0 +1,7 @@
<header>Default settings file</header>
<p>Path to the GRUB defaults file, usually <tt>/etc/default/grub</tt>. This
file contains shell-style assignments such as <tt>GRUB_DEFAULT</tt>,
<tt>GRUB_TIMEOUT</tt>, and kernel command-line settings.</p>
<p>Changes here normally affect the generated menu after the GRUB menu is
regenerated. On BLS-based systems, some existing entries may also be updated
with <tt>grubby</tt>.</p>

View File

@@ -0,0 +1,3 @@
<header>Disable OS prober</header>
<p>When enabled, GRUB does not run OS prober while generating the menu. This
prevents automatic entries for other installed operating systems.</p>

View File

@@ -0,0 +1,9 @@
<header>Disable recovery and rescue entries</header>
<p>When enabled, Webmin sets <tt>GRUB_DISABLE_RECOVERY=true</tt>, which tells
GRUB generator scripts to omit recovery-mode entries on distributions that
support this option.</p>
<p>On BLS-based systems such as Rocky, AlmaLinux, and RHEL, rescue entries are
separate files under <tt>/boot/loader/entries</tt>. Webmin hides existing BLS
rescue entries by renaming their entry files, and restores only entries hidden
by Webmin when this option is disabled again. Future kernel or rescue-image
updates may create new BLS rescue entries.</p>

5
grub2/help/entries.html Normal file
View File

@@ -0,0 +1,5 @@
<header>Boot menu entries</header>
<p>Number of boot entries detected from the generated GRUB menu and, when
present, from BLS entry files referenced by that menu.</p>
<p>This count is read-only status. Use the generated menu tab to inspect the
entries and use the custom entries tab for entries managed by this module.</p>

9
grub2/help/gfxmode.html Normal file
View File

@@ -0,0 +1,9 @@
<header>Graphics mode</header>
<p>Sets the GRUB graphical screen mode used by themes and the graphical
terminal. The theme provides layout and images, but this setting chooses the
resolution GRUB renders them at.</p>
<p>Use <tt>auto</tt> to let GRUB choose, or enter one or more modes such as
<tt>1024x768</tt> or <tt>1280x1024x32</tt>, separated by commas. If a theme is
cropped or shifted, try a resolution closer to the one the theme was designed
for.</p>

6
grub2/help/grub_cfg.html Normal file
View File

@@ -0,0 +1,6 @@
<header>Generated menu file</header>
<p>Path to the generated GRUB menu file, commonly <tt>/boot/grub2/grub.cfg</tt>
or <tt>/boot/grub/grub.cfg</tt>. GRUB reads this file at boot to build the boot
menu and run boot commands.</p>
<p>On BLS-based systems this file may contain loader code that reads separate
BLS entry files instead of listing every kernel entry directly.</p>

6
grub2/help/grub_dir.html Normal file
View File

@@ -0,0 +1,6 @@
<header>Script directory</header>
<p>Directory containing GRUB menu generator scripts, usually
<tt>/etc/grub.d</tt>. These scripts are run by <tt>grub-mkconfig</tt> or
<tt>grub2-mkconfig</tt> to build the generated menu file.</p>
<p>Distribution scripts should usually be left alone. Custom menu entries are
normally placed in the configured custom script instead.</p>

7
grub2/help/grubenv.html Normal file
View File

@@ -0,0 +1,7 @@
<header>Environment file</header>
<p>Path to the GRUB environment file, commonly <tt>/boot/grub2/grubenv</tt> or
<tt>/boot/grub/grubenv</tt>. GRUB uses this file for persistent runtime state
such as <tt>saved_entry</tt>, <tt>next_entry</tt>, and sometimes
<tt>kernelopts</tt>.</p>
<p>This module reads the file for status and uses GRUB tools for runtime boot
selection changes.</p>

View File

@@ -0,0 +1,6 @@
<header>Boot directory</header>
<p>When enabled, this path is passed to <tt>grub-install</tt> as
<tt>--boot-directory</tt>. Leave it disabled for normal installs, where GRUB
uses its default boot directory, usually <tt>/boot</tt>.</p>
<p>Use this only for custom layouts, chroots, rescue installs, or systems where
the GRUB boot files must be installed under a non-default boot directory.</p>

View File

@@ -0,0 +1,3 @@
<header>Boot loader ID</header>
<p>Optional name for the EFI boot loader entry, such as <tt>GRUB</tt> or a distribution name. Leave it blank to let grub-install use its default.</p>

View File

@@ -0,0 +1,6 @@
<header>Boot loader install command</header>
<p>Command used to install GRUB boot loader files to a disk or EFI system
partition, usually <tt>grub2-install</tt> or <tt>grub-install</tt>.</p>
<p>This is separate from regenerating the menu. Installing GRUB changes boot
loader data on disk or in firmware and should be done only when the target is
known to be correct.</p>

View File

@@ -0,0 +1,3 @@
<header>Confirmation</header>
<p>Installing GRUB changes boot loader data on disk or in EFI firmware. Confirm this only after checking that the target and EFI directory match the system you intend to boot.</p>

View File

@@ -0,0 +1,3 @@
<header>GRUB module directory</header>
<p>Optional directory containing GRUB platform files and <tt>modinfo.sh</tt>. This is normally detected automatically from the platform target, but can be set when the distribution keeps GRUB modules in a non-standard location.</p>

View File

@@ -0,0 +1,3 @@
<header>EFI system directory</header>
<p>For UEFI systems, enter the mounted EFI system partition directory, usually <tt>/boot/efi</tt>. Webmin passes this to grub-install as the EFI directory.</p>

View File

@@ -0,0 +1,3 @@
<header>Force EFI install</header>
<p>Some distributions refuse to run <tt>grub-install</tt> for EFI platforms because the result may not support UEFI Secure Boot. Enable this only when Secure Boot is disabled or when you intentionally need grub-install to proceed anyway.</p>

View File

@@ -0,0 +1,3 @@
<header>Platform target</header>
<p>The GRUB platform to install, such as <tt>x86_64-efi</tt>, <tt>arm64-efi</tt>, or <tt>i386-pc</tt>. Webmin detects this from the current boot mode and CPU architecture when possible.</p>

View File

@@ -0,0 +1,3 @@
<header>Install target</header>
<p>Enter the device that GRUB should install to, such as <tt>/dev/sda</tt>, <tt>/dev/nvme0n1</tt>, or a stable path below <tt>/dev/disk/by-id</tt>. For EFI-only installs this may be left blank when an EFI system directory is supplied.</p>

View File

@@ -0,0 +1,11 @@
<header>Detected kernel options source</header>
<p>Shows where the currently detected Linux boot entries get their kernel
command-line options.</p>
<p>Generated menu entries usually come from the GRUB defaults file when the
menu is regenerated. BLS entries may instead store options in
<tt>/boot/loader/entries</tt> files, or reference <tt>$kernelopts</tt>.</p>
<p><tt>kernelopts</tt> is not a setting in <tt>/etc/default/grub</tt>. It is a
GRUB environment value stored in <tt>grubenv</tt> and commonly managed with
<tt>grubby</tt>. Webmin does not edit <tt>grubenv</tt> directly; when
<tt>grubby</tt> is available, Webmin asks it to apply changed kernel options to
existing BLS entries.</p>

5
grub2/help/mkconfig.html Normal file
View File

@@ -0,0 +1,5 @@
<header>Menu generation command</header>
<p>Command used to rebuild the generated GRUB menu file, such as
<tt>grub2-mkconfig</tt> or <tt>grub-mkconfig</tt>.</p>
<p>When regenerating the menu, Webmin writes to a temporary file first, checks
that generated file, and replaces the live menu only after the checks pass.</p>

View File

@@ -0,0 +1,5 @@
<header>Next boot entry</header>
<p>Shows the <tt>next_entry</tt> value from the GRUB environment file. When set,
GRUB uses this entry for the next boot only and then clears it.</p>
<p>The Boot Once action sets this value with the configured GRUB runtime
command.</p>

View File

@@ -0,0 +1,6 @@
<header>Environment default entry</header>
<p>Shows the <tt>saved_entry</tt> value from the GRUB environment file. When
<tt>GRUB_DEFAULT=saved</tt>, GRUB uses this saved value as the default boot
entry.</p>
<p>The Set as default action updates this value with the configured GRUB
runtime command.</p>

View File

@@ -0,0 +1,6 @@
<header>Secure Boot</header>
<p>Shows the detected UEFI Secure Boot state. Secure Boot is only meaningful on
UEFI systems; on legacy BIOS systems it is not applicable.</p>
<p>Some distributions restrict or discourage direct <tt>grub-install</tt> use
when Secure Boot is enabled because an incorrectly installed boot loader may no
longer satisfy firmware signature checks.</p>

View File

@@ -0,0 +1,15 @@
<header>Current protection</header>
<p>GRUB password protection adds a GRUB superuser account to the generated boot
menu. GRUB asks for this password when someone tries to use the GRUB command
line or edit boot menu entries. Depending on how menu entries are generated, it
may also be required to boot entries that are not marked as unrestricted.</p>
<p>This is useful on kiosks, lab systems, shared consoles, and other systems
where an untrusted person may reach the boot menu. It helps stop simple boot
menu attacks such as editing kernel parameters, starting a GRUB shell, or
booting an unrestricted recovery entry.</p>
<p>It does not protect against someone who can change firmware boot settings,
boot from other media, remove the disk, modify an unencrypted boot partition, or
log in as root. Use it with firmware security, Secure Boot, encrypted storage,
and normal operating system access controls when those threats matter.</p>

View File

@@ -0,0 +1,3 @@
<header>Enable password protection</header>
<p>Controls whether Webmin writes GRUB superuser password commands into the
generated boot menu. Regenerate the GRUB menu after saving.</p>

View File

@@ -0,0 +1,6 @@
<header>Password script</header>
<p>Path to the GRUB generator script that defines the Webmin-managed GRUB
superuser and password hash.</p>
<p>The script is included when the generated menu is rebuilt. If the file
exists but is not recognized as managed by this module, the password status is
shown as unmanaged.</p>

View File

@@ -0,0 +1,4 @@
<header>PBKDF2 hash</header>
<p>This is the stored GRUB password hash, not the original password. Leave it
unchanged to keep the current password, or replace it with a hash produced by
GRUB's password hash command.</p>

View File

@@ -0,0 +1,5 @@
<header>Password hash command</header>
<p>Command used to generate GRUB PBKDF2 password hashes, usually
<tt>grub2-mkpasswd-pbkdf2</tt> or <tt>grub-mkpasswd-pbkdf2</tt>.</p>
<p>GRUB stores the hash, not the original password. This command is needed when
setting or replacing password protection from this module.</p>

View File

@@ -0,0 +1,4 @@
<header>New password</header>
<p>Enter and confirm a new password to replace the stored GRUB PBKDF2 hash.
GRUB does not store the original password, so existing passwords cannot be
displayed.</p>

View File

@@ -0,0 +1,3 @@
<header>Superuser name</header>
<p>The GRUB user allowed to unlock protected boot menu editing and command-line
access.</p>

View File

@@ -0,0 +1,4 @@
<header>Terminal output</header>
<p>Controls the GRUB terminal used for the boot menu. Console output shows the
plain text menu. Graphical terminal output is required for GRUB themes and
background images to appear.</p>

5
grub2/help/theme.html Normal file
View File

@@ -0,0 +1,5 @@
<header>Theme file</header>
<p>Shows the extracted GRUB theme definition file currently saved in the GRUB
defaults file. Webmin installs new themes under the configured GRUB theme
directory in <tt>/boot</tt> and saves the installed theme.txt path here.</p>
<p>The boot menu must use graphical terminal output for themes to appear.</p>

View File

@@ -0,0 +1,6 @@
<header>Theme action</header>
<p>Choose whether to keep the current theme setting, remove the theme setting,
or install a new theme from the source below.</p>
<p>Installing a theme copies or extracts it into the configured GRUB theme
directory below <tt>/boot</tt>, then saves the installed theme.txt path in the GRUB
defaults file.</p>

View File

@@ -0,0 +1,7 @@
<header>Theme source</header>
<p>Enter a local theme.txt file, a local directory containing theme.txt, a local
<tt>.tar.gz</tt>, <tt>.tar</tt>, or <tt>.zip</tt> theme archive, or an HTTP,
HTTPS, or FTP URL to one of those files.</p>
<p>Archives are validated before extraction and installed under the configured
GRUB theme directory in <tt>/boot</tt> so the boot loader can read the theme
before the root filesystem is mounted.</p>

4
grub2/help/timeout.html Normal file
View File

@@ -0,0 +1,4 @@
<header>Timeout</header>
<p>Number of seconds GRUB waits before booting the default entry. Use 0 for
immediate boot or -1 to wait indefinitely when supported by the local GRUB
version.</p>

View File

@@ -0,0 +1,4 @@
<header>Timeout style</header>
<p>Controls how GRUB behaves while waiting for the timeout. Menu shows the boot
menu, hidden suppresses it unless interrupted, and countdown displays a compact
countdown when supported.</p>

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
<rect x="9" y="6" width="30" height="36" rx="3" fill="#eef4ff" stroke="#2563eb" stroke-width="2"/>
<path d="M16 16h16M16 24h16M16 32h10" stroke="#1e3a8a" stroke-width="3" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 291 B

5
grub2/images/install.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
<rect x="8" y="28" width="32" height="11" rx="3" fill="#ecfdf5" stroke="#059669" stroke-width="2"/>
<path d="M24 8v18M16 18l8 8 8-8" fill="none" stroke="#047857" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="34" cy="34" r="2" fill="#059669"/>
</svg>

After

Width:  |  Height:  |  Size: 372 B

5
grub2/images/manual.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
<rect x="7" y="10" width="34" height="26" rx="3" fill="#f8fafc" stroke="#475569" stroke-width="2"/>
<path d="M12 17h6M12 23h10M12 29h7" stroke="#334155" stroke-width="3" stroke-linecap="round"/>
<path d="M29 14l6 5-6 5M34 29h7" fill="none" stroke="#0f766e" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 420 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
<rect x="12" y="22" width="24" height="18" rx="3" fill="#fff7ed" stroke="#ea580c" stroke-width="2"/>
<path d="M17 22v-6a7 7 0 0114 0v6" fill="none" stroke="#9a3412" stroke-width="3" stroke-linecap="round"/>
<circle cx="24" cy="31" r="3" fill="#ea580c"/>
</svg>

After

Width:  |  Height:  |  Size: 351 B

5
grub2/images/theme.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
<rect x="7" y="9" width="34" height="30" rx="4" fill="#f0fdf4" stroke="#16a34a" stroke-width="2"/>
<circle cx="17" cy="20" r="4" fill="#facc15"/>
<path d="M10 35l10-10 7 6 5-5 9 9" fill="none" stroke="#15803d" stroke-width="3" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 350 B

405
grub2/index.cgi Executable file
View File

@@ -0,0 +1,405 @@
#!/usr/local/bin/perl
# Display GRUB 2 boot menu and configuration status.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%in, %text);
our $module_name;
our $grub2_formno = 0;
&ReadParse();
&error_setup($text{'acl_ecannot'});
my %access = &grub2_effective_acl();
&error("$text{'eacl_np'} $text{'eacl_pview'}")
if (!&grub2_can_enter_module(\%access));
# Show configuration/install guidance before rendering module actions.
if (!&grub2_any_installed()) {
&ui_print_header(&grub2_version_text() || "", $text{'index_title'},
"", undef, 1, 1);
print &ui_alert($text{'index_missing'}, 'warning');
foreach my $issue (&grub2_install_issues()) {
print &ui_div(&text('index_missing_detail',
&ui_tag('tt', &html_escape($issue))));
}
print &ui_p(&ui_link("@{[&get_webprefix()]}/config.cgi?$module_name",
$text{'index_config_link'}));
if ($access{'install'} && &foreign_available("software")) {
# Offer package installation only to users allowed to install GRUB.
&foreign_require("software", "software-lib.pl");
my $lnk = &software::missing_install_link(
"grub2-common", $text{'index_install_pkg'},
"../$module_name/", $text{'index_title'});
print &ui_p($lnk) if ($lnk);
}
&ui_print_footer("/", $text{'index_return'});
exit;
}
&ui_print_header(&grub2_version_text() || "", $text{'index_title'}, "",
undef, 1, 1, undef, &grub2_action_links(\%access));
foreach my $warning (&grub2_status_warnings()) {
print &ui_alert($warning, 'warning');
}
# Only the two entry lists are tabs; global settings live in separate pages.
my @tabs = (
[ 'entries', $text{'index_entries_tab'} ],
[ 'custom', $text{'index_custom_tab'} ],
);
my %valid = map { $_->[0] => 1 } @tabs;
my $requested = defined($in{'mode'}) ? $in{'mode'} : '';
my $mode = $requested && $valid{$requested} ? $requested : 'entries';
print &ui_tabs_start(\@tabs, "mode", $mode, 1);
print &ui_tabs_start_tab("mode", "entries");
&print_entries_tab(\%access);
print &ui_tabs_end_tab("mode", "entries");
print &ui_tabs_start_tab("mode", "custom");
&print_custom_tab(\%access);
print &ui_tabs_end_tab("mode", "custom");
print &ui_tabs_end();
&print_action_buttons(\%access);
&ui_print_footer("/", $text{'index_return'});
# print_entries_tab(&access)
# Outputs generated boot menu entries and selected-entry runtime actions.
sub print_entries_tab
{
my ($access) = @_;
my @entries = &grub2_boot_entries();
my $parsed = &read_grub_defaults();
my %env = &grub2_read_env();
# Selection roles are derived from both defaults and grubenv state.
my %selection = &grub2_entry_selection_roles(\@entries, $parsed, \%env);
my $can_default = $access->{'runtime'} && &grub2_command('set_default_cmd');
my $can_once = $access->{'runtime'} && &grub2_command('reboot_once_cmd');
my $show_actions = $can_default || $can_once;
print &ui_div($text{'index_entries_desc'});
if (!@entries) {
print &ui_alert($text{'index_no_entries'}, 'info');
return;
}
my @heads = (
$text{'index_col_title'},
$text{'index_col_group'},
$text{'index_col_selection'},
($show_actions ? ( $text{'index_col_actions'} ) : ( )),
);
my @tds = (
"",
"",
"width=10% nowrap",
($show_actions ? ( "width=10% nowrap" ) : ( )),
);
print &ui_columns_start(\@heads, 100, 0, \@tds);
foreach my $entry (@entries) {
# Path displays submenu nesting; BLS top-level entries have no submenu path.
my @cols = (
&entry_title_cell($entry),
@{$entry->{'path'} || []}
? &html_escape(join(' > ', @{$entry->{'path'}}))
: $text{'index_top'},
&selection_cell($selection{$entry->{'index'}}),
($show_actions ? ( &entry_actions_cell(
$entry, $can_default, $can_once) ) : ( )),
);
print &ui_columns_row(\@cols, \@tds);
}
print &ui_columns_end();
}
# print_custom_tab(&access)
# Outputs editable custom menu entries from the configured custom file.
sub print_custom_tab
{
my ($access) = @_;
my $file = &grub2_config_value('custom_file') || '';
my @entries = &grub2_custom_entries($file);
my $can_edit = $access->{'manual'} && $file ne '';
print &ui_div($text{'index_custom_desc'});
if ($file eq '') {
# A blank custom file path means the module cannot safely offer editing.
print &ui_alert($text{'custom_enofile'}, 'info');
return;
}
if ($can_edit && @entries) {
# Checked-table actions need a stable form number for select-all links.
my $formno = $grub2_formno;
print &ui_form_start("custom_action.cgi", "post", undef,
"id='grub2_custom_form'");
$grub2_formno++;
&print_custom_links($can_edit, scalar(@entries), $formno);
}
elsif (@entries) {
&print_custom_links($can_edit, scalar(@entries), $grub2_formno);
}
if (!@entries) {
print &ui_br();
print &ui_p($text{'custom_empty'});
if ($can_edit) {
# Empty state uses a compact link, matching other Webmin list pages.
print &ui_link("edit_custom.cgi", $text{'custom_add'},
"plus");
print &ui_br();
}
return;
}
# A single editable entry can be deleted, but cannot be reordered.
my $show_order = $can_edit && @entries > 1;
my @tds = $can_edit ? (
"width=5",
"",
"",
($show_order ? ( "width=40 style='white-space: nowrap; text-align: center'" ) : ( )),
) : ( );
print &ui_columns_start([
($can_edit ? ( "" ) : ( )),
$text{'index_col_title'},
$text{'index_col_group'},
($show_order ? ( $text{'index_col_order'} ) : ( )),
], 100, 0, \@tds);
foreach my $entry (@entries) {
# Custom indexes refer to parsed menuentry blocks in the custom file.
my $idx = $entry->{'custom_index'};
my $title = &entry_title_cell($entry, "edit_custom.cgi?idx=$idx");
my @cols = (
$title,
@{$entry->{'path'} || []}
? &html_escape(join(' > ', @{$entry->{'path'}}))
: $text{'index_top'},
($show_order ? ( &custom_order_cell($idx, \@entries) ) : ( )),
);
if ($can_edit) {
print &ui_checked_columns_row(\@cols, \@tds, "d", $idx);
}
else {
print &ui_columns_row(\@cols);
}
}
print &ui_columns_end();
if ($can_edit) {
my @left_buttons;
my @right_buttons = (
[ "delete", $text{'index_delete_entry'}, undef, undef,
"form='grub2_custom_form'" ],
);
print &ui_form_end_side_by_side("grub2_custom_form",
\@left_buttons, \@right_buttons);
}
}
# print_action_buttons(&access)
# Outputs the main module actions allowed by ACLs.
sub print_action_buttons
{
my ($access) = @_;
my (@links, @titles, @icons);
my $can_status = $access->{'view'};
my $can_generate = $access->{'apply'} && &grub2_command('mkconfig_cmd');
if ($access->{'install'}) {
# Primary action tiles are ACL-filtered so unavailable pages stay hidden.
push(@links, "edit_install.cgi");
push(@titles, $text{'index_install'});
push(@icons, "images/install.svg");
}
if ($access->{'edit'}) {
push(@links, "edit_defaults.cgi");
push(@titles, $text{'index_edit_defaults'});
push(@icons, "images/defaults.svg");
}
if ($access->{'security'}) {
push(@links, "edit_security.cgi");
push(@titles, $text{'index_edit_security'});
push(@icons, "images/security.svg");
}
if ($access->{'edit'}) {
push(@links, "edit_theme.cgi");
push(@titles, $text{'index_edit_theme'});
push(@icons, "images/theme.svg");
}
if ($access->{'manual'}) {
push(@links, "edit_manual.cgi");
push(@titles, $text{'index_manual'});
push(@icons, "images/manual.svg");
}
return if (!@links && !$can_status && !$can_generate);
print &ui_hr();
if (@links) {
print &ui_subheading($text{'index_global'});
&icons_table(\@links, \@titles, \@icons, scalar(@links) > 5 ? 5 :
scalar(@links));
}
if ($can_status || $can_generate) {
print &ui_hr() if (@links);
print &ui_buttons_start();
print &ui_buttons_row("status.cgi", $text{'index_view_status'},
$text{'index_view_status_msg'}, undef, undef,
undef, "get") if ($can_status);
print &ui_buttons_row("generate.cgi", $text{'index_generate'},
$text{'index_generate_msg'},
[ [ "redir", &grub2_this_url() ] ])
if ($can_generate);
print &ui_buttons_end();
}
}
# print_custom_links(can-edit?, entry-count, form-number)
# Outputs checked-table links for custom entries.
sub print_custom_links
{
my ($can_edit, $count, $formno) = @_;
return if (!$can_edit);
my @left;
if ($count) {
push(@left, &select_all_link("d", $formno),
&select_invert_link("d", $formno));
}
push(@left, &ui_link("edit_custom.cgi", $text{'custom_add'}));
print &ui_links_row(\@left);
}
# selection_cell(&roles)
# Returns display text for default and next-boot entry roles.
sub selection_cell
{
my ($roles) = @_;
return '' if (!$roles || !@$roles);
my @labels = map { $text{'index_selection_'.$_} || $_ } @$roles;
return join(', ', @labels);
}
# entry_title_cell(&entry, [link])
# Returns a title cell with useful GRUB entry metadata in inline details.
sub entry_title_cell
{
my ($entry, $link) = @_;
my $title = &html_escape($entry->{'title'} || '');
my $summary = $link ?
&ui_tag('a', $title, { href => $link, style => 'padding: 0;' }) :
$title;
return &ui_details({
'html' => 1,
'title' => $summary,
'content' => &entry_details_content($entry),
'class' => 'inline inlined',
});
}
# entry_details_content(&entry)
# Returns compact metadata for a boot entry details disclosure.
sub entry_details_content
{
my ($entry) = @_;
my @rows;
my $index = defined($entry->{'index'}) ? $entry->{'index'} :
$entry->{'custom_index'};
# Only include rows that help identify or troubleshoot the selected entry.
push(@rows, &entry_detail_line($text{'index_col_index'}, $index))
if (defined($index));
push(@rows, &entry_detail_line($text{'index_col_id'}, $entry->{'id'}))
if ($entry->{'id'});
push(@rows, &entry_source_detail_line($entry))
if ($entry->{'source_file'});
push(@rows, &entry_detail_line($text{'index_col_version'},
$entry->{'version'}));
push(@rows, &entry_detail_line($text{'index_col_kernel'},
$entry->{'linux'}));
push(@rows, &entry_detail_line($text{'index_col_initrd'},
$entry->{'initrd'}));
push(@rows, &entry_detail_line($text{'index_col_machine_id'},
$entry->{'machine-id'}));
push(@rows, &entry_detail_line($text{'index_col_options'},
$entry->{'options'}));
return join('', @rows);
}
# entry_source_detail_line(&entry)
# Returns source details without implying generator scripts are entry files.
sub entry_source_detail_line
{
my ($entry) = @_;
my $file = $entry->{'source_file'} || '';
return '' if (!defined($file) || $file eq '');
my $custom_file = &grub2_config_value('custom_file') || '';
my $direct_file = (($entry->{'source'} || '') eq 'bls') ||
($custom_file ne '' && $file eq $custom_file);
# Direct entry files are shortened for readability; generator scripts are not.
my $label = $direct_file ? $text{'index_col_file'} :
$text{'index_col_generator'};
my $html;
if ($direct_file) {
my $display = &entry_file_display_name($file);
$html = &ui_tag('tt', &html_escape($display), { 'title' => $file });
}
else {
$html = &ui_tag('tt', &html_escape($file));
}
if (&grub2_check_acl('manual') && &grub2_manual_file($file)) {
# The manual editor repeats its allowlist check on entry.
$html = &ui_tag('a', $html, {
'href' => "edit_manual.cgi?file=".&urlize($file),
});
}
return &entry_detail_line($label, $html, 1);
}
# entry_file_display_name(file)
# Returns a short display name for a linked entry file.
sub entry_file_display_name
{
my ($file) = @_;
$file = '' if (!defined($file));
$file =~ s{.*/}{};
return $file;
}
# entry_detail_line(label, value, [html-value?])
# Returns one escaped metadata line for a boot entry details disclosure.
sub entry_detail_line
{
my ($label, $value, $html) = @_;
return '' if (!defined($value) || $value eq '');
my $display = $html ? $value : &ui_tag('tt', &html_escape($value));
return &ui_tag('div',
&ui_tag('span', &html_escape($label).':',
{ 'style' => 'white-space: nowrap;' }).
&ui_tag('span', $display,
{ 'style' => 'min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere;' }),
{ 'style' => 'display: grid; grid-template-columns: max-content minmax(0, 1fr); column-gap: 0.35em; align-items: start;' });
}
# entry_actions_cell(&entry, can-default?, can-once?)
# Returns runtime action links for one generated boot entry.
sub entry_actions_cell
{
my ($entry, $can_default, $can_once) = @_;
my $idx = $entry->{'index'};
my @actions;
push(@actions, &ui_link("set_default.cgi?idx=$idx",
$text{'index_set_default'})) if ($can_default);
push(@actions, &ui_link("reboot_once.cgi?idx=$idx",
$text{'index_reboot_once'})) if ($can_once);
return join(' | ', @actions);
}
# custom_order_cell(index, &entries)
# Returns up/down ordering controls for one custom entry row.
sub custom_order_cell
{
my ($idx, $entries) = @_;
my $up = $idx > 0 &&
&grub2_paths_equal($entries->[$idx], $entries->[$idx - 1]);
my $down = $idx < @$entries - 1 &&
&grub2_paths_equal($entries->[$idx], $entries->[$idx + 1]);
# Disable movement across submenu boundaries so nesting remains intact.
return &ui_up_down_arrows("custom_action.cgi?idx=$idx&dir=up",
"custom_action.cgi?idx=$idx&dir=down",
$up, $down);
}

146
grub2/install.cgi Executable file
View File

@@ -0,0 +1,146 @@
#!/usr/local/bin/perl
# Install the GRUB 2 boot loader with progress output.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%in, %text);
&ReadParse();
&error_setup($text{'install_err'});
&grub2_assert_acl('install');
# Trim text fields before validation so shell arguments are deterministic.
foreach my $field (qw(target efi_dir platform directory boot_directory
bootloader_id)) {
$in{$field} = "" if (!defined($in{$field}));
$in{$field} =~ s/^\s+|\s+\z//g;
}
&error($text{'install_eboot_directory_required'})
if ($in{'use_boot_directory'} && $in{'boot_directory'} eq '');
my %opts = (
'target' => $in{'target'},
'efi_dir' => $in{'efi_dir'},
'platform' => $in{'platform'} || &grub2_default_platform_target(),
'directory' => $in{'directory'},
'boot_directory' => $in{'use_boot_directory'} ? $in{'boot_directory'} : '',
'bootloader_id' => $in{'bootloader_id'},
'recheck' => $in{'recheck'} ? 1 : 0,
'removable' => $in{'removable'} ? 1 : 0,
'no_nvram' => $in{'no_nvram'} ? 1 : 0,
'force' => $in{'force'} ? 1 : 0,
);
$in{'confirm'} || &error($text{'install_econfirm'});
&grub2_command('install_cmd') || &error($text{'install_ecmd'});
# Validate all paths and identifiers before any progress output is started.
my $precheck = &grub2_validate_install_options(\%opts);
&error($precheck) if ($precheck);
my ($pre_open, $current_step, $failed_printed, $captured_output) =
(0, '', 0, '');
&ui_print_unbuffered_header(undef, $text{'install_progress_title'}, "");
my $callback = sub {
my ($event, $value) = @_;
if ($event eq 'command') {
# The first event opens the visible command transcript.
$current_step = 'command';
&print_step_start($text{'install_installing'});
print &ui_tag_start('pre', { 'style' => 'margin-left: 10px;' });
$pre_open = 1;
print &html_escape($value)."\n";
return;
}
if ($event eq 'output') {
# Some commands emit output before command_done; keep one pre open.
if (!$pre_open) {
print &ui_tag_start('pre',
{ 'style' => 'margin-left: 10px;' });
$pre_open = 1;
}
$captured_output .= $value;
print &html_escape($value);
return;
}
if ($event eq 'command_done') {
# Close the transcript before printing the final status marker.
&close_output(\$pre_open);
&print_step_done(\$current_step);
return;
}
if ($event eq 'command_failed') {
# Print only one failure line even if the caller also returns an error.
&close_output(\$pre_open);
&print_step_failed(\$current_step, \$failed_printed);
return;
}
};
my $err = &grub2_install_bootloader(\%opts, $callback);
&close_output(\$pre_open);
if ($err) {
# Avoid duplicating the same error when it was already in command output.
&print_step_failed(\$current_step, \$failed_printed)
if ($current_step);
my $shown = $captured_output || '';
$shown =~ s/^\s+|\s+\z//g;
print &ui_tag('pre', &html_escape($err),
{ 'style' => 'margin-left: 10px;' })
if ($err ne $shown);
}
else {
&webmin_log("install", undef, &grub2_install_log_target(\%opts));
}
&ui_print_footer("index.cgi", $text{'install_return'});
# close_output(&open-flag)
# Closes the command output block when it is currently being printed.
sub close_output
{
my ($open) = @_;
if ($$open) {
print &ui_tag_end('pre');
$$open = 0;
}
return;
}
# print_step_start(text)
# Prints the first progress line for the installation step.
sub print_step_start
{
my ($msg) = @_;
print &ui_tag('span', &html_escape($msg." .."),
{ 'data-first-print' => undef });
print "<br>\n";
return;
}
# print_step_done(&current-step)
# Prints a successful progress line and clears the active step.
sub print_step_done
{
my ($current) = @_;
print &ui_tag('span', &html_escape(".. ".$text{'install_done'}),
{ 'data-second-print' => undef });
print "<br><div data-x-br=\"\"></div>\n";
$$current = '';
return;
}
# print_step_failed(&current-step, &printed-flag)
# Prints a failed progress line once and clears the active step.
sub print_step_failed
{
my ($current, $printed) = @_;
return if ($$printed);
print &ui_tag('span', &html_escape(".. ".$text{'install_failed_status'}),
{ 'data-second-print' => undef });
print "<br><div data-x-br=\"\"></div>\n";
$$current = '';
$$printed = 1;
return;
}

18
grub2/install_check.pl Normal file
View File

@@ -0,0 +1,18 @@
# install_check.pl
use strict;
use warnings;
do 'grub2-lib.pl';
# is_installed(mode)
# For mode 1, returns 2 if GRUB 2 is installed and configured for Webmin,
# 1 if installed but not configured, or 0 otherwise.
# For mode 0, returns 1 if installed, 0 if not.
sub is_installed
{
my ($mode) = @_;
return 0 if (!&grub2_any_installed());
return $mode ? (&grub2_configured() ? 2 : 1) : 1;
}
1;

349
grub2/lang/en Normal file
View File

@@ -0,0 +1,349 @@
index_title=GRUB 2 Boot Loader
index_version=GRUB version $1
index_return=GRUB 2 module
status_title=GRUB status
index_missing=GRUB 2 does not appear to be installed or configured. Install GRUB 2 packages or update this module's configuration paths.
index_missing_detail=Missing or unusable item: $1
index_config_link=Module configuration
index_install_pkg=GRUB 2 packages
index_summary=Configuration summary
index_boot_mode=Boot mode
index_boot_mode_uefi=UEFI
index_boot_mode_bios=Legacy BIOS
index_secure_boot=Secure Boot
index_secure_boot_enabled=Enabled
index_secure_boot_disabled=Disabled
index_secure_boot_unknown=Unknown
index_secure_boot_not_applicable=Not applicable for BIOS boot
index_default_file=Default settings file
index_grub_cfg=Generated menu file
index_grub_dir=Script directory
index_bls_dir=BLS entries directory
index_mkconfig=Menu generation command
index_install_cmd=Boot loader install command
index_env=Environment file
index_entries=Boot menu entries
index_entry_count=$1 entries
index_kernel_options_source=Kernel options source
index_kernel_options_source_kernelopts=GRUB environment kernelopts
index_kernel_options_source_bls=BLS entry files
index_kernel_options_source_defaults=Generated menu entries
index_no_entries=No boot entries were found in the generated GRUB menu file.
index_entries_tab=Generated menu
index_custom_tab=Custom entries
index_entries_desc=Review entries from the generated GRUB menu and choose one entry for runtime actions; generated kernel, BLS, and distribution entries are not editable from this page.
index_custom_desc=Add, edit, reorder, or delete entries from the configured custom menu file. These entries appear in the generated menu after regeneration.
index_status_desc=Review discovered GRUB paths, common defaults, theme appearance, saved boot selection, and password protection managed by Webmin.
index_security_state=Current protection
index_security_enabled=Enabled
index_security_disabled=Disabled
index_security_unmanaged=Unmanaged file
index_security_user=Superuser
index_security_hash=Password hash
index_security_hash_set=Configured
index_security_hash_missing=Missing
index_security_file=Password script
index_security_mkpasswd=Password hash command
index_boot_selection=Boot selection
index_col_index=Index
index_col_selection=Selection
index_col_order=Order
index_col_title=Title
index_col_id=Entry ID
index_col_group=Submenu
index_col_actions=Actions
index_col_file=File
index_col_generator=Generator script
index_col_version=Version
index_col_kernel=Kernel
index_col_initrd=Initial ramdisk
index_col_machine_id=Machine ID
index_col_options=Kernel options
index_selection_default=Default
index_selection_saved=Saved default
index_selection_next=Boot once
index_top=Top level
index_bls=Boot Loader Specification
index_saved_entry=Environment default entry
index_next_entry=Next boot entry
index_not_set=Not set
index_not_readable=Not readable
index_missing_file=Missing file
index_delete_entry=Delete entry
index_global=Global Options
index_edit_defaults=Edit GRUB Defaults
index_edit_theme=Edit Theme and Appearance
index_edit_security=Edit GRUB Password
index_manual=Edit Config Files Manually
index_view_status=View GRUB status
index_view_status_msg=Review detected paths, defaults, boot selection, theme, and password protection.
index_generate=Regenerate GRUB menu
index_generate_msg=Run GRUB's menu generator and replace the generated menu only after a successful test run.
index_install=Install GRUB Boot Loader
index_set_default=Make Default
index_reboot_once=Boot Once
index_warn_missing_cfg=The generated menu file does not exist. Generate the menu before relying on these settings at boot.
index_warn_missing_default=The default settings file does not exist. Create it or update the module configuration.
index_warn_mkconfig=The menu generation command is not available, so Webmin cannot regenerate the boot menu.
index_warn_saved=The default is set to saved, but the GRUB environment file could not be read.
index_warn_theme_invalid=The configured GRUB theme cannot be used: $1
index_warn_theme_console=A GRUB theme is configured, but terminal output is set to console. Use graphical terminal output for the theme to appear.
index_warn_bls_options=Detected BLS boot entries store kernel options in BLS entry files. Install grubby or configure its path to update existing BLS entries automatically.
index_warn_kernelopts_source=Detected BLS boot entries read kernel options from grubenv kernelopts. Install grubby or configure its path if existing BLS entries do not change after saving kernel options.
index_warn_kernelopts_missing=BLS boot entries refer to grubenv kernelopts, but no kernelopts value was found in the GRUB environment file.
defaults_title=GRUB default settings
defaults_header=Common default settings
defaults_theme_header=Theme and menu appearance
defaults_default=Default menu entry
defaults_default_saved=Saved environment entry
defaults_default_current=Current value: $1
defaults_default_current_entry=Current index $1: $2
defaults_default_entry_id=$1 [$2]
defaults_timeout_style=Timeout style
defaults_timeout=Timeout
defaults_terminal_output=Terminal output
defaults_terminal_console=Console
defaults_terminal_gfxterm=Graphical terminal
defaults_terminal_gfxterm_console=Graphical terminal, then console
defaults_terminal_serial=Serial
defaults_cmdline_default=Kernel options for regular Linux entries
defaults_cmdline=Kernel options for all Linux entries
defaults_kernelopts_source=Detected kernel options source
defaults_disable_recovery=Disable recovery and rescue entries
defaults_disable_recovery_bls=Existing BLS rescue entries are hidden by renaming their entry files.
defaults_disable_os_prober=Disable OS prober
defaults_theme=Theme file
defaults_theme_current=Current theme file
defaults_theme_none=Not set
defaults_theme_action=Theme action
defaults_theme_keep=Keep current theme
defaults_theme_install=Install or select theme source
defaults_theme_clear=Use no theme
defaults_theme_source=Theme source
defaults_gfxmode=Graphics mode
defaults_gfxmode_default=Use default resolution
defaults_gfxmode_auto=Automatic resolution
defaults_background=Background image
defaults_color_normal=Normal menu colors
defaults_color_highlight=Selected menu colors
defaults_color_default=Use default colors
defaults_color_custom=Set custom colors
defaults_color_text=Text
defaults_color_background=Background
defaults_keep=Use GRUB default
defaults_menu=Menu
defaults_hidden=Hidden
defaults_countdown=Countdown
defaults_true=Yes
defaults_false=No
defaults_format_note=Saving these settings rewrites the managed assignments in normalized shell format while preserving other lines and comments.
defaults_err=Failed to save GRUB defaults
defaults_edefault=The default menu entry cannot contain line breaks or null bytes.
defaults_edefault_choice=The selected default menu entry is not one of the detected entries.
defaults_etimeout=The timeout must be -1 or a non-negative number of seconds.
defaults_etimeout_style=Invalid timeout style.
defaults_eterminal_output=Invalid terminal output.
defaults_egfxmode=Invalid graphics mode. Use auto or modes like 1024x768 or 1024x768x32, separated by commas.
defaults_ebool=Invalid boolean value for $1.
defaults_ecmdline=$1 cannot contain line breaks or null bytes.
defaults_egrubby=The grubby command is not available, so existing BLS entries were not updated.
defaults_egrubby_failed=The grubby command failed without output.
defaults_ebls_rescue_exists=Cannot disable BLS rescue entry $1 because $2 already exists.
defaults_ebls_rescue_move=Failed to move BLS rescue entry from $1 to $2: $3
defaults_edir=Directory $1 does not exist or is not accessible.
defaults_epath=$1 cannot contain line breaks or null bytes.
defaults_eabspath=$1 must be an absolute path.
defaults_econfigpath=Configured file path $1 must be absolute.
defaults_epathchars=$1 contains characters that are not allowed in GRUB file paths.
defaults_ecolor=Invalid color selection for $1.
defaults_ecolorfile=The configured GRUB color script already exists but is not managed by Webmin. Edit it manually or choose another path in module configuration.
defaults_etheme_archive=$1 is a theme archive, not a GRUB theme file. Extract the theme and select its theme.txt file.
defaults_etheme_file=The GRUB theme file $1 does not exist or cannot be read.
defaults_etheme_source=Enter a local theme file, local theme archive, local theme directory, or HTTP, HTTPS, or FTP URL.
defaults_etheme_url=Enter a valid HTTP, HTTPS, or FTP URL for the theme source.
defaults_etheme_download=Failed to download the theme source: $1
defaults_etheme_mode=Invalid theme action.
defaults_etheme_nottheme=$1 is not a theme.txt file or a supported theme archive.
defaults_etheme_notfound=No theme.txt file was found in the selected theme source.
defaults_etheme_archive_list=Failed to list the theme archive.
defaults_etheme_extract=Failed to extract the theme archive.
defaults_etheme_type=Unsupported theme archive type.
defaults_etheme_cmd=The command $1 is required to install this theme source.
defaults_etheme_member=The theme archive contains an unsafe or unsupported member: $1
defaults_etheme_terminal=GRUB themes require graphical terminal output. Change Terminal output to Graphical terminal before saving this theme.
defaults_ebackground_file=The GRUB background image $1 does not exist or cannot be read.
defaults_ebackground_type=The GRUB background image $1 must be a PNG, JPEG, or TGA file.
defaults_theme_note=Enter an existing theme.txt, a theme directory, a local .tar.gz or .zip archive, or an HTTP, HTTPS, or FTP URL. Webmin installs the theme under the configured GRUB theme directory in <tt>/boot</tt>. Use graphical terminal output for themes.
defaults_background_note=Webmin copies this image under the configured GRUB background directory in <tt>/boot</tt> before saving it.
theme_title=GRUB theme and appearance
theme_err=Failed to save GRUB theme and appearance
security_title=GRUB password protection
security_header=Password protection
security_current_state=Current protection
security_current_enabled=Enabled for superuser $1
security_current_disabled=Disabled
security_current_hash=Current password
security_current_hash_set=Set, not shown
security_current_hash_missing=Not set
security_enable=Enable password protection
security_user=Superuser name
security_password_status=Password update
security_password_keep=Leave blank to keep the current password, or enter a new one to replace it.
security_password_required=No password is currently set. Enter and confirm a password before enabling protection.
security_newpass=New password
security_newpass2=Confirm password
security_hash=PBKDF2 hash
security_hash_note=This is the stored GRUB password hash, not the original password. Leave it unchanged to keep the current password, or replace it with a pre-generated grub.pbkdf2 hash.
security_unmanaged=The configured password script already exists but is not managed by Webmin. Edit it manually or choose another path in module configuration.
security_err=Failed to save GRUB password protection
security_euser=The GRUB superuser name is required and can only contain letters, numbers, dots, underscores, plus signs, at signs, and hyphens.
security_epass=Enter a password or PBKDF2 hash before enabling password protection.
security_epassmatch=The password and confirmation do not match.
security_epasschars=The password cannot contain line breaks or null bytes.
security_epassmode=Enter either a password or a PBKDF2 hash, not both.
security_ehash=The PBKDF2 hash is invalid.
security_ehashgen=The password hash command did not return a GRUB PBKDF2 hash.
security_emkpasswd=The GRUB password hash command is not available. Install GRUB tools or paste a PBKDF2 hash.
security_eunmanaged=The configured GRUB password script is not managed by Webmin and will not be overwritten.
color_black=Black
color_blue=Blue
color_green=Green
color_cyan=Cyan
color_red=Red
color_magenta=Magenta
color_brown=Brown
color_light-gray=Light gray
color_dark-gray=Dark gray
color_light-blue=Light blue
color_light-green=Light green
color_light-cyan=Light cyan
color_light-red=Light red
color_light-magenta=Light magenta
color_yellow=Yellow
color_white=White
manual_title=Manual GRUB configuration
manual_select=Edit config file:
manual_ok=Edit
manual_err=Failed to save GRUB file
manual_efile=The selected file is not allowed for manual editing.
manual_enofile=No GRUB files are configured for manual editing.
manual_evalidate=The file failed validation: $1
manual_ebls=The BLS entry must contain key/value lines and may contain comments or blank lines.
manual_eblsline=Line $1 is not valid BLS key/value syntax.
custom_title_new=Add Custom GRUB Entry
custom_title_edit=Edit Custom GRUB Entry
custom_header=Custom boot entry
custom_add=Add new custom boot entry
custom_entry_title=Menu title
custom_entry_id=Entry ID
custom_entry_body=GRUB commands
custom_id_note=Optional. Use letters, numbers, dots, underscores, colons, plus signs, equals signs, commas, at signs, and hyphens only.
custom_empty=No custom boot entries were found.
custom_err=Failed to save custom GRUB entry
custom_efile=No custom GRUB menu file is configured.
custom_enofile=No custom GRUB menu file is configured.
custom_eentry=Invalid custom boot entry.
custom_eone=Select exactly one custom boot entry.
custom_etitle=The menu title is required and cannot contain line breaks or null bytes.
custom_eid=The entry ID contains invalid characters.
custom_ebody=The GRUB commands contain invalid data.
custom_evalidate=The GRUB script failed validation.
custom_ebraces=The GRUB commands have unbalanced braces.
custom_emove=The selected custom entry cannot be moved in that direction.
generate_title=Regenerating GRUB menu
generate_err=Failed to generate the GRUB menu
generate_missing=The GRUB menu generation command is not available.
generate_empty=The generated test menu was empty.
generate_failed=The GRUB menu generation command failed without output.
generate_evalidate=The generated test menu failed validation: $1
generate_regenerating=Regenerating GRUB menu
generate_check=Checking generated test menu
generate_replace=Replacing generated menu
generate_done=done
generate_failed_status=failed
generate_return=GRUB 2 module
install_title=Install GRUB boot loader
install_progress_title=Installing GRUB boot loader
install_header=Boot loader installation
install_command=Install command
install_target=Install target
install_efi_dir=EFI system directory
install_platform=Platform target
install_directory=GRUB module directory
install_boot_directory=Boot directory
install_bootloader_id=Boot loader ID
install_options=Options
install_boot_directory_enable=Pass boot directory to grub-install
install_recheck=Probe devices again before installing
install_removable=Install as removable EFI media
install_no_nvram=Do not update EFI firmware boot variables
install_force=Force EFI install even though it may not support Secure Boot
install_confirm=Confirmation
install_confirm_label=I understand that installing GRUB to the wrong target can make this system unbootable.
install_submit=Install GRUB
install_installing=Installing GRUB boot loader
install_done=done
install_failed_status=failed
install_return=GRUB 2 module
install_warn_modules=GRUB platform files for $1 were not found. Install the matching GRUB EFI/module package before installing the boot loader, or enter the module directory manually.
install_err=Failed to install the GRUB boot loader
install_ecmd=The GRUB boot loader install command is not available.
install_etarget=Enter an absolute device path under /dev such as /dev/sda, or an EFI system directory.
install_etarget_missing=Install target $1 does not exist.
install_eefi=The EFI system directory must be an absolute path with safe characters.
install_eefi_missing=EFI system directory $1 does not exist.
install_eplatform=The platform target can only contain letters, numbers, underscores, and hyphens.
install_eplatform_modules=GRUB platform files for $1 were not found. Install the matching GRUB EFI/module package or enter the module directory manually. Checked: $2
install_edirectory=The GRUB module directory must be an absolute path with safe characters.
install_edirectory_missing=GRUB module directory $1 does not exist.
install_edirectory_modinfo=GRUB module directory $1 does not contain modinfo.sh.
install_eboot_directory=The boot directory must be an absolute path with safe characters.
install_eboot_directory_missing=Boot directory $1 does not exist.
install_eboot_directory_required=Enter a boot directory, or clear the boot directory option.
install_ebootloader=The boot loader ID can only contain letters, numbers, dots, underscores, plus signs, and hyphens.
install_eoption=Invalid install option.
install_econfirm=Confirm that you understand the risk before installing GRUB.
install_failed=The GRUB boot loader install command failed without output.
install_log_efi=EFI directory $1
runtime_err=Failed to update GRUB boot selection
runtime_eentry=Invalid boot entry.
runtime_ecmd=The required GRUB command is not available.
runtime_eselector=Unsafe or invalid GRUB boot entry selector.
runtime_eaction=No GRUB boot action was selected.
delete_enone=Select one or more boot menu entries to delete.
delete_ecustom=Only custom menu entries from the configured custom file can be deleted here.
acl_section_view=View permissions
acl_section_change=Change permissions
acl_section_admin=Administrative permissions
acl_view=View GRUB configuration and boot entries
acl_edit=Edit common GRUB defaults
acl_security=Edit GRUB password protection
acl_apply=Generate the GRUB menu
acl_runtime=Set saved or one-time boot entries
acl_manual=Manually edit allowed GRUB files
acl_install=Install GRUB boot loader
acl_backup=Include GRUB files in backups
acl_ecannot=You are not allowed to perform this GRUB 2 action.
eacl_np=Access denied:
eacl_pview=view GRUB configuration
eacl_pedit=edit GRUB defaults
eacl_psecurity=edit GRUB password protection
eacl_papply=generate the GRUB menu
eacl_pruntime=change GRUB boot selection
eacl_pmanual=manually edit GRUB files
eacl_pinstall=install GRUB boot loader
eacl_pbackup=backup GRUB files
log_defaults=Modified GRUB default settings
log_bls_args=Updated existing BLS kernel options
log_theme=Modified GRUB theme and appearance
log_security=Modified GRUB password protection
log_manual=Modified GRUB file $1
log_custom_create=Created custom GRUB entry $1
log_custom_modify=Modified custom GRUB entry $1
log_custom_move=Moved custom GRUB entry $1
log_generate=Generated GRUB menu $1
log_install=Installed GRUB boot loader to $1
log_default=Set saved GRUB default to $1
log_once=Set next GRUB boot to $1
log_custom_delete=Deleted $1 custom GRUB menu entries
__norefs=1

68
grub2/log_parser.pl Normal file
View File

@@ -0,0 +1,68 @@
# log_parser.pl
# Functions for parsing this module's logs.
use strict;
use warnings;
do 'grub2-lib.pl';
our %text;
# parse_webmin_log(user, script, action, type, object, &params, [long])
# Converts logged information from this module into human-readable form.
sub parse_webmin_log
{
my ($user, $script, $action, $type, $object, $p, $long) = @_;
# Simple module-wide actions do not need an object value.
if ($action eq 'defaults') {
return $text{'log_defaults'};
}
if ($action eq 'bls_args') {
return $text{'log_bls_args'};
}
if ($action eq 'theme') {
return $text{'log_theme'};
}
if ($action eq 'security') {
return $text{'log_security'};
}
# Object-bearing actions display paths, selectors, or titles as literals.
if ($action eq 'manual') {
return &text('log_manual', &log_value($object));
}
if ($action eq 'custom_create') {
return &text('log_custom_create', &log_value($object));
}
if ($action eq 'custom_modify') {
return &text('log_custom_modify', &log_value($object));
}
if ($action eq 'custom_move') {
return &text('log_custom_move', &log_value($object));
}
if ($action eq 'generate') {
return &text('log_generate', &log_value($object));
}
if ($action eq 'install') {
return &text('log_install', &log_value($object));
}
if ($action eq 'default') {
return &text('log_default', &log_value($object));
}
if ($action eq 'once') {
return &text('log_once', &log_value($object));
}
if ($action eq 'custom_delete') {
return &text('log_custom_delete', $object || 0);
}
return;
}
# log_value(value)
# Returns a styled inline value for log descriptions.
sub log_value
{
my ($value) = @_;
$value = '' if (!defined($value));
return &ui_tag('tt', &html_escape($value));
}
1;

7
grub2/module.info Normal file
View File

@@ -0,0 +1,7 @@
name=GRUB 2
desc=GRUB 2 Boot Loader
category=hardware
os_support=*-linux
longdesc=Configure GRUB 2 boot loader defaults, inspect generated menu entries, and update boot loader configuration safely.
rpm_recommends=grub2-tools
deb_recommends=grub2-common

22
grub2/reboot_once.cgi Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/local/bin/perl
# Set the one-time next GRUB 2 boot entry.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%in, %text);
&ReadParse();
&error_setup($text{'runtime_err'});
&grub2_assert_acl('runtime');
# Runtime commands operate on the parsed generated menu index shown on index.cgi.
my $entry = &grub2_entry_by_index($in{'idx'});
&error($text{'runtime_eentry'}) if (!$entry);
# The helper validates the selector before invoking grub-reboot.
my $err = &grub2_run_entry_command('reboot_once_cmd', $entry);
&error($err) if ($err);
my $selector = &grub2_entry_selector($entry);
&webmin_log("once", undef, $selector);
&redirect("");

31
grub2/save_custom.cgi Executable file
View File

@@ -0,0 +1,31 @@
#!/usr/local/bin/perl
# Save a custom GRUB 2 menu entry.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%in, %text);
&ReadParse();
&error_setup($text{'custom_err'});
&grub2_assert_acl('manual');
# A missing index means create; a present one must address a parsed entry.
my $idx = defined($in{'idx'}) && $in{'idx'} ne '' ? $in{'idx'} : undef;
if (defined($idx) && $idx !~ /^\d+\z/) {
&error($text{'custom_eentry'});
}
# Normalize absent fields before validation so empty strings mean intentional.
foreach my $field (qw(title id body)) {
$in{$field} = "" if (!defined($in{$field}));
}
$in{'body'} =~ s/\r//g;
# The library validates GRUB script balance before rewriting the custom file.
my $err = &grub2_save_custom_entry($idx, $in{'title'}, $in{'id'},
$in{'body'});
&error($err) if ($err);
&grub2_mark_regenerate_needed();
&webmin_log(defined($idx) ? "custom_modify" : "custom_create",
undef, $in{'title'});
&redirect("index.cgi?mode=custom");

215
grub2/save_defaults.cgi Executable file
View File

@@ -0,0 +1,215 @@
#!/usr/local/bin/perl
# Save common GRUB 2 defaults.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%in, %text);
&ReadParse();
&error_setup($text{'defaults_err'});
&grub2_assert_acl('edit');
# Capture both the current defaults file and generated entries for validation.
my $current = &read_grub_defaults();
my $current_values = $current->{'values'};
my @entries = &grub2_boot_entries();
my %updates;
# Default entries must come from the selector, except for the preserved value.
$in{'default'} = '' if (!defined($in{'default'}));
$in{'default'} =~ /[\r\n\0]/ && &error($text{'defaults_edefault'});
&error($text{'defaults_edefault_choice'})
if (!&valid_default_entry_value(
$in{'default'}, $current_values->{'GRUB_DEFAULT'}, \@entries));
$updates{'GRUB_DEFAULT'} = $in{'default'} if ($in{'default'} ne '');
my %styles = map { $_ => 1 } ('', qw(menu hidden countdown));
&error($text{'defaults_etimeout_style'})
if (!defined($in{'timeout_style'}) || !$styles{$in{'timeout_style'}});
$updates{'GRUB_TIMEOUT_STYLE'} =
$in{'timeout_style'} eq '' ? undef : $in{'timeout_style'};
# Empty timeout removes the local override; otherwise GRUB accepts -1 or more.
if (defined($in{'timeout'}) && $in{'timeout'} ne '') {
$in{'timeout'} =~ /^-?\d+\z/ && $in{'timeout'} >= -1
|| &error($text{'defaults_etimeout'});
$updates{'GRUB_TIMEOUT'} = $in{'timeout'};
}
else {
$updates{'GRUB_TIMEOUT'} = undef;
}
# Kernel command-line values are single-line shell assignment values.
foreach my $field (
[ 'cmdline_default', 'GRUB_CMDLINE_LINUX_DEFAULT',
$text{'defaults_cmdline_default'} ],
[ 'cmdline', 'GRUB_CMDLINE_LINUX', $text{'defaults_cmdline'} ],
)
{
my ($input, $key, $label) = @$field;
$in{$input} = '' if (!defined($in{$input}));
$in{$input} =~ /[\r\n\0]/ && &error(&text('defaults_ecmdline', $label));
$updates{$key} = $in{$input} eq '' ? undef : $in{$input};
}
# Boolean GRUB defaults keep their tri-state UI: inherit, true, or false.
foreach my $field (
[ 'disable_recovery', 'GRUB_DISABLE_RECOVERY',
$text{'defaults_disable_recovery'} ],
[ 'disable_os_prober', 'GRUB_DISABLE_OS_PROBER',
$text{'defaults_disable_os_prober'} ],
)
{
my ($input, $key, $label) = @$field;
$in{$input} = '' if (!defined($in{$input}));
if ($in{$input} eq '') {
$updates{$key} = undef;
}
elsif ($in{$input} =~ /^(true|false)\z/) {
$updates{$key} = $in{$input};
}
else {
&error(&text('defaults_ebool', $label));
}
}
my $err = &save_grub_defaults_values(\%updates);
&error(&text('manual_evalidate', $err)) if ($err);
# BLS rescue entries are real files, so restore them before changing BLS args.
my $disable_bls_rescue =
(($updates{'GRUB_DISABLE_RECOVERY'} || '') eq 'true') ? 1 : 0;
my $can_handle_bls_rescue =
&grub2_has_bls_rescue_entries(\@entries) ||
&grub2_disabled_bls_rescue_files();
my $bls_rescue_err;
if (!$disable_bls_rescue) {
$bls_rescue_err = &grub2_set_bls_rescue_disabled(0);
# Refresh entry data after restores so grubby sees current BLS files.
@entries = &grub2_boot_entries() if (!$bls_rescue_err);
}
# On BLS systems, grubby applies kernel arg deltas to existing boot entries.
my %bls_args_updated;
my $bls_err;
if (&grub2_bls_update_available(\@entries)) {
my @locked_bls_files = &lock_bls_update_files(
$current_values, \%updates, \@entries);
($bls_err, %bls_args_updated) = &update_bls_kernel_args(
$current_values, \%updates, \@entries);
&unlock_bls_update_files(@locked_bls_files);
}
if (!$bls_err && !$bls_rescue_err && $disable_bls_rescue) {
# Hide rescue files after grubby has had a chance to update normal entries.
$bls_rescue_err = &grub2_set_bls_rescue_disabled(1, \@entries);
}
$bls_args_updated{'GRUB_DISABLE_RECOVERY'} = 1
if (!$bls_rescue_err && $can_handle_bls_rescue &&
!&grub2_has_non_bls_recovery_entries(\@entries));
&grub2_mark_regenerate_needed()
if (&grub2_defaults_updates_need_generate(
$current_values, \%updates, \%bls_args_updated));
&webmin_log("defaults");
&error($bls_err) if ($bls_err);
&error($bls_rescue_err) if ($bls_rescue_err);
&redirect("index.cgi");
# valid_default_entry_value(value, current-value, &entries)
# Returns true if a posted default entry came from the detected selector.
sub valid_default_entry_value
{
my ($value, $current, $entries) = @_;
return 1 if (!defined($value) || $value eq '');
$current = '0' if (!defined($current) || $current eq '');
return 1 if ($value eq 'saved');
return 1 if ($value eq $current);
foreach my $entry (@$entries) {
my $selector = &grub2_entry_selector($entry);
return 1 if (defined($selector) && $selector eq $value);
}
return 0;
}
# update_bls_kernel_args(&old-values, &updates, &entries)
# Applies changed kernel option defaults to existing BLS entries.
sub update_bls_kernel_args
{
my ($old_values, $updates, $entries) = @_;
my %updated;
foreach my $field (
[ 'GRUB_CMDLINE_LINUX', undef ],
[ 'GRUB_CMDLINE_LINUX_DEFAULT',
[ &grub2_bls_kernel_arg_targets($entries, 0) ] ],
)
{
my ($key, $targets) = @$field;
my ($remove, $add) =
&grub2_kernel_args_delta($old_values->{$key}, $updates->{$key});
# No delta means this defaults field can be ignored for BLS updates.
next if (!@$remove && !@$add);
next if (defined($targets) && !@$targets);
my $err = &grub2_update_bls_kernel_args(
$old_values->{$key}, $updates->{$key}, $targets);
if ($err) {
# A partial grubby failure falls back to regeneration when needed.
&grub2_mark_regenerate_needed()
if (&grub2_defaults_updates_need_generate(
$old_values, $updates, \%updated));
return ($err, %updated);
}
$updated{$key} = 1;
}
return (undef, %updated);
}
# lock_bls_update_files(&old-values, &updates, &entries)
# Locks files that grubby may change so Webmin can diff them.
sub lock_bls_update_files
{
my ($old_values, $updates, $entries) = @_;
my %lock_all = &kernel_args_changed(
$old_values->{'GRUB_CMDLINE_LINUX'}, $updates->{'GRUB_CMDLINE_LINUX'}) ?
( all => 1 ) : ();
my %lock_default = &kernel_args_changed(
$old_values->{'GRUB_CMDLINE_LINUX_DEFAULT'},
$updates->{'GRUB_CMDLINE_LINUX_DEFAULT'}) ? ( default => 1 ) : ();
return () if (!%lock_all && !%lock_default);
my (@locked, %seen);
# kernelopts-based entries may be updated through grubenv instead of .conf files.
if (grep { &grub2_entry_uses_kernelopts($_) } @$entries) {
my $file = &grub2_config_value('grubenv_file') || '';
if ($file ne '' && -e $file && !$seen{$file}++) {
&lock_file($file);
push(@locked, $file);
}
}
foreach my $entry (@$entries) {
next if (($entry->{'source'} || '') ne 'bls');
# Default-only changes skip rescue entries, matching the edit form wording.
next if (!%lock_all && &grub2_entry_is_bls_rescue($entry));
my $file = $entry->{'file'};
next if (!defined($file) || $file eq '' || $seen{$file}++);
&lock_file($file);
push(@locked, $file);
}
return @locked;
}
# unlock_bls_update_files(files...)
# Unlocks files after grubby has run.
sub unlock_bls_update_files
{
foreach my $file (reverse @_) {
&unlock_file($file);
}
}
# kernel_args_changed(old-args, new-args)
# Returns true when a kernel-args delta would be applied.
sub kernel_args_changed
{
my ($old_args, $new_args) = @_;
my ($remove, $add) = &grub2_kernel_args_delta($old_args, $new_args);
return @$remove || @$add;
}

25
grub2/save_manual.cgi Executable file
View File

@@ -0,0 +1,25 @@
#!/usr/local/bin/perl
# Save a manually edited allowlisted GRUB 2 file.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%in, %text);
&ReadParseMime();
&error_setup($text{'manual_err'});
&grub2_assert_acl('manual');
# Re-check the allowlist on save; the form value is not trusted.
my $file = $in{'file'} || '';
&grub2_manual_file($file) || &error($text{'manual_efile'});
$in{'data'} = '' if (!defined($in{'data'}));
$in{'data'} =~ s/\r//g;
# Each file type gets its own validator before the locked write happens.
my $err = &save_manual_grub_file($file, $in{'data'});
&error(&text('manual_evalidate', $err)) if ($err);
&grub2_mark_regenerate_needed();
&webmin_log("manual", undef, $file);
&redirect("");

36
grub2/save_security.cgi Executable file
View File

@@ -0,0 +1,36 @@
#!/usr/local/bin/perl
# Save GRUB 2 password protection settings.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%in, %text);
&ReadParse();
&error_setup($text{'security_err'});
&grub2_assert_acl('security');
# The enable flag is deliberately boolean; all other fields normalize below.
defined($in{'enabled'}) && $in{'enabled'} =~ /^[01]\z/ ||
&error($text{'security_err'});
foreach my $field (qw(user password password2 hash)) {
$in{$field} = "" if (!defined($in{$field}));
}
$in{'hash'} =~ s/^\s+|\s+\z//g;
# Read current state so disabling an existing script still triggers regenerate.
my $current = &grub2_read_security_config();
my $err = &grub2_save_security_config({
'enabled' => $in{'enabled'},
'user' => $in{'user'},
'password' => $in{'password'},
'password2' => $in{'password2'},
'hash' => $in{'hash'},
});
&error($err) if ($err);
# A changed password script is included by grub-mkconfig, so refresh the menu.
&grub2_mark_regenerate_needed()
if ($in{'enabled'} || $current->{'exists'});
&webmin_log("security", undef, $in{'enabled'} ? "enabled" : "disabled");
&redirect("index.cgi");

119
grub2/save_theme.cgi Executable file
View File

@@ -0,0 +1,119 @@
#!/usr/local/bin/perl
# Save GRUB 2 theme and appearance settings.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%in, %text);
&ReadParse();
&error_setup($text{'theme_err'});
&grub2_assert_acl('edit');
my $parsed = &read_grub_defaults();
my $current_values = $parsed->{'values'};
my %updates;
# Terminal output is constrained because themes require graphical output.
my %terminal_outputs = map { $_ => 1 }
('', 'console', 'gfxterm', 'gfxterm console', 'serial');
$in{'terminal_output'} = '' if (!defined($in{'terminal_output'}));
&error($text{'defaults_eterminal_output'})
if (!$terminal_outputs{$in{'terminal_output'}});
$updates{'GRUB_TERMINAL_OUTPUT'} =
$in{'terminal_output'} eq '' ? undef : $in{'terminal_output'};
# Graphics mode is a small GRUB grammar, not a general shell value.
$in{'gfxmode'} = '' if (!defined($in{'gfxmode'}));
my $gerr = &grub2_validate_gfxmode($in{'gfxmode'});
&error($gerr) if ($gerr);
$updates{'GRUB_GFXMODE'} = $in{'gfxmode'} eq '' ? undef : $in{'gfxmode'};
# Theme mode decides whether a source is installed, kept, or explicitly unset.
my %theme_modes = map { $_ => 1 } qw(keep install clear);
$in{'theme_mode'} = 'keep' if (!defined($in{'theme_mode'}));
&error($text{'defaults_etheme_mode'}) if (!$theme_modes{$in{'theme_mode'}});
if ($in{'theme_mode'} eq 'install') {
# Refuse a graphical theme if the pending terminal output is console-only.
my $requested_terminal_output =
defined($updates{'GRUB_TERMINAL_OUTPUT'}) ?
$updates{'GRUB_TERMINAL_OUTPUT'} :
$current_values->{'GRUB_TERMINAL_OUTPUT'};
if (defined($requested_terminal_output) &&
$requested_terminal_output eq 'console') {
&error($text{'defaults_etheme_terminal'});
}
my ($theme, $terr) = &grub2_install_theme_source($in{'theme_source'});
&error($terr) if ($terr);
$updates{'GRUB_THEME'} = $theme;
}
elsif ($in{'theme_mode'} eq 'clear') {
$updates{'GRUB_THEME'} = undef;
}
# Background images are copied below the configured GRUB boot tree.
$in{'background'} = '' if (!defined($in{'background'}));
if ($in{'background'} eq '') {
$updates{'GRUB_BACKGROUND'} = undef;
}
else {
my ($background, $berr) =
&grub2_install_background_source($in{'background'});
&error($berr) if ($berr);
$updates{'GRUB_BACKGROUND'} = $background;
}
# Color fields use a mode selector so the default/unset state stays explicit.
foreach my $field (
[ 'color_normal', 'GRUB_COLOR_NORMAL',
$text{'defaults_color_normal'} ],
[ 'color_highlight', 'GRUB_COLOR_HIGHLIGHT',
$text{'defaults_color_highlight'} ],
)
{
my ($input, $key, $label) = @$field;
my $mode = $in{$input.'_mode'} || 'default';
my $fg = $in{$input.'_fg'} || '';
my $bg = $in{$input.'_bg'} || '';
my %colors = map { $_ => 1 } &grub2_color_names();
if ($mode eq 'default') {
$updates{$key} = undef;
}
elsif ($mode eq 'set' && $colors{$fg} && $colors{$bg}) {
$updates{$key} = $fg.'/'.$bg;
}
else {
&error(&text('defaults_ecolor', $label));
}
}
my $theme = exists($updates{'GRUB_THEME'}) ?
$updates{'GRUB_THEME'} : $current_values->{'GRUB_THEME'};
my $terminal_output = exists($updates{'GRUB_TERMINAL_OUTPUT'}) ?
$updates{'GRUB_TERMINAL_OUTPUT'} :
$current_values->{'GRUB_TERMINAL_OUTPUT'};
# Check the final combined state too, because either field may be unchanged.
if (defined($theme) && $theme ne '' &&
defined($terminal_output) && $terminal_output eq 'console') {
&error($text{'defaults_etheme_terminal'});
}
# Keep the generator script while any color override exists or used to exist.
my $need_color_script =
(defined($updates{'GRUB_COLOR_NORMAL'}) &&
$updates{'GRUB_COLOR_NORMAL'} ne '') ||
(defined($updates{'GRUB_COLOR_HIGHLIGHT'}) &&
$updates{'GRUB_COLOR_HIGHLIGHT'} ne '') ||
-e &grub2_color_file();
my $err = &save_grub_defaults_values(\%updates);
&error(&text('manual_evalidate', $err)) if ($err);
if ($need_color_script) {
# The color script reads /etc/default/grub at generation time.
$err = &grub2_save_color_script();
&error(&text('manual_evalidate', $err)) if ($err);
}
&grub2_mark_regenerate_needed();
&webmin_log("theme");
&redirect("index.cgi");

22
grub2/set_default.cgi Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/local/bin/perl
# Set the saved GRUB 2 default boot entry.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%in, %text);
&ReadParse();
&error_setup($text{'runtime_err'});
&grub2_assert_acl('runtime');
# Runtime commands operate on the parsed generated menu index shown on index.cgi.
my $entry = &grub2_entry_by_index($in{'idx'});
&error($text{'runtime_eentry'}) if (!$entry);
# The helper validates the selector before invoking grub-set-default.
my $err = &grub2_run_entry_command('set_default_cmd', $entry);
&error($err) if ($err);
my $selector = &grub2_entry_selector($entry);
&webmin_log("default", undef, $selector);
&redirect("");

252
grub2/status.cgi Executable file
View File

@@ -0,0 +1,252 @@
#!/usr/local/bin/perl
# Display GRUB 2 configuration and runtime status.
use strict;
use warnings;
require './grub2-lib.pl'; ## no critic
our (%text);
&error_setup($text{'acl_ecannot'});
&grub2_assert_acl('view');
my %access = &grub2_effective_acl();
&ui_print_header(undef, $text{'status_title'}, "");
# Missing-install output mirrors index.cgi but keeps this page read-only.
if (!&grub2_any_installed()) {
print &ui_alert($text{'index_missing'}, 'warning');
foreach my $issue (&grub2_install_issues()) {
print &ui_div(&text('index_missing_detail',
&ui_tag('tt', &html_escape($issue))));
}
&ui_print_footer("index.cgi", $text{'index_return'});
exit;
}
foreach my $warning (&grub2_status_warnings()) {
print &ui_alert($warning, 'warning');
}
print &ui_div($text{'index_status_desc'});
# The summary uses defaults plus grubenv because GRUB stores both persistently.
my $parsed = &read_grub_defaults();
my %env = &grub2_read_env();
&print_summary($parsed);
&print_boot_selection($parsed, \%env);
&print_security_status();
&print_theme_status($parsed);
&ui_print_footer("index.cgi", $text{'index_return'});
# print_summary(&parsed-defaults)
# Outputs a compact summary of important GRUB paths, commands, and defaults.
sub print_summary
{
my ($parsed) = @_;
my $values = $parsed->{'values'};
my @entries = &grub2_boot_entries();
print &ui_table_start($text{'index_summary'}, "width=100%", 2);
# Start with path and command discovery so support issues are visible first.
print &status_table_row($text{'index_boot_mode'}, "boot_mode",
&boot_mode_cell());
print &status_table_row($text{'index_secure_boot'}, "secure_boot",
&secure_boot_cell());
print &status_table_row($text{'index_default_file'}, "default_file",
&path_cell(&grub2_config_value('default_file')));
print &status_table_row($text{'index_grub_cfg'}, "grub_cfg",
&path_cell(&grub2_config_value('grub_cfg')));
print &status_table_row($text{'index_grub_dir'}, "grub_dir",
&path_cell(&grub2_config_value('grub_dir')));
print &status_table_row($text{'index_bls_dir'}, "bls_dir",
&path_cell(&grub2_config_value('bls_dir')));
print &status_table_row($text{'index_mkconfig'}, "mkconfig",
&command_cell('mkconfig_cmd'));
print &status_table_row($text{'index_install_cmd'}, "install_cmd",
&command_cell('install_cmd'));
print &ui_table_hr();
# Defaults below the separator mirror the editable defaults page.
print &status_table_row($text{'index_entries'}, "entries",
&text('index_entry_count', scalar(@entries)));
print &status_table_row($text{'index_kernel_options_source'},
"kernelopts_source",
&value_cell(&grub2_kernel_options_source_text(\@entries)));
foreach my $pair (
[ 'GRUB_TIMEOUT_STYLE', $text{'defaults_timeout_style'}, "timeout_style" ],
[ 'GRUB_TIMEOUT', $text{'defaults_timeout'}, "timeout" ],
[ 'GRUB_CMDLINE_LINUX_DEFAULT', $text{'defaults_cmdline_default'},
"cmdline_default" ],
[ 'GRUB_CMDLINE_LINUX', $text{'defaults_cmdline'}, "cmdline" ],
[ 'GRUB_DISABLE_RECOVERY', $text{'defaults_disable_recovery'},
"disable_recovery" ],
[ 'GRUB_DISABLE_OS_PROBER', $text{'defaults_disable_os_prober'},
"disable_os_prober" ],
)
{
my ($key, $label, $help) = @$pair;
print &status_table_row($label, $help, &literal_cell($values->{$key}));
}
print &ui_table_end();
}
# print_theme_status(&parsed-defaults)
# Outputs theme and graphical menu appearance settings.
sub print_theme_status
{
my ($parsed) = @_;
my $values = $parsed->{'values'};
print &ui_hidden_table_start($text{'defaults_theme_header'}, "width=100%", 2,
"theme", 0);
foreach my $pair (
[ 'GRUB_TERMINAL_OUTPUT', $text{'defaults_terminal_output'},
"terminal_output" ],
[ 'GRUB_GFXMODE', $text{'defaults_gfxmode'}, "gfxmode" ],
[ 'GRUB_THEME', $text{'defaults_theme'}, "theme" ],
[ 'GRUB_BACKGROUND', $text{'defaults_background'}, "background" ],
[ 'GRUB_COLOR_NORMAL', $text{'defaults_color_normal'}, "color_normal" ],
[ 'GRUB_COLOR_HIGHLIGHT', $text{'defaults_color_highlight'},
"color_highlight" ],
)
{
my ($key, $label, $help) = @$pair;
print &status_table_row($label, $help, &literal_cell($values->{$key}));
}
print &ui_hidden_table_end("theme");
}
# print_boot_selection(&parsed-defaults, &env)
# Outputs saved and one-time boot selection state.
sub print_boot_selection
{
my ($parsed, $env) = @_;
print &ui_hidden_table_start($text{'index_boot_selection'}, "width=100%", 2,
"boot_selection", 0);
print &status_table_row($text{'defaults_default'}, "default",
&literal_cell($parsed->{'values'}->{'GRUB_DEFAULT'}));
print &status_table_row($text{'index_saved_entry'}, "saved_entry",
&literal_cell($env->{'saved_entry'}));
print &status_table_row($text{'index_next_entry'}, "next_entry",
&literal_cell($env->{'next_entry'}));
print &status_table_row($text{'index_env'}, "grubenv",
&path_cell(&grub2_config_value('grubenv_file')));
print &ui_hidden_table_end("boot_selection");
}
# print_security_status()
# Outputs Webmin-managed GRUB password protection state.
sub print_security_status
{
my $state = &grub2_read_security_config();
print &ui_alert($text{'security_unmanaged'}, 'warning')
if ($state->{'exists'} && !$state->{'managed'});
print &ui_hidden_table_start($text{'security_header'}, "width=100%", 2,
"security", 0);
# Password hash contents are never displayed, only whether one is configured.
print &status_table_row($text{'index_security_state'}, "security_current",
&security_state_cell($state));
print &status_table_row($text{'index_security_user'}, "security_user",
$state->{'enabled'} ? &html_escape($state->{'user'}) :
$text{'index_not_set'});
print &status_table_row($text{'index_security_hash'}, "security_hash",
$state->{'hash'} ? $text{'index_security_hash_set'} :
$text{'index_security_hash_missing'});
print &status_table_row($text{'index_security_file'}, "security_file",
&path_cell($state->{'file'}));
print &status_table_row($text{'index_security_mkpasswd'}, "security_mkpasswd",
&command_cell('mkpasswd_cmd'));
print &ui_hidden_table_end("security");
}
# status_table_row(label, help, value)
# Returns a standard status table row with contextual help on the label.
sub status_table_row
{
my ($label, $help, $value) = @_;
return &ui_table_row(&hlink($label, $help), $value);
}
# path_cell(path)
# Returns escaped path display HTML with missing-state text.
sub path_cell
{
my ($path) = @_;
return $text{'index_not_set'} if (!defined($path) || $path eq '');
my $html = &manual_path_link($path, &ui_tag('tt', &html_escape($path)));
return -e $path ? $html : $html.' '.$text{'index_missing_file'};
}
# command_cell(config-key)
# Returns escaped command display HTML with availability state.
sub command_cell
{
my ($key) = @_;
my $cmd = &grub2_command($key);
return &ui_tag('tt', &html_escape($cmd)) if ($cmd);
my $raw = &grub2_config_value($key);
return $text{'index_not_set'} if (!defined($raw) || $raw eq '');
return &ui_tag('tt', &html_escape($raw)).' '.$text{'index_not_readable'};
}
# manual_path_link(path, html)
# Links editable GRUB files to the manual editor when permitted.
sub manual_path_link
{
my ($path, $html) = @_;
return $html if (!&grub2_check_acl('manual') || !&grub2_manual_file($path));
# Link only allowlisted paths; generated grub.cfg remains informational.
return &ui_tag('a', $html, {
'href' => "edit_manual.cgi?file=".&urlize($path),
});
}
# security_state_cell(&state)
# Returns text for the password protection status.
sub security_state_cell
{
my ($state) = @_;
return $text{'index_security_unmanaged'}
if ($state->{'exists'} && !$state->{'managed'});
return $state->{'enabled'} ? $text{'index_security_enabled'} :
$text{'index_security_disabled'};
}
# boot_mode_cell()
# Returns the detected firmware boot mode for display.
sub boot_mode_cell
{
my $mode = &grub2_boot_mode();
return $text{'index_boot_mode_uefi'} if ($mode eq 'uefi');
return $text{'index_boot_mode_bios'};
}
# secure_boot_cell()
# Returns the detected Secure Boot state for display.
sub secure_boot_cell
{
my $state = &grub2_secure_boot_status();
return $text{'index_secure_boot_'.$state} || $text{'index_secure_boot_unknown'};
}
# value_cell(value)
# Returns escaped value display HTML with unset-state text.
sub value_cell
{
my ($value) = @_;
return $text{'index_not_set'} if (!defined($value) || $value eq '');
return $text{'defaults_true'} if ($value eq 'true');
return $text{'defaults_false'} if ($value eq 'false');
return &html_escape($value);
}
# literal_cell(value)
# Returns an escaped literal GRUB value with unset and boolean mapping.
sub literal_cell
{
my ($value) = @_;
return $text{'index_not_set'} if (!defined($value) || $value eq '');
return $text{'defaults_true'} if ($value eq 'true');
return $text{'defaults_false'} if ($value eq 'false');
return &ui_tag('tt', &html_escape($value));
}

68
grub2/t/perlcritic.t Normal file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/perl
use strict;
use warnings;
use Test::More;
BEGIN {
eval { require Perl::Critic; 1 }
or plan skip_all => 'Perl::Critic not installed';
}
use File::Find;
# script_dir()
# Returns the directory containing this test file, without relying on Cwd.
sub script_dir
{
my $path = $0;
if ($path =~ m{^/}) {
$path =~ s{/[^/]+$}{};
return $path;
}
my $cwd = `pwd`;
chomp($cwd);
if ($path =~ m{/}) {
$path =~ s{/[^/]+$}{};
return $cwd.'/'.$path;
}
return $cwd;
}
my $bindir = script_dir();
my $module_dir = "$bindir/..";
my $profile = "$bindir/../../.perlcriticrc";
if (!-r $profile) {
plan skip_all => 'Perl::Critic profile not installed';
}
chdir($module_dir) or die "chdir: $!";
my @files;
find(
sub {
return if -d;
return if -l;
return unless /\.(pl|cgi)\z/;
return if /\.info\.pl\z/;
push(@files, $File::Find::name);
},
'.'
);
@files = sort @files;
if (!@files) {
plan skip_all => 'no perl files to check';
}
my $critic = Perl::Critic->new(
-profile => $profile,
);
foreach my $file (@files) {
my @violations = $critic->critique($file);
is(scalar @violations, 0, "$file perlcritic");
if (@violations) {
diag join("", @violations);
}
}
done_testing();

1422
grub2/t/run-tests.t Normal file

File diff suppressed because it is too large Load Diff