commit aaa3b1a6ec25b49010809b98d8c033f5b8559476 Author: Sven Slootweg Date: Sun Mar 31 21:41:57 2013 +0200 Initial commit diff --git a/installer/install.py b/installer/install.py new file mode 100644 index 0000000..791e085 --- /dev/null +++ b/installer/install.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python + +import os, sys, pwd, glob, setuplib, subprocess + +def get_uid_gid(): + try: + target_user = pwd.getpwnam("pylsa") + except KeyError, e: + raise Exception("The PyLSA installation appears to have been corrupted.") + + return (target_user[2], target_user[3]) + +def switch_user(): + target_uid, target_gid = get_uid_gid() + + os.setgid(target_gid) + os.setuid(target_uid) + +def install(as_root=False): + try: + target_uid, target_gid = get_uid_gid() + except Exception, e: + target_uid, target_gid = (-1, -1) + + if as_root == True: + target_path = "/home/pylsa/.pylsa" + else: + target_path = os.path.expanduser("~/.pylsa") + + setuplib.create_directory(target_path, True, target_uid, target_gid) + setuplib.copy_file("src/daemon.py", "%s/daemon.py" % target_path, False, target_uid, target_gid) + setuplib.copy_file("pylsa.conf", "%s/pylsa.conf" % target_path, True, target_uid, target_gid) + subprocess.call(["tar", "-xzf", "psutil-0.6.1.tar.gz"]) + os.chdir("psutil-0.6.1") + + stfu = open("/dev/null", "w") + if subprocess.call(["python", "setup.py", "build"], stdout=stfu, stderr=stfu) != 0: + sys.stderr.write("An error occurred during compilation.\n") + exit(1) + + try: + build_dir = glob.glob("build/lib.*")[0] + except IndexError, e: + sys.stderr.write("Compilation appears to have failed, exiting...\n") + exit(1) + + setuplib.copy_directory(build_dir, "%s/lib" % target_path, False, target_uid, target_gid) + +def add_user(uname): + # Lock /etc/passwd and /etc/group so we can safely add a user. + open("/etc/passwd.lock", "w").close() + open("/etc/group.lock", "w").close() + + # Find highest non-reserved UID in the user list + passwd = open("/etc/passwd", "r+") + highest_uid = 1000 + + for line in passwd: + username, password, uid, gid, name, homedir, shell = line.split(":") + + if username == uname: + return True + + if int(uid) < 32000 and int(uid) > highest_uid: + highest_uid = int(uid) + + new_uid = highest_uid + 1 + + # Find highest non-reserved GID in the group list - we will assume same restrictions as for UID + grp = open("/etc/group", "r+") + highest_gid = 1000 + + for line in grp: + groupname, password, gid, users = line.split(":") + + if groupname == uname: + return True + + if int(gid) < 32000 and int(gid) > highest_gid: + highest_gid = int(gid) + + new_gid = highest_gid + 1 + + # Append new user and group + passwd.seek(0, 2) + grp.seek(0, 2) + + setuplib.create_directory("/home/%s" % uname, True, new_uid, new_gid, "u+rwx g+rx") + passwd.write("%s::%d:%d::/home/cvm:/bin/false\n" % (uname, new_uid, new_gid)) + grp.write("%s::%d:\n" % (uname, new_gid)) + + # We're done with /etc/passwd and /etc/group + passwd.close() + grp.close() + + # Remove the locks on /etc/passwd and /etc/group + os.remove("/etc/passwd.lock") + os.remove("/etc/group.lock") + + return True + +if os.getuid() == 0: + # Script is run as root + if os.path.isdir("/home/pylsa/.pylsa"): + # Already installed, run as pylsa user + switch_user() + exit(subprocess.call(["python", "/home/pylsa/.pylsa/daemon.py"])) + else: + # Not installed yet, install as pylsa user then run + add_user("pylsa") + install(True) + switch_user() + exit(subprocess.call(["python", "/home/pylsa/.pylsa/daemon.py"])) +else: + # Script is run as unprivileged user + if os.path.isdir(os.path.expanduser("~/.pylsa")): + # Already installed + exit(subprocess.call(["python", os.path.expanduser("~/.pylsa/daemon.py")])) + else: + # Not installed yet + install(False) + exit(subprocess.call(["python", os.path.expanduser("~/.pylsa/daemon.py")])) diff --git a/installer/psutil-0.6.1.tar.gz b/installer/psutil-0.6.1.tar.gz new file mode 100644 index 0000000..5b91ac8 Binary files /dev/null and b/installer/psutil-0.6.1.tar.gz differ diff --git a/installer/pylsa.conf b/installer/pylsa.conf new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/installer/pylsa.conf @@ -0,0 +1 @@ + diff --git a/installer/setuplib.py b/installer/setuplib.py new file mode 100644 index 0000000..8aa7d06 --- /dev/null +++ b/installer/setuplib.py @@ -0,0 +1,148 @@ +import stat, os, shutil, urllib, subprocess +from itertools import dropwhile + +def create_directory(path, ignore_failure=True, uid=-1, gid=-1, modes=""): + try: + os.makedirs(path) + except OSError, e: + if ignore_failure: + pass + else: + raise + + if uid != -1 or gid != -1: + os.chown(path, uid, gid) + + if modes != "": + set_modes(path, modes) + +def copy_file(source, destination, ignore_failure=True, uid=-1, gid=-1, modes=""): + if ignore_failure == False: + if os.path.exists(destination): + raise Exception("Destination path already exists.") + + try: + shutil.copy(source, destination) + except IOError, e: + if ignore_failure: + pass + else: + raise + + if uid != -1 or gid != -1: + os.chown(destination, uid, gid) + + if modes != "": + set_modes(destination, modes) + +def copy_directory(source, destination, ignore_failure=True, uid=-1, gid=-1, modes=""): + if ignore_failure == False: + if os.path.exists(destination): + raise Exception("Destination path already exists.") + + try: + shutil.copytree(source, destination) + except IOError, e: + if ignore_failure: + pass + else: + raise + + if uid != -1 or gid != -1: + os.chown(destination, uid, gid) + + if modes != "": + set_modes(destination, modes) + +def create_file(path, contents="", uid=-1, gid=-1, modes=""): + f = open(path, "w") + + if contents != "": + f.write(contents) + + f.close() + + if uid != -1 or gid != -1: + os.chown(path, uid, gid) + + if modes != "": + set_modes(path, modes) + +def set_modes(path, modes): + mode_map = { + "u": { + "r": stat.S_IRUSR, + "w": stat.S_IWUSR, + "x": stat.S_IXUSR + }, + "g": { + "r": stat.S_IRGRP, + "w": stat.S_IWGRP, + "x": stat.S_IXGRP + }, + "o": { + "r": stat.S_IROTH, + "w": stat.S_IWOTH, + "x": stat.S_IXOTH + } + + } + + chunks = modes.split(" ") + mode = 0 + + for chunk in chunks: + usertype, changes = chunk.split("+") + + if usertype in mode_map: + for change in list(changes): + if change in mode_map[usertype]: + mode = mode | mode_map[usertype][change] + else: + raise Exception("Unknown permission in modes specification.") + elif usertype == "a": + for change in list(changes): + for i in mode_map: + if change in mode_map[i]: + mode = mode | mode_map[i][change] + else: + raise Exception("Unknown user type in modes specification.") + + os.chmod(path, mode) + +def download_file(name, mirrors): + try: + file_mirrors = mirrors[name] + except KeyError, e: + raise Exception("No such file exists in the mirror list.") + + for url in file_mirrors: + try: + urllib.urlretrieve(url, name) + except: + continue + else: + return name + + raise Exception("No functional mirrors found for this file.") + +def install_rpm(path): + stfu = open("/dev/null", "wb") + + result = subprocess.call(["yum", "--nogpgcheck", "install", "-y", path], stdout=stfu, stderr=stfu) + + stfu.close() + + if result != 0: + raise Exception("Failed to install package.") + +def install_remote_rpm(name, mirrors): + download_file(name, mirrors) + install_rpm(name) + +def rindex(lst, item): + # http://stackoverflow.com/a/6892096/1332715 + try: + return dropwhile(lambda x: lst[x].strip() != item, reversed(xrange(len(lst)))).next() + except StopIteration: + raise ValueError, "rindex(lst, item): item not in list" diff --git a/script/daemon.py b/script/daemon.py new file mode 100644 index 0000000..8478cc2 --- /dev/null +++ b/script/daemon.py @@ -0,0 +1,135 @@ +import sys, os, json +from datetime import datetime + +# We need to set both the Python module path and system environment path, to make sure that the daemon +# can find both the psutil library and its compiled libraries. +sys.path.insert(0, "%s/lib" % os.path.split(os.path.realpath(__file__))[0]) +os.environ['PATH'] = "%s/lib:%s" % (os.path.split(os.path.realpath(__file__))[0], os.environ['PATH']) + +import psutil + +import urlparse +import SocketServer, SimpleHTTPServer + +bind_ip = "" +port = 8081 + +def generate_stats(get_processes): + listed_filesystems = ["ext2", "ext3", "ext4", "reiserfs", "removable", "fixed", "simfs"] + + mem = psutil.virtual_memory() + swap = psutil.swap_memory() + disks = {} + + for disk in psutil.disk_partitions(True): + if disk.fstype in listed_filesystems: + usage = psutil.disk_usage(disk.mountpoint) + + disks[disk.mountpoint] = { + "device": disk.device, + "options": disk.opts, + "filesystem": disk.fstype, + "total": usage.total, + "free": usage.free + } + + return_data = { + "uptime": (datetime.now() - datetime.fromtimestamp(psutil.BOOT_TIME)).total_seconds(), + "memory": { + "total": mem.total, + "available": mem.available, + "used": mem.used, + "unused": mem.free + }, + "swap": { + "total": swap.total, + "used": swap.used, + "unused": swap.free, + "in": swap.sin, + "out": swap.sout + }, + "disk": disks, + "cpu": psutil.cpu_percent(percpu=True) + } + + if get_processes: + processes = [] + + for proc in psutil.process_iter(): + try: + cwd = proc.getcwd() + except psutil.AccessDenied, e: + cwd = None + + processes.append({ + "pid": proc.pid, + "parent": proc.ppid, + "name": proc.name, + "command": proc.cmdline, + "user": proc.username, + "status": str(proc.status), # If we don't explicitly use str() here, json module will get confused + "cwd": cwd, + "cpu": proc.get_cpu_percent(interval=0.1), + "rss": proc.get_memory_info()[0] + }) + + return_data['process'] = processes + + return return_data + +class StatsHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def do_GET(self): + req = urlparse.urlparse(self.path) + get_params = urlparse.parse_qs(req.query) + path = req.path + + try: + get_processes = (get_params['processes'][0] == "1") + except: + get_processes = False + + if path=='/': + self.send_response(200) + self.send_header('Content-type','text/json') + self.end_headers() + self.wfile.write(json.dumps(generate_stats(get_processes))) + return + else: + self.send_response(404) + self.send_header('Content-type','text/plain') + self.end_headers() + self.wfile.write("404 Not Found") + return + +#### Hacky way to daemonize the whole thing + +# Fork away +if os.fork(): exit(0) +os.umask(0) +os.setsid() +if os.fork(): exit(0) + +# Write PID to file +outfile = open("pylsa.pid", "w") +outfile.write('%i' % os.getpid()) +outfile.close() + +# Bind the server +httpd = SocketServer.ThreadingTCPServer((bind_ip, port), StatsHandler, False) +httpd.allow_reuse_address = True +httpd.server_bind() +httpd.server_activate() + +# If all went well, we'll redirect streams to silence them +sys.stdout.flush() +sys.stderr.flush() +si = file('/dev/null', 'r') +so = file('/dev/null', 'a+') +se = file('/dev/null', 'a+', 0) +os.dup2(si.fileno(), sys.stdin.fileno()) +os.dup2(so.fileno(), sys.stdout.fileno()) +os.dup2(se.fileno(), sys.stderr.fileno()) + +# Done! +print 'Port=',port +httpd.serve_forever()