Prowl Server Checker [ENG]

Topp

This script turned out to be quite useful. It's a combination of Python, crontab, MySQL and PHP. PHP is the interface to add/edit/delete servers, Python is the actual script that tries to connect to the server to see if it's up and running. MySQL is used as backend, PHP writes to it, the Python script fetches from it. Crontab schedules it to run every 5 minutes. I developed this script for my work, I needed something that would notify me if one of our servers went down. And it turned out that this solution was perfect. However, bare with me, it took me less than 4-5 hours to make this, hence the quality of the code. However, it works, which is the important part. And the code is under GPLv2 so feel free to fork it and improve the quality of the code. You can download the script here

psc.py source code

1. #!/usr/bin/python
2. import socket
3. import MySQLdb
4. from httplib import HTTPSConnection as https
5. from urllib import urlencode
6. 
7. ###########################################################
8. #
9. # Author:  Caesar "sniker" Ahlenhed
10. # Contact: sniker@se.linux.org
11. # Name:    PSC(Prowlserverchecker, creative, I know)
12. # Description: Reads servers listed in a database
13. #              which is controlled through a webinterface
14. #              and tries to connect to them, if
15. #              that fails, it'll send a prowl
16. #              notification that the server is down
17. # License:     GPLv2
18. # Url:         http://sniker.codebase.nu/
19. # Changed:     Thu Mar 18 00:28:43 CET 2011
20. #
21. #
22. # eth0 will prevail. || irc.eth0.info
23. #
24. ###########################################################
25. 
26. # Start of settings
27. dbip = "127.0.0.1"      # Probably don't need any changing
28. dbusername = "username" # Database username
29. dbpassword = "password" # Database password
30. database = "database"   # Mysql Database
31. # End of settings
32. 
33. # Dont touch anything below this unless you know what you're doing.
34. API_DOMAIN = 'prowl.weks.net'
35. __version__ = "0.3"
36. 
37. def post(apikey, application, event, description, priority):
38.     
39.     headers = {'User-Agent': application + "/%s" % str(__version__),
40.                 'Content-type': "application/x-www-form-urlencoded"}
41. 
42.     h = https(API_DOMAIN)
43. 
44.     data = { 
45.             'apikey': apikey,
46.             'application': application,
47.             'event': event,
48.             'description': description,
49.             'priority': priority
50.     }
51. 
52.     h.request( "POST", "/publicapi/add", headers = headers, body = urlencode(data))
53. 
54. def check(host, port, timeout):
55.     sock = socket.socket()
56.     sock.settimeout(timeout)
57.     
58.     try:
59.         sock.connect((host, port))
60.         return True
61.     except socket.timeout:
62.         return False
63.     except socket.error:
64.         return False
65.     
66.     sock.close()
67. 
68. db = MySQLdb.connect(host=dbip, user=dbusername, passwd=dbpassword, db=database)
69. cursor = db.cursor()
70. 
71. cursor.execute("SELECT * FROM servers WHERE status = 1 ORDER BY ID asc")
72. rows = cursor.fetchall()
73. rowcount = cursor.rowcount
74. for i in range(0, rowcount):
75.     if not check(rows[i][5], rows[i][6], rows[i][8]):
76.         post(rows[i][1], rows[i][2], rows[i][3], rows[i][4], rows[i][7])
77. 

This is the actual script that tries to connect to the server on a specified port. Not much to say really, only that you need to change the database username, password etc. As usual. However, see the timeout option, 3 seconds is OKAY if it's inside the same data center, however, if there's two servers far away from each other, you probably want to increase the timeout to at least 5 seconds, otherwise your phone WILL get raped with false-positives YOU CAN NOW SET THE TIMEOUT FOR EACH SERVER INDIVIDUALLY FROM THE CONTROL PANEL. To make the script run every 5 minutes, we'll want to add a crontab for it, the line below will run this script every 5 min.

*/5 * * * *    /path/to/psc.py >/dev/null 2>&1

You can edit your crontabs by typing

$ crontab -e

When that's done, the python script should be up and running! However, we still need a database. Which is what we'll cover now.

Database template

1. -- 
2. -- Table structure for table `servers`
3. -- 
4. 
5. CREATE TABLE `servers` (
6.   `id` int(11) NOT NULL auto_increment,
7.   `apikey` text collate utf8_unicode_ci NOT NULL,
8.   `application` varchar(255) collate utf8_unicode_ci NOT NULL,
9.   `event` varchar(255) collate utf8_unicode_ci NOT NULL,
10.   `description` text collate utf8_unicode_ci NOT NULL,
11.   `ip4` varchar(255) collate utf8_unicode_ci NOT NULL,
12.   `sport` int(11) NOT NULL,
13.   `priority` varchar(3) collate utf8_unicode_ci NOT NULL,
14.   `timeout` int(11) NOT NULL default '5',
15.   `status` int(11) NOT NULL default '1',
16.   PRIMARY KEY  (`id`)
17. ) ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Import the SQL-file to your database, this can be done in several ways. See the README file at the bottom of this page to see an example.

Webinterface for PSC

This section contains the files for the webinterface. It's 4 PHP files, they are all listed below. It's nothing fancy really, edit dbClass.php with you database settings and upload it somewhere, then you should be good to go.

Index.php source



<?php
    include "lib/dbClass.php";
    
    $db = new dbClass();

    if(isset($_GET["id"]))
        $db->dbQuery("DELETE FROM servers WHERE id = ".mysql_real_escape_string($_GET["id"]));
?>
<html>
<head>
    <title>IPs and server</title>
</head>
<div>
    <ul>
        <li><a href="addserver.php" title="Add a server">Add server</a></li>
    </ul>
    <p><b>Current servers are listed below(<?=$db->dbRows("servers")?>):</b></p>
<?php
    $result $db->dbQuery("SELECT * FROM servers ORDER BY id ASC");

    while($row mysql_fetch_object($result)){
?>
    <ol>
        <li><a href="editserver.php?id=<?=$row->id?>"><?=$row->ip4?></a> [<a href="?id=<?=$row->id?>">D</a>]</li>
        <li>API key: <b><?=$row->apikey?></b></li>
        <li>Description: <b><?=$row->description?></b></li>
        <li>PORT: <b><?=$row->sport?></b></li>
        <li>Priority: <b><?=$row->priority?></b></li>
        <li>Timeout: <b><?=$row->timeout?></b></li>
        <li>Status: <b><?if($row->status == 1) echo "<span style=\"color:green\">Enabled</span>"; else echo "<span style=\"color:red\">Disabled</span>";?></b></li>
    </ol>
<?php
    }
?>
</div>
</html>

editserver.php source



<?php
    include "lib/dbClass.php";

    $db = new dbClass();
    
    if(isset($_POST["edit"])){
        if(!empty($_POST["apikey"]))
            $apikey mysql_real_escape_string($_POST["apikey"]);
        else
            die("<h1>You need to enter an API key!</h1>");

        if(!empty($_POST["desc"]))
            $desc mysql_real_escape_string($_POST["desc"]);
        else
            die("<h1>You need to enter a description!</h1>");

        if(!empty($_POST["ip4"]))
            $ip4 mysql_real_escape_string($_POST["ip4"]);
        else
            die("<h1>You need to enter an IP!</h1>");

        if(!empty($_POST["port"]))
            $port mysql_real_escape_string($_POST["port"]);
        else
            die("<h1>You need to enter a PORT number!</h1>");

        $priority mysql_real_escape_string($_POST["priority"]);

        if($_POST["timeout"] >= 0)
            $timeout mysql_real_escape_string($_POST["timeout"]);
        else
            $timeout 5;

        if($_POST["status"] == 1)
            $status mysql_real_escape_string($_POST["status"]);
        else
            $status 0;

        $db->dbQuery("UPDATE servers SET apikey = '".$apikey."', description = '".$desc."', ip4 = '".$ip4."', sport = '".$port."', priority = '".$priority."', timeout = '".$timeout."', status = '".$status."'  WHERE id = ".mysql_real_escape_string($_GET["id"]));
    }

    if(is_numeric($_GET["id"])){
        $result $db->dbQuery("SELECT * FROM servers WHERE id = ".mysql_real_escape_string($_GET["id"]));
        $row mysql_fetch_object($result);
    }else{
        die("<h1>Didn't get an ID</h1>");
    }
?>

<html>
<head>
<title>Edit <?=$row->ip4?></title>
</head>
<div>
    <label><a href="index.php">cd ~/</a></label>
    <h3>Edit <?=$row->ip4?></h3>
    <form action="?id=<?=$_GET["id"]?>" method="post" name="editsrv">
        <input type="text" name="apikey" value="<?=$row->apikey?>" />
        <input type="text" name="desc" value="<?=$row->description?>" />
        <input type="text" name="ip4" value="<?=$row->ip4?>" />
        <input type="text" name="port" value="<?=$row->sport?>" />
        <input type="text" name="timeout" value="<?=$row->timeout?>" />
        <select name="priority">
            <option value="-2">-2 (Very low)</option>
            <option value="-1">-1 (Moderate)</option>
            <option value="0" selected="yes">0 (Normal)</option>
            <option value="1">1 (High)</option>
            <option value="2">2 (Emergency, bypasses quiet hours)</option>
        </select>
        <input type="checkbox" name="status" value="1" <?if($row->status == 1) echo 'checked'?> /> Enabled?
        <input type="submit" name="edit" value="Edit!" />
    </form>
</div>
</html>

addserver.php source



<?php
    include "lib/dbClass.php";

    $db = new dbClass();
    
    if(isset($_POST["add"])){
        if(!empty($_POST["apikey"]))
            $apikey mysql_real_escape_string($_POST["apikey"]);
        else
            die("<h1>You need to enter an API key!</h1>");

        if(!empty($_POST["desc"]))
            $desc mysql_real_escape_string($_POST["desc"]);
        else
            die("<h1>You need to enter a description!</h1>");

        if(!empty($_POST["ip4"]))
            $ip4 mysql_real_escape_string($_POST["ip4"]);
        else
            die("<h1>You need to enter an IP!</h1>");

        if(!empty($_POST["port"]))
            $port mysql_real_escape_string($_POST["port"]);
        else
            die("<h1>You need to enter a PORT number!</h1>");

        $priority mysql_real_escape_string($_POST["priority"]);
            
        if($_POST["timeout"] >= 0)
            $timeout mysql_real_escape_string($_POST["timeout"]);
        else
            $timeout 5;

        if($_POST["status"] == 1)
            $status mysql_real_escape_string($_POST["status"]);
        else
            $status 0;

        $db->dbQuery("INSERT INTO servers(apikey, application, event, description, ip4, sport, priority, timeout, status) VALUES('".$apikey."','PSC', 'DOWN', '".$desc."', '".$ip4."', '".$port."', '".$priority."', '".$timeout."', '".$status."')");
        header("Location: index.php");
    }
?>

<html>
<head>
    <title>Add new server</title>
</head>
<div>
    <label><a href="index.php">cd ~/</a></label>
    <h3>Add new server!</h3>
    <form action="" method="post" name="addsrv">
        <input type="text" name="apikey" value="API Key" />
        <input type="text" name="desc" value="Description" />
        <input type="text" name="ip4" value="IPv4 to check" />
        <input type="text" name="port" value="Port to check" />
        <input type="text" name="timeout" value="Timeout(in seconds, 5 is default)" />
        <select name="priority">
            <option value="-2">-2 (Very low)</option>
            <option value="-1">-1 (Moderate)</option>
            <option value="0" selected="yes">0 (Normal)</option>
            <option value="1">1 (High)</option>
            <option value="2">2 (Emergency, bypasses quiet hours)</option>
        </select>
        <input type="checkbox" name="status" value="1" checked />Enabled?
        <input type="submit" name="add" value="Add server!" />
    </form>
</div>
</html>

lib/dbClass.php source



<?php
class dbClass{

    var $conn;

    function __construct(){
        $this->conn mysql_connect("localhost""username""password") or die(mysql_error());
        mysql_select_db("database");
    }

    function dbQuery($query){
        $result mysql_query($query) or die("<b>".mysql_error()."</b><br />".htmlentities($query));
        return $result;
    }

    function dbResult($query){
        return mysql_fetch_object($query);
    }

    function dbRows($table){
        $query $this->dbQuery("SELECT COUNT(id) FROM ".$table);
        $result mysql_fetch_array($query) or die(mysql_error());
        return $result[0];
    }

    function dbLastId($table){
        $query $this->dbQuery(" SELECT LAST_INSERT_ID(id) FROM ".$table." ORDER by id DESC LIMIT 1");
        $result mysql_fetch_array($query) or die(mysql_error());
        return $result[0];
    }

    function __destruct(){
        mysql_close($this->conn) or die(mysql_error());
    }
}
?>


Remember to change username, password etc to match the username and password of your database!

Password protection

Since this is version 0.3 I don't have any built in function for handling users and/or usernames and passwords. Currently I use .htaccess and HTTP Basic Authentication. If you'd like to do the same, create a folder in your root directory and name it .htaccess and in that, put the following:

AuthUserFile /path/to/.htpasswd
AuthName "PSC Control Panel"
AuthType Basic
require valid-user

You can generate a .htpasswd file by typing the following:

$ htpasswd -c .htpasswd YOUR_USERNAME

Included README file

###########################################################
#
# Author:  Caesar "sniker" Ahlenhed
# Contact: sniker@se.linux.org
# Name:    PSC(Prowlserverchecker, creative, I know)
# Description: Reads servers listed in a database
#              which is controlled through a webinterface
#              and tries to connect to them, if
#              that fails, it'll send a prowl
#              notification that the server is down
# License:     GPLv2
# Url:         http://sniker.codebase.nu/
# Changed:     Tue Mar 15 01:23:43 CET 2011
#
#
# eth0 will prevail. || irc.eth0.info
#
###########################################################

These scripts make it possible to get alerts when servers you have added goes down,
the script uses Prowl for this, so it's completely useless unless you have an iPhone
with Prowl installed.

What you need:
1) MySQL
2) PHP
3) Webserver
4) Python
5) Python-mysqldb(apt-get install python-mysqldb)

Structure:
www - Folder containing PHP-scripts.
psc.py - Pything script that actually checks if the server is up
psc.sql - Database structure(within mysql)
README - Make a guess

How to setup:

Database
1) if you have done that yet, create a database.
2) import the SQL: mysql -u username -p database_name < psc.sql
3) Done!

WWW
1) Move the contents of www to whatever place you desire.
2) edit lib/dbClass.php with host, username, password and database
3) Done!

psc.py
1) Move the script to whatever place you desire.
2) Open up psc.py and edit the Settings section.
3) Open up crontab by typing crontab -e
4) Add the following line: */5 * * * *    /path/to/psc.py >/dev/null 2>&1
5) Save.

Done!

This article is pretty much redundant because the README contains most of the information you'd need. However, if you have any questions, just drop a line below.

Last Notes

I made this in just under 4-5 hours, it's not pretty, but it works. And I've had a lot of use for it, don't hesitate to fork it. I recommend configuring prowl like the screenshot below:

prowl settings

I promise you, when the emergency sound goes off, you'll shit your pants, and there's no way you can sleep through that. It's a horrible noise, hence, perfect for waking you up. Cheers!

Author

sniker[at]codebase[dot]nu

Updated Fri, 18 Mar 2011 16:01:08 +0100

Topp

blog comments powered by Disqus