#!/usr/bin/perl -w ############################################################ # $Id: psmon-config,v 1.5 2005/05/06 12:50:20 nicolaw Exp $ # psmon.conf - Example psmon Configuration File # Copyright: (c)2002,2003,2004,2005 Nicola Worthington. All rights reserved. ############################################################ # This file is part of psmon. # # psmon is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # psmon is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with psmon; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ############################################################ use strict; use warnings; use English; use Term::ReadLine (); use Proc::ProcessTable (); use Config::General (); use Text::Wrap qw(wrap); use POSIX (); use vars qw($VERSION $SELF); $OUTPUT_AUTOFLUSH = 1; ($SELF = $PROGRAM_NAME) =~ s|^.*/||; $VERSION = sprintf('%d.%02d', q$Revision: 1.5 $ =~ /(\d+)/g); $Text::Wrap::columns = 77; # Create Term::Realine object my $term = new Term::ReadLine $SELF; # Start header of config output my $str = sprintf("# Generated by %s, by %s@%s\n", $SELF, (getpwuid($EFFECTIVE_USER_ID))[0], (POSIX::uname())[1] ); $str .= sprintf("# Created at %s\n\n",scalar localtime); $str .= "# Please read through your configuration file before using it in production!\n"; $str .= "Disabled True\n\n"; # Print some information print <{$directive}->[1]) =~ s/\s\s+/ /g; printf("\n%-13s%s\n",'Directive:',$directive); print wrap('Description: ',' ',"$help\n"); printf("%-13s%s\n",'Defaults to:',$directives->{$directive}->[0]) if $directives->{$directive}->[0]; my $prompt = "Specify $directive? [".($directives->{$directive}->[0] ? 'y/N' : 'Y/n')."]: "; $_ = $term->readline($prompt); redo unless /^\s*(y|n)?\s*$/i; #$str .= sprintf("# %-13s%s\n",'Directive:',$directive); #$str .= wrap('# Description: ','# ',"$help\n"); $str .= wrap('# ','# ',"$help\n"); $str .= sprintf("# %-13s%s\n",'Defaults to:',$directives->{$directive}->[0]) if $directives->{$directive}->[0]; if ($_ =~ /y/ || (!length($directives->{$directive}->[0]) && $_ !~ /n/)) { $str .= sprintf("%s %s\n\n",$directive,get_value('Value?: ')); } else { $str .= "$directive $directives->{$directive}->[0]\n\n"; } } # Print some information about process scope directives print < 1 ); for my $process (@{$p->table}) { unless ($process->{ttynum} || $process->{ttydev}) { my ($executable) = $process->{cmndline} =~ /^\s*(\S+)/; if (-f $executable) { my $instances = 1; if (exists $daemons->{$process->{cmndline}}) { $instances += $daemons->{$process->{cmndline}}->{instances}; } $daemons->{$process->{cmndline}} = $process; $daemons->{$process->{cmndline}}->{instances} = $instances; } } } print "done\n"; # Get process scope directive information $directives = get_process_scope_directives(); for my $process (values %{$daemons}) { print "\n"; print "Process name: $process->{fname}\n"; print "Command line: $process->{cmndline}\n"; print "Working UID : $process->{uid}\n"; print "Instances : $process->{instances}\n"; $_ = $term->readline("Would you like to monitor '$process->{fname}'? [y/N]: "); redo unless /^\s*(y|n)?\s*$/i; next unless /y/i; $str .= "# Process $process->{fname} added by psmon-config\n"; $str .= "{fname}>\n"; for my $directive (sort keys(%{$directives})) { (my $help = $directives->{$directive}->[1]) =~ s/\s\s+/ /g; printf("\n%-13s%s\n",'Directive:',$directive); print wrap('Description: ',' ',"$help\n"); printf("%-13s%s\n",'Defaults to:',$directives->{$directive}->[0]) if $directives->{$directive}->[0]; my $prompt = "Specify $directive? [".($directives->{$directive}->[0] ? 'y/N' : 'Y/n')."]: "; $_ = $term->readline($prompt); redo unless /^\s*(y|n)?\s*$/i; if ($_ =~ /y/ || (!length($directives->{$directive}->[0]) && $_ !~ /n/)) { $str .= sprintf("\t%s %s\n",$directive,get_value('Value?: ')); } } $str .= "\n\n"; } # Add this again for those who just do not pay attention $str .= "# You need to remove BOTH of these 'Disabled' directives before using this\n"; $str .= "# configuration file. Please make sure you have read and understood everything\n"; $str .= "# in this file before using it in a live production environment!\n"; $str .= "Disabled True\n"; # Print the configuration open(FH,">psmon-config.conf") || die "Unable to open file handle FH for file 'psmon-config.conf': $!"; print FH <<__TEXT__; ############################################################################## # # Please read through this configuration file in detail. It will NOT function # right out of the box without any modifications. This is for good reason, # since I don't want to receive snotty emails from you or your system # administrator, being accused of killing your server or workstation. # # There is further documentation supplied with the psmon software. I suggest # that you read it thoroughly. # # - The author, Nicola Worthington # ############################################################################## __TEXT__ print FH "$str\n"; print FH <<__TEXT__; # The scope is commented out by default. It should be used with # *EXTREME* care. If you do decide to use it, may I suggest that you run psmon # in 'DryRun' mode by adding the 'DryRun' directive in this configuration # file. READ THE DOCUMENTATION THOROUGHLY BEFORE ENABLING THIS FEATURE!!! # # PctCpu 80 # PctMem 50 # # I have included a set of commonly required processes. They are all vital # services which must be running on all of my workstations and servers. It's # a pretty good guess you'll want them to always be running too. # Secure Shell Daemon # # LogLevel LOG_CRITICAL # SpawnCmd /sbin/service sshd restart # PidFile /var/run/sshd.pid # # Instances 30 # # PctCPU 90 # # Cron Daemon # # spawncmd /sbin/service crond restart # pidfile /var/run/crond.pid # # System Logger Daemon # # spawncmd /sbin/service syslog restart # pidfile /var/run/syslogd.pid # # Internet Super Daemon # # spawncmd /sbin/service xinetd restart # pidfile /var/run/xinetd.pid # # Remote WHO Daemon # # # rwhod is *EVIL*! There is almost never any real # # reason why you would ever want to run such pants! # killcmd /sbin/service rwhod stop # ttl 1 # # BIND DNS Daemon # # spawncmd /sbin/service named start # pidfile /var/run/named.pid # # Exim SMTP Mail Daemon # # spawncmd /sbin/service exim restart # pidfile /var/run/exim.pid # # instamces 30 # # pctcpu 90 # # Sendmail SMTP Mail Daemon # # spawncmd /sbin/service sendmail start # pidfile /var/run/sendmail.pid # # Samba SMB File Sharing Daemon # # spawncmd /sbin/service smbd restart # pidfile /var/run/samba/smbd.pid # # # spawncmd /sbin/service smbd restart # pidfile /var/run/samba/nmbd.pid # # Quallcomm QPopper POP3 Daemon # # spawncmd /sbin/service popper restart # pidfile /var/run/popper.pid # # Apache Group HTTP Daemon # # spawncmd /sbin/service httpd restart # pidfile /var/run/httpd.pid # # instances 200 # # pctcpu 80 # # MySQL Database # # spawncmd /sbin/service mysqld restart # killcmd /sbin/service mysqld stop # pidfile /var/run/mysqld/mysqld.pid # # pctcpu 90 # # pctmem 60 # # NTP Time Daemon # # spawncmd /sbin/service ntpd restart # # SMNP Daemon # # # # ProFTPD FTP Daemon # # spawncmd /sbin/service proftpd restart # pidfile /var/run/proftpd.pid # # These are processes which run frequently on my machines, but I have had # experience of either running for too long (for whatever readon), or spawning # too many copies. # # The following is a quick table for your ease of reference: # Seconds Minutes Hours Days Weeks # 60 1 # 3600 60 1 # 43200 720 12 0.5 # 86400 1440 24 1 # 604800 10080 168 7 1 # Kill excessive of slothenly rsync processes # # ttl 43200 # instances 5 # # Kill excessive of slothenly updatedb processes # # ttl 43200 # instances 2 # # Kill excessive of slothenly find processes # # ttl 86400 # instances 30 # ### END OF FILE __TEXT__ close(FH) || warn "Unable to close file handle FH for file 'psmon-config.conf': $!"; print "\n\n"; print "Configuration file written to ./psmon-config.conf\n\n"; # Subroutines sub get_value { my $prompt = shift || 'Value?: '; my $retval = ''; until ($retval) { $_ = $term->readline($prompt); chomp; redo unless /\S+/; confirm: { print "You entered: $_\n"; my $confirm = $term->readline('Is this correct? [Y/n]: '); redo confirm unless $confirm =~ /^\s*(y|n)?\s*$/i; $retval = $_ if $confirm !~ /n/i; } } return $retval; } sub read_config { my $config_file = shift; unless (-f $config_file && -r $config_file) { return undef; } my $conf = new Config::General( -ConfigFile => $config_file, -LowerCaseNames => 1, -UseApacheInclude => 1, -IncludeRelative => 1, -MergeDuplicateBlocks => 1, -AllowMultiOptions => 1, -MergeDuplicateOptions => 1, -AutoTrue => 1, ); return $conf->getall; } sub get_process_scope_directives { my $directives = { SpawnCmd => [ ('', 'Defines the full command line to be executed in order to respawn a dead process.') ], KillCmd => [ ('*Undefined*', 'Defines the full command line to be executed in order to gracefully shutdown or kill a rogue process. If the command returns a boolean true exit status then it is assumed that the command failed to execute sucessfully. If no KillCmd is specified or the command fails, the process will be killed by sending a SIGKILL signal with the standard kill() function.') ], AdminEmail => [ ('*Undefined*', 'Defines the email address where notification emails should be sent to for this process scope. An AdminEmail entry in the process scope will take priority over the global AdminEmail declaration. All AdminEmail values in the configuration file will be ignored and overridden if AdminEmail is specified as a command line option.') ], PIDFile => [ ('', 'Defines the full path and filename of a file created by a process which contain it\'s main parent process ID.') ], TTL => [ ('*Undefined*', 'Defines a maximum time to live (in seconds) of a process. The process will be killed once it has been running longer than this value, and it\'s process ID isn\'t contained in the defined pidfile.') ], PctCpu => [ ('*Undefined*', 'Defines a maximum allowable percentage of CPU time a process may use. The process will be killed once it\'s CPU usage exceeds this threshold and it\'s process ID isn\'t contained in the defined pidfile.') ], PctMem => [ ('*Undefined*', 'Defines a maximum allowable percentage of total system memory a process may use. The process will be killed once it\'s memory usage exceeds this threshold and it\'s process ID isn\'t contained in the defined pidfile.') ], Instances => [ ('*Undefined*', 'Defines a maximum number of instances of a process which may run. The process will be killed once there are more than this number of occurances running, and it\'s process ID isn\'t contained in the defined pid file.') ], NoEmailOnKill => [ ('False', 'Accepts a boolean value of True or False. Surpresses process killing notification emails for this process scope.') ], NoEmailOnSpawn => [ ('False', 'Accepts a boolean value of True or False. Surpresses process spawning notification emails for this process scope.') ], NoEmail => [ ('False', 'Accepts a boolean value of True or False. Surpresses all notification emails for this process scope.') ], }; return $directives; } sub get_directives { my $directives = { Facility => [ ('LOG_DAEMON', 'Defines which syslog facility to log to. Refer to your syslogd and/or operating system documentation for a list of valid facilities.') ], LogLevel => [ ('LOG_NOTICE', 'Defines the loglevel priority that notifications to syslog will be marked as. Refer to your operating system\'s kernel.h documentation for a list of valid priorities.') ], AdminEmail => [ ('root@localhost', 'Defines the email address where notification emails should be sent to. This may be also be used in a Process scope which will take priority over a global declaration. All AdminEmail entries in the configuration file will be overridden if it is specified on the command line as an option.') ], NotifyEmailFrom => [ (sprintf('%s@%s',(getpwuid($EFFECTIVE_USER_ID))[0],(POSIX::uname())[1]), 'Defines the email address that notification email should be addresses from.') ], SMTPHost => [ ('localhost', 'Defines the IP address or hostname of the SMTP server to used to send email notifications.') ], DefaultEmailMethod => [ ('sendmail', 'Defines which method should be used by default to try and send notification emails. Legal values are "SMTP" or "sendmail".') ], SMTPTimeout => [ ('20', 'Defines the timeout in seconds to be used during SMTP connections.') ], SendmailCMD => [ ('', 'Defines the sendmail command to use to send notification emails if there is a failure with the SMTP connection to the host defined by SMTPHost. PSMon will attempt to locate the sendmail command for you by looking in common locations.') ], Frequency => [ ('60', 'Defines the frequency (in seconds) of process table queries.') ], LastSafePID => [ ('100', 'When defined, psmon will never attempt to kill a process ID which is numerically less than or equal to the value defined by lastsafepid. It should be noted that psmon will never attempt to kill itself, or a process ID less than or equal to 1.') ], NeverKillPID => [ ('1', 'Accepts a space delimited list of PIDs which will never be killed.') ], NeverKillProcessName => [ ('kswapd kupdated mdrecoveryd pageout sched init', 'Accepts a space delimited list of process names which will never be killed. ') ], ProtectSafePIDsQuietly => [ ('Off', 'Accepts a boolean value of On or Off. Surpresses all notifications of preserved process IDs when used in conjunction with the lastsafepid directive.') ], }; return $directives; }