Files
webmin/grub2/status.cgi
Ilia Ross 523d68c67a 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.
2026-05-28 02:20:53 +02:00

253 lines
8.6 KiB
Perl
Executable File

#!/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));
}