Compare commits

...

7 Commits

Author SHA1 Message Date
Jamie Cameron
d16d9ea205 Merge pull request #2821 from webmin/dev/miniserv-logrotate
Some checks failed
Tests / prove (push) Has been cancelled
Package and upload artifacts / build (push) Has been cancelled
Add an option to rotate Miniserv logs using logrotate
2026-08-21 21:47:19 -07:00
Ilia Ross
f7da47cd5e Update changelog 2026-08-22 01:19:22 +02:00
Ilia Ross
0660a18276 Add function to make vendor configs editable 2026-08-22 01:17:49 +02:00
Ilia Ross
6183a4006a Add an option to rotate Miniserv logs using logrotate
This PR adds the ability for the logging pages to hand the Miniserv access and error logs, and the Webmin actions log, to logrotate instead of having Miniserv periodically delete them. The generated section uses copy-truncate rotation, since Miniserv only reopens its error log when the file disappears.

The option only appears when the Log File Rotation module is available and configured correctly.
2026-08-22 01:03:48 +02:00
Ilia Ross
850cc1f498 Fix error log check on systems without systemd unit
Some checks failed
Tests / prove (push) Has been cancelled
Package and upload artifacts / build (push) Has been cancelled
2026-08-21 21:24:23 +02:00
Ilia Ross
523853963e Add option to send Miniserv errors to the systemd journal
Add a Logging page to the Usermin Configuration module for the access-log settings, guarded by a new "log" ACL.

Also, Webmin and Usermin logging pages add new an "Error log destination" choice on systemd systems. Selecting the journal sets errorlog=- and installs a local StandardError=journal drop-in, leaving the packaged unit untouched.

https://forum.virtualmin.com/t/miniserv-webserver-log-growing-too-big-should-be-rotated/136562/10?u=ilia
2026-08-21 21:02:58 +02:00
Ilia Ross
3c26b2be3b Fix FQDN detection on systemd systems
ⓘ Do not return a short name from /etc/hostname when the full name is requested; resolve it with hostname -f first, honoring the no_hostname_f option, and otherwise fall back to the existing file-based logic.

Also check for /run/systemd/system instead of reading /proc/1/comm as it's much cheaper.

Fixes #2054
2026-08-21 19:37:47 +02:00
14 changed files with 461 additions and 15 deletions

View File

@@ -1,4 +1,9 @@
## Changelog
#### 2.661 (September, 2026)
* Add options to send Webmin and Usermin errors to the systemd journal [forum.virtualmin.com/t/136562](https://forum.virtualmin.com/t/miniserv-webserver-log-growing-too-big-should-be-rotated/136562)
* Add webserver logging controls to Usermin Configuration module
* Add option to rotate Webmin and Usermin webserver logs using `logrotate` instead of periodically clearing them [#2821](https://github.com/webmin/webmin/pull/2821)
#### 2.660 (August 20, 2026)
* Add support for creating `vfsv1` Linux quota files for limits above 4 TiB, while preserving existing quota file formats
* Add Btrfs subvolume quota management to the Disk Quotas module, with full and simple accounting modes

View File

@@ -168,6 +168,31 @@ return $file if (!$local);
return &copy_vendor_config($file, $local);
}
# ensure_writable_config_file(file)
# Makes a config file safe to edit, by creating the local override that
# shadows it when it belongs to the vendor tree. Callers must use this before
# saving directives, as save_directive refuses to write to a vendor file.
sub ensure_writable_config_file
{
my ($file) = @_;
if (&is_vendor_main_config($file)) {
return &ensure_local_main_config();
}
elsif (&is_vendor_config_file($file)) {
return &ensure_local_config_override($file);
}
elsif (my $vendor = &get_vendor_config_file($file)) {
# A local path that shadows a vendor file, but does not exist yet
return &ensure_local_config_override($vendor);
}
elsif ($file eq &get_config_parent()->{'file'} &&
&is_vendor_main_config(&get_main_config_file())) {
# New directives go to the main config, which is still the vendor one
return &ensure_local_main_config();
}
return $file;
}
# list_config_dir_files(directory, [relative-subdirectory])
# Returns relative and absolute paths for regular files below a directory
sub list_config_dir_files

View File

@@ -43,6 +43,6 @@ $o->{'bootup'} = $in{'bootup'};
sub get_icons
{
return ( "access" ,"bind" ,"ui" ,"umods" ,"os" ,"lang" ,"upgrade" ,"session" ,"assignment" ,"categories" ,"themes", "referers", "anon", "ssl" ,"configs" ,"acl" ,"restrict" ,"users" ,"defacl", "sessions", "blocked", "advanced" );
return ( "access" ,"bind" ,"log" ,"ui" ,"umods" ,"os" ,"lang" ,"upgrade" ,"session" ,"assignment" ,"categories" ,"themes", "referers", "anon", "ssl" ,"configs" ,"acl" ,"restrict" ,"users" ,"defacl", "sessions", "blocked", "advanced" );
}

65
usermin/change_log.cgi Executable file
View File

@@ -0,0 +1,65 @@
#!/usr/local/bin/perl
# Save Usermin webserver logging options
require './usermin-lib.pl';
&ReadParse();
$access{'log'} || &error($text{'acl_ecannot'});
&error_setup($text{'log_err'});
&get_usermin_miniserv_config(\%miniserv);
# Only a change of error destination on a systemd-managed service needs the
# drop-in and a full restart; otherwise the option is ignored.
my $journal_changed = defined($in{'error_journal'}) &&
&webmin::miniserv_systemd_journal_available("usermin.service") &&
($miniserv{'errorlog'} eq '-' ? 1 : 0) != ($in{'error_journal'} ? 1 : 0);
# Either Miniserv clears the logs itself, or logrotate takes them over. There
# is no setting for the latter, so it is on when clearing is off and a section
# already rotates the logs.
my $logrotate = $in{'logclear'} == 2 ? 1 : 0;
!$logrotate || &webmin::miniserv_logrotate_available() ||
&error($text{'log_elogrotate'});
my $was_logrotate = !int($miniserv{'logclear'}) &&
&webmin::miniserv_logrotate_available() &&
&webmin::get_miniserv_logrotate_section(\%miniserv) ? 1 : 0;
# Validate and save the access-log settings.
$miniserv{'log'} = $in{'log'};
$miniserv{'loghost'} = $in{'loghost'};
$miniserv{'logtrust'} = $in{'logtrust'};
$miniserv{'logclf'} = $in{'logclf'};
$miniserv{'logclear'} = $logrotate ? 0 : $in{'logclear'};
!$miniserv{'logclear'} || $in{'logtime'} =~ /^[1-9][0-9]*$/ ||
&error(&text('log_ehours', $in{'logtime'}));
$miniserv{'logtime'} = $in{'logtime'};
# Save the error destination and matching systemd drop-in when supported.
&lock_file($usermin_miniserv_config);
if ($journal_changed) {
&webmin::set_miniserv_error_destination(\%miniserv,
$usermin_miniserv_config, "usermin.service",
$in{'error_journal'});
}
else {
&put_usermin_miniserv_config(\%miniserv);
}
&unlock_file($usermin_miniserv_config);
# Hand the Miniserv logs to logrotate, or take them back from it
if ($logrotate) {
&webmin::setup_miniserv_logrotate(\%miniserv, "usermin");
}
elsif ($was_logrotate) {
&webmin::remove_miniserv_logrotate(\%miniserv);
}
# Restart through systemd when stderr needs to be re-attached.
if ($journal_changed) {
&webmin::restart_miniserv_systemd_service("usermin.service", 2);
}
else {
&restart_usermin_miniserv();
}
&webmin_log("log");
&redirect("");

View File

@@ -29,3 +29,4 @@ advanced=1
mobile=1
twofactor=1
web=1
log=1

46
usermin/edit_log.cgi Executable file
View File

@@ -0,0 +1,46 @@
#!/usr/local/bin/perl
# Display Usermin webserver logging options
require './usermin-lib.pl';
$access{'log'} || &error($text{'acl_ecannot'});
&ui_print_header(undef, $text{'log_title'}, "");
&get_usermin_miniserv_config(\%miniserv);
print &text('log_desc', "<tt>$miniserv{'logfile'}</tt>"),"<p>\n";
print &ui_form_start("change_log.cgi", "post");
print &ui_table_start($text{'log_header'}, undef, 2);
# Control the Usermin access log and its built-in expiry mechanism.
print &ui_table_row($text{'log_status'},
&ui_radio("log", $miniserv{'log'} ? 1 : 0,
[ [ 1, $text{'log_enable'} ],
[ 0, $text{'log_disable'} ] ]));
print &ui_table_row($text{'log_resolv'},
&ui_yesno_radio("loghost", int($miniserv{'loghost'})));
print &ui_table_row($text{'log_trust'},
&ui_yesno_radio("logtrust", int($miniserv{'logtrust'})));
print &ui_table_row($text{'log_clf'},
&ui_yesno_radio("logclf", int($miniserv{'logclf'})));
my @clear_opts = ( [ 1, &text('log_period',
&ui_textbox("logtime", $miniserv{'logtime'}, 10)) ] );
my $logclear = int($miniserv{'logclear'});
if (&webmin::miniserv_logrotate_available()) {
push(@clear_opts, [ 2, $text{'log_logrotate'} ]);
$logclear = 2 if (!$logclear &&
&webmin::get_miniserv_logrotate_section(\%miniserv));
}
push(@clear_opts, [ 0, $text{'no'} ]);
print &ui_table_row($text{'log_clear'},
&ui_radio("logclear", $logclear, \@clear_opts));
# Only systemd services can safely inherit stderr into the journal.
if (&webmin::miniserv_systemd_journal_available("usermin.service")) {
print &ui_table_row($text{'log_error'},
&ui_radio("error_journal", $miniserv{'errorlog'} eq '-' ? 1 : 0,
[ [ 0, $text{'log_error_file'} ],
[ 1, $text{'log_error_journal'} ] ]));
}
print &ui_table_end();
print &ui_form_end([ [ "save", $text{'save'} ] ]);
&ui_print_footer("", $text{'index_return'});

BIN
usermin/images/log.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

View File

@@ -55,6 +55,7 @@ if (!$miniserv{'root'} && !-d $miniserv{'root'}) {
@links = ( "edit_access.cgi",
"edit_bind.cgi",
"edit_log.cgi",
"edit_ui.cgi",
"edit_mods.cgi",
"edit_os.cgi",

View File

@@ -1,4 +1,22 @@
index_title=Usermin Configuration
log_title=Logging
log_desc=Usermin can be configured to write a log of web server hits, in the standard CLF log file format. If logging is enabled, you can also choose whether IP addresses or hostnames are recorded, and how often the log file is cleared. When enabled, logs are written to the file $1.
log_header=Webserver logging options
log_status=Logging active?
log_enable=Enable logging
log_disable=Disable logging
log_resolv=Log resolved hostnames?
log_trust=Log client IP address when behind proxy?
log_clf=Use combined log format (including referrer and user agent)?
log_clear=Periodically clear log files?
log_period=Yes, every $1 hours
log_logrotate=Yes, rotate using <tt>logrotate</tt>
log_elogrotate=The <tt>logrotate</tt> configuration is not available on this system
log_error=Error log destination
log_error_file=Write to <tt>miniserv.error</tt>
log_error_journal=Write to the <tt>systemd journal</tt>
log_err=Failed to save logging options
log_ehours='$1' is not a valid number of hours
index_econfig=The directory $1 either does not exist on your system, or is not the Usermin configuration directory. Maybe Usermin is not installed, or your <a href='$2'>module configuration</a> is incorrect.
index_return=Usermin configuration
index_stop=Stop Usermin

View File

@@ -4859,11 +4859,25 @@ return if ($nocache == 2);
if (!$system_hostname[$m]) {
if ($gconfig{'os_type'} ne 'windows') {
# If systemd system try /etc/hostname straight away
my $initsys = &trim(&backquote_command("cat /proc/1/comm 2>/dev/null"));
if ($initsys eq 'systemd') {
if (-d "/run/systemd/system") {
my $hostname = &read_file_contents("/etc/hostname");
$hostname =~ s/\r|\n//g;
# Cache the short name before resolving the full name
if ($hostname) {
my $shortname = $hostname;
$shortname =~ s/\..*$//;
$system_hostname[1] = $shortname;
}
# Resolve a short static hostname before caching it
if ($hostname && !$m && $hostname !~ /\./ &&
!$gconfig{'no_hostname_f'}) {
my $fqdn;
my $ex = &execute_command("hostname -f", undef,
\$fqdn, undef, 0, 1);
$fqdn = &trim($fqdn);
$hostname = $fqdn if (!$ex && $fqdn =~ /\./);
}
if ($hostname && ($m || $hostname =~ /\./)) {
$hostname =~ s/\..*$// if ($m);
$system_hostname[$m] = $hostname;
return $hostname;
@@ -7607,6 +7621,14 @@ my ($param_action,
my $m = $param_module ? $param_module : &get_module_name();
# When logrotate has emptied the actions log, throw away the file changes
# and annotations that belonged to the rotated entries
if ($gconfig{'logrotate'} && -e $webmin_logfile && !-s _) {
&unlink_file("$ENV{'WEBMIN_VAR'}/diffs");
&unlink_file("$ENV{'WEBMIN_VAR'}/files");
&unlink_file("$ENV{'WEBMIN_VAR'}/annotations");
}
if ($gconfig{'logclear'}) {
# check if it is time to clear the log
my @st = stat("$webmin_logfile.time");

View File

@@ -9,12 +9,29 @@ require './webmin-lib.pl';
&lock_file($ENV{'MINISERV_CONFIG'});
&get_miniserv_config(\%miniserv);
my ($miniserv_log, $in_log) = ($miniserv{'log'}, $in{'log'});
# Only a change of error destination on a systemd-managed service needs the
# drop-in and a full restart; otherwise the option is ignored.
my $journal_changed = defined($in{'error_journal'}) &&
&miniserv_systemd_journal_available("webmin.service") &&
($miniserv{'errorlog'} eq '-' ? 1 : 0) != ($in{'error_journal'} ? 1 : 0);
$miniserv{'log'} = $in{'log'};
$miniserv{'loghost'} = $in{'loghost'};
$miniserv{'logtrust'} = $in{'logtrust'};
$miniserv{'logclf'} = $in{'logclf'};
$miniserv{'logclear'} = $in{'logclear'};
!$in{'logclear'} || $in{'logtime'} =~ /^[1-9][0-9]*$/ ||
# Either Miniserv clears the logs itself, or logrotate takes them over. There
# is no setting for the latter, so it is on when clearing is off and a section
# already rotates the logs.
my $logrotate = $in{'logclear'} == 2 ? 1 : 0;
!$logrotate || &miniserv_logrotate_available() ||
&error($text{'log_elogrotate'});
my $was_logrotate = !int($miniserv{'logclear'}) &&
&miniserv_logrotate_available() &&
&get_miniserv_logrotate_section(\%miniserv) ? 1 : 0;
$miniserv{'logclear'} = $logrotate ? 0 : $in{'logclear'};
!$miniserv{'logclear'} || $in{'logtime'} =~ /^[1-9][0-9]*$/ ||
&error(&text('log_ehours', $in{'logtime'}));
$miniserv{'logtime'} = $in{'logtime'};
if ($in{'perms_def'}) {
@@ -42,12 +59,29 @@ if (defined($in{'login'})) {
delete($miniserv{'failed_script'});
}
}
&put_miniserv_config(\%miniserv);
# Save the error destination and matching systemd drop-in when supported.
if ($journal_changed) {
&set_miniserv_error_destination(\%miniserv,
$ENV{'MINISERV_CONFIG'}, "webmin.service",
$in{'error_journal'});
}
else {
&put_miniserv_config(\%miniserv);
}
&unlock_file($ENV{'MINISERV_CONFIG'});
# Hand the Miniserv and actions logs to logrotate, or take them back from it
if ($logrotate) {
&setup_miniserv_logrotate(\%miniserv, "webmin", [ $webmin_logfile ]);
}
elsif ($was_logrotate) {
&remove_miniserv_logrotate(\%miniserv, [ $webmin_logfile ]);
}
$gconfig{'log'} = $in{'log'};
$gconfig{'logtime'} = $in{'logtime'};
$gconfig{'logclear'} = $in{'logclear'};
$gconfig{'logclear'} = $miniserv{'logclear'};
$gconfig{'logrotate'} = $logrotate;
$gconfig{'logusers'} =
$in{'uall'} ? '' : join(" ", split(/\0/, $in{'users'}));
$gconfig{'logmodules'} =
@@ -71,6 +105,12 @@ if ($miniserv_log != $in_log) {
}
}
&show_restart_page();
# Restart through systemd when stderr needs to be re-attached.
if ($journal_changed) {
&restart_miniserv_systemd_service("webmin.service", 2);
&redirect("");
}
else {
&show_restart_page();
}
&webmin_log("log", undef, undef, \%in);

View File

@@ -31,12 +31,26 @@ print &ui_table_row($text{'log_trust'},
print &ui_table_row($text{'log_clf'},
&ui_yesno_radio("logclf", int($miniserv{'logclf'})));
# Clear logs regularly
# Clear logs regularly, or hand them over to logrotate when available
my @clear_opts = ( [ 1, &text('log_period',
&ui_textbox("logtime", $miniserv{'logtime'}, 10)) ] );
my $logclear = int($miniserv{'logclear'});
if (&miniserv_logrotate_available()) {
push(@clear_opts, [ 2, $text{'log_logrotate'} ]);
$logclear = 2 if (!$logclear &&
&get_miniserv_logrotate_section(\%miniserv));
}
push(@clear_opts, [ 0, $text{'no'} ]);
print &ui_table_row($text{'log_clear2'},
&ui_radio("logclear", int($miniserv{'logclear'}),
[ [ 1, &text('log_period',
&ui_textbox("logtime", $miniserv{'logtime'}, 10)) ],
[ 0, $text{'no'} ] ]));
&ui_radio("logclear", $logclear, \@clear_opts));
# A systemd service can keep Miniserv errors in the journal instead.
if (&miniserv_systemd_journal_available("webmin.service")) {
print &ui_table_row($text{'log_error'},
&ui_radio("error_journal", $miniserv{'errorlog'} eq '-' ? 1 : 0,
[ [ 0, $text{'log_error_file'} ],
[ 1, $text{'log_error_journal'} ] ]));
}
print &ui_table_hr();
@@ -101,4 +115,3 @@ print &ui_table_end();
print &ui_form_end([ [ "save", $text{'save'} ] ]);
&ui_print_footer("", $text{'index_return'});

View File

@@ -93,6 +93,11 @@ log_trust=Log client IP address when behind proxy?
log_clf=Use combined log format (including referrer and user agent)?
log_clear2=Periodically clear log files?
log_period=Yes, every $1 hours
log_logrotate=Yes, rotate using <tt>logrotate</tt>
log_elogrotate=The <tt>logrotate</tt> configuration is not available on this system
log_error=Error log destination
log_error_file=Write to <tt>miniserv.error</tt>
log_error_journal=Write to the <tt>systemd journal</tt>
log_forusers=Users to log Webmin actions for
log_uall=Log actions by all users
log_users=Only log actions by ..

View File

@@ -43,6 +43,211 @@ if (!-r $update_cache) {
our $primary_host = "webmin.com";
our $primary_ssl = $can_http_ssl;
=head2 miniserv_systemd_journal_available(unit)
Returns 1 if a systemd service is available for receiving Miniserv errors.
=cut
sub miniserv_systemd_journal_available
{
my ($unit) = @_;
return &has_command("systemctl") &&
(-r "/etc/systemd/system/$unit" ||
-r "/usr/lib/systemd/system/$unit" ||
-r "/lib/systemd/system/$unit");
}
=head2 set_miniserv_error_destination(&miniserv, config, unit, journal)
Selects the Miniserv error file or the systemd journal, using a local systemd
drop-in so that the packaged service unit remains unchanged.
=cut
sub set_miniserv_error_destination
{
my ($miniserv, $config_file, $unit, $journal) = @_;
$unit =~ /^(webmin|usermin)\.service$/ || return 0;
my $dropin_dir = "/etc/systemd/system/$unit.d";
my $dropin = "$dropin_dir/10-miniserv-error-log.conf";
# The dash keeps Miniserv's stderr attached to the service manager.
if ($journal) {
$miniserv->{'errorlog'} = '-';
&make_dir($dropin_dir, 0755) if (!-d $dropin_dir);
&write_file_contents($dropin,
"[Service]\nStandardError=journal\n");
}
else {
my $logfile = $miniserv->{'logfile'};
$logfile =~ s![^/]+$!miniserv.error!;
$miniserv->{'errorlog'} = $logfile;
&unlink_file($dropin) if (-e $dropin);
# Take the directory too, unless other drop-ins are still using it
rmdir($dropin_dir) if (-d $dropin_dir);
}
# Apply the Miniserv and service-manager settings as one UI operation.
&write_file($config_file, $miniserv);
&system_logged("systemctl daemon-reload >/dev/null 2>&1");
}
=head2 restart_miniserv_systemd_service(unit, [delay])
Fully restarts a Miniserv systemd service so that stderr is re-attached to the
selected destination. A delay allows the current Webmin response to finish.
=cut
sub restart_miniserv_systemd_service
{
my ($unit, $delay) = @_;
$unit =~ /^(webmin|usermin)\.service$/ || return 0;
$delay = int($delay);
if ($delay) {
&system_logged("(sleep $delay; systemctl --no-block restart $unit) " .
">/dev/null 2>&1 </dev/null &");
}
else {
&system_logged("systemctl restart $unit >/dev/null 2>&1 </dev/null");
}
return 1;
}
=head2 miniserv_logrotate_available()
Returns 1 if logrotate is installed and can be configured from Webmin.
=cut
sub miniserv_logrotate_available
{
return &foreign_available("logrotate") && &foreign_installed("logrotate")
? 1
: 0;
}
=head2 miniserv_log_files(&miniserv)
Returns the Miniserv access and error log paths that rotation applies to.
=cut
sub miniserv_log_files
{
my ($miniserv) = @_;
my $errorlog = $miniserv->{'logfile'};
$errorlog =~ s![^/]+$!miniserv.error!;
$errorlog = $miniserv->{'errorlog'} if ($miniserv->{'errorlog'} =~ /^\//);
return &unique($miniserv->{'logfile'}, $errorlog);
}
=head2 get_miniserv_logrotate_section(&miniserv)
Returns the logrotate section that covers the Miniserv access log, if any.
=cut
sub get_miniserv_logrotate_section
{
my ($miniserv) = @_;
&foreign_require("logrotate");
my ($logfile) = &miniserv_log_files($miniserv);
# Global directives like weekly and rotate share the list with log sections,
# but their name is a plain string instead of a list of log files
foreach my $c (@{&logrotate::get_config()}) {
next if (ref($c->{'name'}) ne 'ARRAY');
return $c if (&indexof($logfile, @{$c->{'name'}}) >= 0);
}
return undef;
}
=head2 setup_miniserv_logrotate(&miniserv, name, [&extra-logs])
Adds any missing Miniserv and extra logs to the access-log section, or creates
a section when none exists. Copy-truncate rotation lets Miniserv keep its open
error log handle, as it only re-opens that log when the file disappears.
=cut
sub setup_miniserv_logrotate
{
my ($miniserv, $name, $extra) = @_;
my @logs = &unique(&miniserv_log_files($miniserv), @{$extra || [ ]});
my $lconf = &get_miniserv_logrotate_section($miniserv);
# Skip any log that some other section already rotates
my %covered;
foreach my $c (@{&logrotate::get_config()}) {
next if (ref($c->{'name'}) ne 'ARRAY');
foreach my $log (@{$c->{'name'}}) {
$covered{$log} = 1;
}
}
my @missing = grep { !$covered{$_} } @logs;
return 0 if ($lconf && !@missing);
# Vendor files cannot be edited in place, so copy first on systems that
# keep their logrotate config under /usr
my $file = $lconf ? $lconf->{'file'} : &logrotate::get_add_file($name);
&logrotate::ensure_writable_config_file($file);
my $parent = &logrotate::get_config_parent();
if ($lconf) {
# Copying re-reads the config, so look the section up again
$lconf = &get_miniserv_logrotate_section($miniserv);
push(@{$lconf->{'name'}}, @missing);
}
else {
$lconf = { 'file' => &logrotate::get_add_file($name),
'name' => [ grep { !$covered{$_} } @logs ],
'members' => [ { 'name' => 'weekly' },
{ 'name' => 'rotate', 'value' => 7 },
{ 'name' => 'missingok' },
{ 'name' => 'notifempty' },
{ 'name' => 'compress' },
{ 'name' => 'copytruncate' } ] };
}
&lock_file($lconf->{'file'});
&logrotate::save_directive($parent,
defined($lconf->{'index'}) ? $lconf : undef,
$lconf);
&flush_file_lines($lconf->{'file'});
&unlock_file($lconf->{'file'});
&logrotate::flush_logrotate_config_cache();
return 1;
}
=head2 remove_miniserv_logrotate(&miniserv, [&extra-logs])
Removes the Miniserv logs and any extra logs from their logrotate section. The
section and its file are deleted when they cover nothing else.
=cut
sub remove_miniserv_logrotate
{
my ($miniserv, $extra) = @_;
my $lconf = &get_miniserv_logrotate_section($miniserv);
return 0 if (!$lconf);
&logrotate::ensure_writable_config_file($lconf->{'file'});
# Copying re-reads the config, so look the section up again
$lconf = &get_miniserv_logrotate_section($miniserv);
my %logs = map { $_, 1 } (&miniserv_log_files($miniserv), @{$extra || [ ]});
my @leftover = grep { !$logs{$_} } @{$lconf->{'name'}};
my $parent = &logrotate::get_config_parent();
&lock_file($lconf->{'file'});
if (@leftover) {
# Other logs share the section, so only drop ours
$lconf->{'name'} = \@leftover;
&logrotate::save_directive($parent, $lconf, $lconf);
&flush_file_lines($lconf->{'file'});
&unlock_file($lconf->{'file'});
&logrotate::flush_logrotate_config_cache();
}
else {
&logrotate::save_directive($parent, $lconf, undef);
&flush_file_lines($lconf->{'file'});
&unlock_file($lconf->{'file'});
&logrotate::flush_logrotate_config_cache();
&logrotate::delete_if_empty($lconf->{'file'});
}
return 1;
}
our $primary_port = $primary_ssl ? 443 : 80;
our $webmin_key_email = "jcameron\@webmin.com";