mirror of
https://github.com/webmin/webmin.git
synced 2026-02-03 06:03:28 +00:00
568 lines
21 KiB
Perl
Executable File
568 lines
21 KiB
Perl
Executable File
#!/usr/bin/env perl
|
|
# server - control Webmin web-server
|
|
|
|
use strict;
|
|
use warnings;
|
|
use 5.010;
|
|
|
|
use File::Basename;
|
|
use Getopt::Long;
|
|
use Pod::Usage;
|
|
use Term::ANSIColor qw(:constants);
|
|
use lib (dirname(dirname($0)));
|
|
use WebminCore;
|
|
|
|
sub main
|
|
{
|
|
my %opt;
|
|
GetOptions('help|h' => \$opt{'help'},
|
|
'command|x=s' => \$opt{'command'},
|
|
'config|c=s' => \$opt{'config'});
|
|
|
|
# If username passed as regular param
|
|
my $cmd = scalar(@ARGV) == 1 && $ARGV[0];
|
|
$cmd = $opt{'command'} if ($opt{'command'});
|
|
if ($cmd !~ /^(stats|status|start|stop|restart|reload|force-restart|kill)$/) {
|
|
$cmd = undef;
|
|
}
|
|
|
|
# Show usage
|
|
pod2usage(0) if ($opt{'help'} || !$cmd);
|
|
|
|
# Assign defaults
|
|
$opt{'config'} ||= "/etc/webmin";
|
|
$opt{'cmd'} = $cmd;
|
|
|
|
# Catch kill signal
|
|
my $sigkill = sub {
|
|
system("stty echo");
|
|
print "\n^C";
|
|
print "\n";
|
|
exit 1;
|
|
};
|
|
$SIG{INT} = \&$sigkill;
|
|
|
|
# Run change password command
|
|
run(\%opt);
|
|
|
|
return 0;
|
|
}
|
|
exit main(\@ARGV) if !caller(0);
|
|
|
|
sub run
|
|
{
|
|
my ($o) = @_;
|
|
my $conf_check = sub {
|
|
my ($configs) = @_;
|
|
foreach my $config (@{$configs}) {
|
|
if (!-r $config) {
|
|
say BRIGHT_RED, "Error: ", RESET, "Failed to read Webmin essential config file: ", BRIGHT_YELLOW, $config,
|
|
RESET, " doesn't exist";
|
|
exit 1;
|
|
}
|
|
}
|
|
};
|
|
root($o->{'config'}, \&$conf_check);
|
|
my $service = ($o->{'config'} =~ /usermin/ ? 'usermin' : 'webmin');
|
|
my $systemctlcmd = &has_command('systemctl');
|
|
$systemctlcmd =~ s/\s+$//;
|
|
if ($o->{'cmd'} =~ /^(start|stop|restart|reload)$/) {
|
|
my $rs = system("$o->{'config'}/$o->{'cmd'} $service");
|
|
exit $rs;
|
|
}
|
|
if ($o->{'cmd'} =~ /^(kill)$/) {
|
|
my $rs;
|
|
if (-x $systemctlcmd) {
|
|
$rs = system("$systemctlcmd stop $service");
|
|
$rs = system("$systemctlcmd kill -s SIGTERM $service");
|
|
}
|
|
$rs = system("$o->{'config'}/.stop-init --kill >/dev/null 2>&1 $service");
|
|
exit $rs;
|
|
}
|
|
if ($o->{'cmd'} =~ /^(force-restart)$/) {
|
|
my $rs = system("$o->{'config'}/restart-by-force-kill $service");
|
|
exit $rs;
|
|
}
|
|
if ($o->{'cmd'} =~ /^(status)$/) {
|
|
my $rs;
|
|
if (-x $systemctlcmd) {
|
|
$rs = system("$systemctlcmd status $service");
|
|
} else {
|
|
$rs = system("service $service status");
|
|
}
|
|
exit $rs;
|
|
}
|
|
if ($o->{'cmd'} =~ /^(stats)$/) {
|
|
my $rs = 0;
|
|
if (-x $systemctlcmd) {
|
|
my $format_bytes = sub {
|
|
my $bytes = shift;
|
|
return "0" unless defined $bytes && $bytes =~ /^\d+$/;
|
|
|
|
my $mb = $bytes / 1048576;
|
|
my $gb = $mb / 1024;
|
|
|
|
if ($gb >= 1) {
|
|
return sprintf("%.2f GB", $gb);
|
|
} elsif ($mb >= 1) {
|
|
return sprintf("%.2f MB", $mb);
|
|
} else {
|
|
return sprintf("%.2f KB", $bytes / 1024);
|
|
}
|
|
};
|
|
|
|
# Check if service is running first
|
|
my $is_active_cmd = qq{systemctl is-active "$service" 2>/dev/null};
|
|
my $is_active = `$is_active_cmd`;
|
|
$rs = $? >> 8;
|
|
chomp($is_active);
|
|
|
|
if ($rs != 0 || $is_active ne 'active') {
|
|
print "Service '$service' is not running (status: $is_active)\n";
|
|
return 2;
|
|
}
|
|
|
|
# Get main pid
|
|
my $main_pid_cmd = qq{systemctl show -p MainPID --value "$service"};
|
|
my $main_pid = `$main_pid_cmd`;
|
|
$rs = $? >> 8;
|
|
return $rs if $rs != 0;
|
|
chomp($main_pid);
|
|
|
|
if (!$main_pid || $main_pid eq '0') {
|
|
print "Service '$service' has no main PID\n";
|
|
return;
|
|
}
|
|
|
|
# Get process list
|
|
my $cmd = qq{
|
|
CG=\$(systemctl show -p ControlGroup --value "$service");
|
|
P=\$({ cat /sys/fs/cgroup"\$CG"/cgroup.procs; systemctl show -p MainPID --value "$service"; } | sort -u);
|
|
COLUMNS=10000 ps --cols 10000 -ww --no-headers -o pid=,ppid=,rss=,pmem=,pcpu=,args= --sort=-rss -p \$P |
|
|
awk 'function h(k){m=k/1024;g=m/1024;return g>=1?sprintf("%.2fG",g):sprintf("%.1fM",m)} BEGIN{printf "%6s %6s %9s %6s %6s %-s\\n","PID","PPID","RSS_KiB","%MEM","%CPU","CMD (RSS_human)"} {cmd=substr(\$0,index(\$0,\$6)); printf "%6s %6s %9s %6s %6s %s (%s)\\n",\$1,\$2,\$3,\$4,\$5,cmd,h(\$3)}'
|
|
};
|
|
my $out = `$cmd`;
|
|
$rs = $? >> 8;
|
|
return $rs if $rs != 0;
|
|
|
|
# Extract pids from the output
|
|
my @all_pids;
|
|
foreach my $line (split(/\n/, $out)) {
|
|
if ($line =~ /^\s*(\d+)\s+/) {
|
|
push @all_pids, $1;
|
|
}
|
|
}
|
|
|
|
if (!@all_pids) {
|
|
print "No processes found for service '$service'\n";
|
|
return 3;
|
|
}
|
|
|
|
# Reorder with main pid first, then rest sorted by size
|
|
my @pids;
|
|
if ($main_pid && $main_pid ne '' && grep { $_ eq $main_pid } @all_pids) {
|
|
push @pids, $main_pid;
|
|
push @pids, grep { $_ ne $main_pid } @all_pids;
|
|
} else {
|
|
@pids = @all_pids;
|
|
}
|
|
|
|
# Print the table with main pid marked
|
|
foreach my $line (split(/\n/, $out)) {
|
|
if ($line =~ /^\s*$main_pid\s+/ && $main_pid) {
|
|
chomp($line);
|
|
print "$line [MAIN]\n";
|
|
} else {
|
|
print "$line\n";
|
|
}
|
|
}
|
|
|
|
# Check if lsof is available
|
|
my $has_lsof = has_command('lsof');
|
|
|
|
# Get detailed info for each pid
|
|
foreach my $pid (@pids) {
|
|
my $is_main = ($pid eq $main_pid) ? " [MAIN PROCESS]" : "";
|
|
|
|
# Check if process still exists
|
|
unless (-d "/proc/$pid") {
|
|
print "\n\nProcess $pid no longer exists, skipping...\n";
|
|
next;
|
|
}
|
|
|
|
print "\n";
|
|
print "╔" . "═"x78 . "╗\n";
|
|
print "║" . sprintf("%-78s", " DETAILED ANALYSIS FOR PID $pid$is_main") . "║\n";
|
|
print "╚" . "═"x78 . "╝\n";
|
|
|
|
# Working directory and binary
|
|
print "\n┌─ WORKING DIRECTORY & BINARY " . "─"x49 . "\n";
|
|
my $cwd = `readlink /proc/$pid/cwd 2>/dev/null`;
|
|
chomp($cwd);
|
|
print "CWD: $cwd\n" if $cwd;
|
|
|
|
my $exe = `readlink /proc/$pid/exe 2>/dev/null`;
|
|
chomp($exe);
|
|
print "EXE: $exe\n" if $exe;
|
|
|
|
my $root = `readlink /proc/$pid/root 2>/dev/null`;
|
|
chomp($root);
|
|
print "ROOT: $root\n" if $root && $root ne '/';
|
|
|
|
# Environment variables
|
|
print "\n┌─ ENVIRONMENT VARIABLES " . "─"x54 . "\n";
|
|
my $env = `cat /proc/$pid/environ 2>/dev/null | tr '\\0' '\\n' | grep -E '^(PATH|HOME|USER|LANG|TZ|LD_|PYTHON|JAVA|NODE|PORT|HOST|DB_|API_)' | sort`;
|
|
if ($env) {
|
|
print $env;
|
|
} else {
|
|
print "Unable to read environment\n";
|
|
}
|
|
|
|
# Basic process info
|
|
print "\n┌─ PROCESS INFO " . "─"x63 . "\n";
|
|
my $ps_info = `ps -p $pid -o user=,pid=,ppid=,pri=,ni=,vsz=,rss=,stat=,start=,time=,cmd= 2>/dev/null`;
|
|
if ($ps_info) {
|
|
print "USER PID PPID PRI NI VSZ RSS STAT START TIME CMD\n";
|
|
print $ps_info;
|
|
} else {
|
|
print "Process no longer exists\n";
|
|
next;
|
|
}
|
|
|
|
# Process tree
|
|
print "\n┌─ PROCESS TREE " . "─"x63 . "\n";
|
|
my $pstree = `pstree -p -a $pid 2>/dev/null`;
|
|
if ($pstree) {
|
|
print $pstree;
|
|
} else {
|
|
print "pstree not available\n";
|
|
}
|
|
|
|
# Memory and status
|
|
print "\n┌─ MEMORY & STATUS " . "─"x60 . "\n";
|
|
my $status = `grep -E 'VmPeak|VmSize|VmRSS|VmSwap|RssAnon|RssFile|Threads|voluntary_ctxt|nonvoluntary_ctxt' /proc/$pid/status 2>/dev/null`;
|
|
print $status || "N/A\n";
|
|
|
|
# Open file descriptors
|
|
print "\n┌─ FILE DESCRIPTORS " . "─"x59 . "\n";
|
|
my $fd_count = `ls -1 /proc/$pid/fd 2>/dev/null | wc -l`;
|
|
chomp($fd_count);
|
|
print "Total Open FDs: $fd_count\n";
|
|
|
|
if ($has_lsof) {
|
|
print "\nFile Descriptor Types:\n";
|
|
my $fd_types = `lsof +c 0 -p $pid 2>/dev/null | awk 'NR>1 {print \$5}' | sort | uniq -c | sort -rn`;
|
|
print $fd_types || "Unable to get FD types\n";
|
|
|
|
print "\nDetailed File Descriptors:\n";
|
|
my $all_fds = `lsof +c 0 -p $pid 2>/dev/null`;
|
|
$all_fds =~ s/^/ /mg;
|
|
print $all_fds || "No files open\n";
|
|
} else {
|
|
print "\n(Install lsof for detailed file descriptor analysis)\n";
|
|
print "\nOpen FD Sample:\n";
|
|
my $fd_sample = `ls -la /proc/$pid/fd 2>/dev/null | head -15`;
|
|
print $fd_sample;
|
|
}
|
|
|
|
# Network Connections
|
|
print "\n┌─ NETWORK CONNECTIONS " . "─"x56 . "\n";
|
|
|
|
# tcp connections with details
|
|
my $tcp_detailed = `ss -tnp -o 2>/dev/null | grep 'pid=$pid'`;
|
|
my $tcp_count = `echo "$tcp_detailed" | grep -c 'pid=$pid'` || 0;
|
|
chomp($tcp_count);
|
|
print "Active TCP Connections: $tcp_count\n";
|
|
|
|
if ($tcp_count > 0) {
|
|
print "\nTCP Connections (with timers and queues):\n";
|
|
print $tcp_detailed;
|
|
|
|
print "\nConnection State Summary:\n";
|
|
my $state_summary = `ss -tnp 2>/dev/null | grep 'pid=$pid' | awk '{print \$1}' | sort | uniq -c | sort -rn`;
|
|
print $state_summary;
|
|
|
|
print "\nLocal Ports in Use:\n";
|
|
my $local_ports = `ss -tnp 2>/dev/null | grep 'pid=$pid' | awk '{split(\$4,a,":"); print a[length(a)]}' | sort -n | uniq -c`;
|
|
print $local_ports || "None\n";
|
|
|
|
print "\nRemote Endpoints:\n";
|
|
my $remote_ips = `ss -tnp 2>/dev/null | grep 'pid=$pid' | awk '{print \$5}' | cut -d: -f1 | sort | uniq -c | sort -rn`;
|
|
print $remote_ips || "None\n";
|
|
}
|
|
|
|
# tcp listening
|
|
my $tcp_listen = `ss -tlnp 2>/dev/null | grep 'pid=$pid'`;
|
|
if ($tcp_listen) {
|
|
print "\nTCP Listening Sockets:\n";
|
|
print $tcp_listen;
|
|
}
|
|
|
|
# udp connections
|
|
my $udp_count = `ss -unp 2>/dev/null | grep -c 'pid=$pid'`;
|
|
chomp($udp_count);
|
|
if ($udp_count > 0) {
|
|
print "\nUDP Connections: $udp_count\n";
|
|
my $udp_conns = `ss -unp 2>/dev/null | grep 'pid=$pid'`;
|
|
print $udp_conns;
|
|
}
|
|
|
|
# udp listening
|
|
my $udp_listen = `ss -ulnp 2>/dev/null | grep 'pid=$pid'`;
|
|
if ($udp_listen) {
|
|
print "\nUDP Listening Sockets:\n";
|
|
print $udp_listen;
|
|
}
|
|
|
|
# unix sockets
|
|
my $unix_sockets = `ss -xp 2>/dev/null | grep 'pid=$pid' | wc -l`;
|
|
chomp($unix_sockets);
|
|
if ($unix_sockets > 0) {
|
|
print "\nUnix Domain Sockets: $unix_sockets\n";
|
|
}
|
|
|
|
# I/O Statistics
|
|
print "\n┌─ I/O STATISTICS " . "─"x61 . "\n";
|
|
my $io = `cat /proc/$pid/io 2>/dev/null`;
|
|
if ($io) {
|
|
print $io;
|
|
# Parse and show human-readable
|
|
my ($read_bytes, $write_bytes);
|
|
if ($io =~ /read_bytes:\s*(\d+)/) {
|
|
$read_bytes = $1;
|
|
}
|
|
if ($io =~ /write_bytes:\s*(\d+)/) {
|
|
$write_bytes = $1;
|
|
}
|
|
if (defined $read_bytes && defined $write_bytes) {
|
|
print "\nRead: " . $format_bytes->($read_bytes) .
|
|
", Write: " . $format_bytes->($write_bytes) . "\n";
|
|
}
|
|
} else {
|
|
print "N/A\n";
|
|
}
|
|
|
|
# Resource Limits
|
|
print "\n┌─ RESOURCE LIMITS " . "─"x60 . "\n";
|
|
my $limits = `grep -E 'Max open files|Max processes|Max locked memory|Max address space|Max cpu time' /proc/$pid/limits 2>/dev/null`;
|
|
print $limits || "N/A\n";
|
|
|
|
# Cgroup limits
|
|
my $cg_path = `cat /proc/$pid/cgroup 2>/dev/null | grep '^0::' | cut -d: -f3`;
|
|
chomp($cg_path);
|
|
my $cgroup_output = "";
|
|
if ($cg_path) {
|
|
my $mem_limit = `cat /sys/fs/cgroup$cg_path/memory.max 2>/dev/null`;
|
|
my $mem_current = `cat /sys/fs/cgroup$cg_path/memory.current 2>/dev/null`;
|
|
my $cpu_max = `cat /sys/fs/cgroup$cg_path/cpu.max 2>/dev/null`;
|
|
|
|
chomp($mem_limit, $mem_current, $cpu_max);
|
|
|
|
if ($mem_limit && $mem_limit ne 'max') {
|
|
$cgroup_output .= "Memory Limit: " . $format_bytes->(int($mem_limit)) . "\n";
|
|
$cgroup_output .= "Memory Current: " . $format_bytes->(int($mem_current)) . "\n" if $mem_current;
|
|
if ($mem_current) {
|
|
my $pct = sprintf("%.1f", ($mem_current / $mem_limit) * 100);
|
|
$cgroup_output .= "Memory Usage: $pct%\n";
|
|
}
|
|
}
|
|
if ($cpu_max && $cpu_max ne 'max') {
|
|
$cgroup_output .= "CPU Quota: $cpu_max\n";
|
|
}
|
|
}
|
|
if ($cgroup_output) {
|
|
print "\n┌─ CGROUP LIMITS " . "─"x62 . "\n";
|
|
print $cgroup_output;
|
|
}
|
|
|
|
# CPU & Scheduling
|
|
print "\n┌─ CPU & SCHEDULING " . "─"x59 . "\n";
|
|
my $sched = `grep -E 'se.sum_exec_runtime|nr_switches|nr_voluntary_switches|nr_involuntary_switches' /proc/$pid/sched 2>/dev/null | head -4`;
|
|
if ($sched) {
|
|
print $sched;
|
|
}
|
|
my $cpuset = `cat /proc/$pid/cpuset 2>/dev/null`;
|
|
chomp($cpuset);
|
|
print "CPUset: $cpuset\n" if $cpuset;
|
|
|
|
# Signal handlers
|
|
print "\n┌─ SIGNAL HANDLERS " . "─"x60 . "\n";
|
|
my $signals = `cat /proc/$pid/status 2>/dev/null | grep -E '^Sig(Cgt|Ign|Blk):'`;
|
|
if ($signals) {
|
|
print $signals;
|
|
|
|
# Decode signal masks
|
|
my %signal_names = (
|
|
1 => 'SIGHUP', 2 => 'SIGINT', 3 => 'SIGQUIT',
|
|
4 => 'SIGILL', 5 => 'SIGTRAP', 6 => 'SIGABRT',
|
|
7 => 'SIGBUS', 8 => 'SIGFPE', 9 => 'SIGKILL',
|
|
10 => 'SIGUSR1', 11 => 'SIGSEGV', 12 => 'SIGUSR2',
|
|
13 => 'SIGPIPE', 14 => 'SIGALRM', 15 => 'SIGTERM',
|
|
16 => 'SIGSTKFLT', 17 => 'SIGCHLD', 18 => 'SIGCONT',
|
|
19 => 'SIGSTOP', 20 => 'SIGTSTP', 21 => 'SIGTTIN',
|
|
22 => 'SIGTTOU', 23 => 'SIGURG', 24 => 'SIGXCPU',
|
|
25 => 'SIGXFSZ', 26 => 'SIGVTALRM', 27 => 'SIGPROF',
|
|
28 => 'SIGWINCH', 29 => 'SIGIO', 30 => 'SIGPWR',
|
|
31 => 'SIGSYS'
|
|
);
|
|
|
|
my $decode_sigmask = sub {
|
|
my ($hex_mask, $names_ref) = @_;
|
|
return "none" if $hex_mask eq '0000000000000000';
|
|
|
|
# Convert hex to decimal
|
|
my $mask = hex($hex_mask);
|
|
my @signals;
|
|
|
|
# Check each bit
|
|
for (my $i = 1; $i <= 31; $i++) {
|
|
if ($mask & (1 << ($i - 1))) {
|
|
push @signals, "$names_ref->{$i}($i)";
|
|
}
|
|
}
|
|
|
|
return @signals ? join(", ", @signals) : "none";
|
|
};
|
|
|
|
print "\nDecoded:\n";
|
|
if ($signals =~ /SigBlk:\s*([0-9a-f]+)/i) {
|
|
print " Blocked: " .
|
|
$decode_sigmask->($1, \%signal_names) . "\n";
|
|
}
|
|
if ($signals =~ /SigIgn:\s*([0-9a-f]+)/i) {
|
|
print " Ignored: " .
|
|
$decode_sigmask->($1, \%signal_names) . "\n";
|
|
}
|
|
if ($signals =~ /SigCgt:\s*([0-9a-f]+)/i) {
|
|
print " Caught: " .
|
|
$decode_sigmask->($1, \%signal_names) . "\n";
|
|
}
|
|
} else {
|
|
print "N/A\n";
|
|
}
|
|
|
|
# Memory maps sum
|
|
print "\n┌─ MEMORY MAPS (top 20 by size) " . "─"x47 . "\n";
|
|
my $maps = `awk '
|
|
/^[0-9a-f]+-[0-9a-f]+/ {hdr=\$0}
|
|
/^Size:/ {size=\$2}
|
|
/^Rss:/ {rss=\$2}
|
|
/^VmFlags:/ { if (rss>0) {print rss"\\t"size"\\t"hdr} rss=0; size=0 }
|
|
' /proc/$pid/smaps 2>/dev/null | sort -rn | head -20`;
|
|
|
|
if ($maps) {
|
|
print "RSS(MB)\tSize(MB)\tMapping\n";
|
|
foreach my $map_line (split(/\n/, $maps)) {
|
|
if ($map_line =~ /^(\d+)\s+(\d+)\s+(.+)$/) {
|
|
my $rss_mb = sprintf("%.2f", $1 / 1024);
|
|
my $size_mb = sprintf("%.2f", $2 / 1024);
|
|
print "$rss_mb\t$size_mb\t\t$3\n";
|
|
}
|
|
}
|
|
} else {
|
|
print "Unable to read memory maps\n";
|
|
}
|
|
|
|
# Recent logs
|
|
print "\n┌─ RECENT LOGS (last 20 lines) " . "─"x48 . "\n";
|
|
my $logs = `journalctl _PID=$pid -b -n 20 --no-pager -o short-precise 2>/dev/null`;
|
|
if ($logs && $logs !~ /^-- No entries --/) {
|
|
print $logs;
|
|
} else {
|
|
print "No recent logs found for this PID in current boot\n";
|
|
}
|
|
|
|
print "\n" . "─"x79 . "\n";
|
|
}
|
|
|
|
} else {
|
|
print "Stats command is only available on systemd based systems.\n";
|
|
$rs = 1;
|
|
}
|
|
exit $rs;
|
|
}
|
|
exit 0;
|
|
}
|
|
|
|
sub root
|
|
{
|
|
my ($config, $conf_check) = @_;
|
|
my $mconf = "$config/miniserv.conf";
|
|
$conf_check->([$mconf]);
|
|
open(my $CONF, "<", $mconf);
|
|
my $root;
|
|
while (<$CONF>) {
|
|
if (/^root=(.*)/) {
|
|
$root = $1;
|
|
}
|
|
}
|
|
close($CONF);
|
|
|
|
# Does the Webmin root exist?
|
|
if ($root) {
|
|
die BRIGHT_RED, "Error: ", BRIGHT_YELLOW, $root, RESET, " is not a directory\n" unless (-d $root);
|
|
} else {
|
|
|
|
# Try to guess where Webmin lives, since config file didn't know.
|
|
die BRIGHT_RED, "Error: ", RESET, "Unable to determine Webmin installation directory\n";
|
|
}
|
|
|
|
return $root;
|
|
}
|
|
|
|
1;
|
|
|
|
=pod
|
|
|
|
=head1 NAME
|
|
|
|
server
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This program allows you to control Webmin web-server
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
webmin server [command]
|
|
webmin [command]
|
|
|
|
=head1 OPTIONS
|
|
|
|
=over
|
|
|
|
=item --help, -h
|
|
|
|
Print this usage summary and exit.
|
|
|
|
Examples of usage:
|
|
- webmin server status
|
|
- webmin server restart
|
|
- webmin server --config /usr/local/etc/webmin --command start
|
|
- webmin status
|
|
- webmin restart
|
|
|
|
=item --config, -c
|
|
|
|
Specify the full path to the Webmin configuration directory. Defaults to C</etc/webmin>
|
|
|
|
=item --command, -x
|
|
|
|
Available commands:
|
|
- status
|
|
- start
|
|
- stop
|
|
- restart
|
|
- force-restart
|
|
- reload
|
|
- kill
|
|
|
|
=back
|
|
|
|
=head1 LICENSE AND COPYRIGHT
|
|
|
|
Copyright 2018 Jamie Cameron <jcameron@webmin.com>
|
|
Joe Cooper <joe@virtualmin.com>
|
|
Ilia Ross <ilia@virtualmin.com>
|
|
|