mirror of
https://github.com/webmin/webmin.git
synced 2026-06-24 21:10:29 +01:00
This PR adds a standalone Systemd Services and Units module for managing systemd units across system and user scopes. The module keeps systemd-specific behavior separate from the legacy Bootup and Shutdown module and is implemented as standalone `strict`/`warnings` Perl code rather than depending on its existing init helpers. Those helpers intentionally smooth over multiple init systems, while this module keeps systemd-specific file handling, user-manager behavior, ACL checks, and control operations explicit, scoped, and easier to audit. It includes: - Tabbed views for services, timers, sockets, paths, targets, storage, resources, devices, and user units - Guided creation and editing for common unit types, with contextual fields, validation, and help - User-scoped unit management with linger support and safe handling of home-directory unit files - Runtime actions for start, stop, restart, enable, disable, status, logs, properties, dependencies, and system-unit mask/unmask - Drop-in override inventory plus create, edit, and delete flows - Manual unit-file editing with daemon reload reminders and actions - Configurable module behavior, visible tabs, display options, and post-create navigation - Comprehensive ACL controls for system/user scopes, actions, manual edits, drop-ins, linger, reload, backup, and user filters - Safe Webmin user support through a scoped safe ACL preset - Virtualmin integration for granting domain owners access to their own systemd user units - Tests for unit generation, safety checks, ACL behavior, user-unit handling, backup coverage, and Perl::Critic compatibility A companion Virtualmin PR adds template integration so domain owners can be granted scoped access to their own systemd user units when this module is installed.
123 lines
3.8 KiB
Perl
Executable File
123 lines
3.8 KiB
Perl
Executable File
#!/usr/local/bin/perl
|
|
# Show an inventory of discovered systemd drop-in override files.
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
require './systemd-lib.pl'; ## no critic
|
|
|
|
our (%access, %config, %text);
|
|
|
|
ReadParse();
|
|
|
|
has_command("systemctl") || error($text{'systemd_esystemctl'});
|
|
systemd_can_enter_module(\%access) || systemd_acl_error('penter');
|
|
$config{'show_dropin_inventory'} || error($text{'dropins_disabled'});
|
|
|
|
my $can_system = systemd_can_view_system(\%access);
|
|
my $can_user = systemd_can_view_user_scope(\%access);
|
|
|
|
my @system_units = $can_system ? list_units() : ( );
|
|
my %system_units = map { $_->{'name'}, $_ } @system_units;
|
|
my @user_units = $can_user ?
|
|
grep { systemd_acl_user_allowed(\%access, $_->{'user'}) }
|
|
list_all_user_units() : ( );
|
|
my %user_units = map { $_->{'user'}."\t".$_->{'name'}, $_ } @user_units;
|
|
|
|
my @dropins;
|
|
push(@dropins, list_system_dropin_override_files()) if ($can_system);
|
|
if ($can_user) {
|
|
foreach my $dropin (list_all_user_dropin_override_files()) {
|
|
next if (!systemd_acl_user_allowed(\%access, $dropin->{'user'}));
|
|
push(@dropins, $dropin);
|
|
}
|
|
}
|
|
@dropins = sort { $a->{'scope'} cmp $b->{'scope'} ||
|
|
($a->{'user'} || "") cmp ($b->{'user'} || "") ||
|
|
$a->{'unit'} cmp $b->{'unit'} ||
|
|
$a->{'file'} cmp $b->{'file'} } @dropins;
|
|
|
|
ui_print_header(undef, $text{'dropins_title'}, "", "intro", undef, 1,
|
|
undef, action_links());
|
|
|
|
print ui_tag('p', $text{'dropins_desc'});
|
|
if (!@dropins) {
|
|
print ui_tag('p', $text{'dropins_empty'});
|
|
}
|
|
else {
|
|
print_dropin_table(\@dropins, \%system_units, \%user_units);
|
|
}
|
|
|
|
ui_print_footer("index.cgi", $text{'index_return'});
|
|
|
|
# print_dropin_table(dropins, system-units, user-units)
|
|
# Outputs the discovered drop-in inventory table.
|
|
sub print_dropin_table
|
|
{
|
|
my ($dropins, $system_units, $user_units) = @_;
|
|
print ui_columns_start([
|
|
$text{'systemd_name'},
|
|
$text{'dropins_scope'},
|
|
$text{'systemd_owner'},
|
|
$text{'dropins_file'},
|
|
$text{'dropins_actions'},
|
|
]);
|
|
foreach my $dropin (@$dropins) {
|
|
print ui_columns_row([
|
|
ui_tag('tt', html_escape($dropin->{'unit'})),
|
|
html_escape(dropin_scope_label($dropin)),
|
|
$dropin->{'scope'} eq 'user' ?
|
|
ui_tag('tt', html_escape($dropin->{'user'})) :
|
|
html_escape("-"),
|
|
ui_tag('tt', html_escape($dropin->{'file'})),
|
|
dropin_action_link($dropin, $system_units, $user_units),
|
|
]);
|
|
}
|
|
print ui_columns_end();
|
|
}
|
|
|
|
# dropin_scope_label(dropin)
|
|
# Returns a human-readable scope label for a drop-in descriptor.
|
|
sub dropin_scope_label
|
|
{
|
|
my ($dropin) = @_;
|
|
return $dropin->{'scope'} eq 'user' ?
|
|
$text{'dropins_scope_user'} : $text{'dropins_scope_system'};
|
|
}
|
|
|
|
# dropin_action_link(dropin, system-units, user-units)
|
|
# Returns an edit action when the discovered drop-in belongs to a known unit.
|
|
sub dropin_action_link
|
|
{
|
|
my ($dropin, $system_units, $user_units) = @_;
|
|
if ($dropin->{'scope'} eq 'user') {
|
|
my $user = $dropin->{'user'};
|
|
my $unit = $dropin->{'unit'};
|
|
return ui_tag('i', html_escape($text{'dropins_unit_missing'}))
|
|
if (!$user_units->{$user."\t".$unit});
|
|
return ui_tag('i', html_escape($text{'dropins_view_only'}))
|
|
if (!systemd_can_dropin(\%access, 1, $user));
|
|
my $url = "edit_unit.cgi?scope=user&unituser=".urlize($user).
|
|
"&name=".urlize($unit)."&dropin=1".
|
|
dropin_file_arg($dropin);
|
|
return ui_link($url, $text{'dropins_edit'});
|
|
}
|
|
my $unit = $dropin->{'unit'};
|
|
return ui_tag('i', html_escape($text{'dropins_unit_missing'}))
|
|
if (!$system_units->{$unit});
|
|
return ui_tag('i', html_escape($text{'dropins_view_only'}))
|
|
if (!systemd_can_dropin(\%access, 0));
|
|
return ui_link("edit_unit.cgi?name=".urlize($unit)."&dropin=1".
|
|
dropin_file_arg($dropin),
|
|
$text{'dropins_edit'});
|
|
}
|
|
|
|
# dropin_file_arg(dropin)
|
|
# Returns an exact drop-in file query argument for non-standard drop-ins.
|
|
sub dropin_file_arg
|
|
{
|
|
my ($dropin) = @_;
|
|
return "" if ($dropin->{'standard'});
|
|
return "&dropfile=".urlize($dropin->{'file'});
|
|
}
|