Commit f1e764f2 authored by Bryan Newbold's avatar Bryan Newbold

integrate exmachina configuration management layer

- add exmachina code and test code
- modify plinth.py to listen for shared secret on stdin at start
  (if appropriate flag is set) and try to connect to exmachina daemon
- use exmachina to read and set /etc/hostname as a demo
- update plinth init.d script to start exmachina and share keys
- update docs with new deps and run instructions
parent 337560b0
......@@ -2,10 +2,10 @@
## Installing Plinth
Install the python-cheetah package and pandoc:
apt-get install python-cheetah pandoc
Install the python-cheetah package, pandoc, python-augeas, and
bjsonrpc:
apt-get install python-cheetah pandoc python-augeas python-bjsonrpc
Install the python-simplejson
......@@ -13,9 +13,16 @@ apt-get install python-simplejson
Unzip and untar the source into a directory. Change to the directory
containing the program. Do `make` and then run `./plinth.py` and
point your web browser at `localhost:8000`. The default username is
"admin" and the default password is "secret".
containing the program. Run:
$ make
$ ./plinth.py
and point your web browser at `localhost:8000`. The default username is "admin"
and the default password is "secret". To actually edit the configuration of
your local/dev machine, also run:
$ sudo ./exmachina/exmachina.py -v &
## Dependencies
......@@ -27,6 +34,10 @@ point your web browser at `localhost:8000`. The default username is
* *GNU Make* is used to build the templates and such.
* bjsonrpc - used for configuration management layer
* python-augeas and augeas - used for configuration management
The documentation has some dependencies too.
* *Markdown* is used to format and style docs.
......
......@@ -2,6 +2,18 @@
%
% February 2012
# Edits by bnewbold
## 2012-07-12 "exmachina" configuration management layer
- this new code is very ugly and in the "just make it work" style
- add exmachina code and test code
- modify plinth.py to listen for shared secret on stdin at start
(if appropriate flag is set) and try to connect to exmachina daemon
- use exmachina to read and set /etc/hostname as a demo
- update plinth init.d script to start exmachina and share keys
- update docs with new deps and run instructions
# Edits by seandiggity
## 2012-02-27 new theme based upon bootstrap
......
......@@ -50,4 +50,5 @@ interface will overwrite those changes at first opportunity. This
interface is not a tool for super admins facing complex scenarios. It
is for home users to do a wide variety of basic tasks.
See comments in exmachina/exmachina.py for more details about the configuration
management process seperation scheme.
This diff is collapsed.
#!/usr/bin/env bash
# Test init.d-style initialization; run this script as root (or sudo it)
export key=`./exmachina.py --random-key`
echo $key | ./exmachina.py -vk --pidfile /tmp/exmachina_test.pid
sleep 1
echo $key | sudo -u www-data -g www-data ./test_exmachina.py -k
kill `cat /tmp/exmachina_test.pid` && rm /tmp/exmachina_test.pid
sleep 1
jobs
#!/usr/bin/env python
"""
This file tests the "client side" of the exmachina layer.
To use with secret keys, do the following in seperate terminals:
$ echo "<key>" | ./exmachina.py -vk
$ echo "<key>" | ./test.py -k
To use without, do the following in seperate terminals:
$ echo "<key>" | ./exmachina.py -vk
$ echo "<key>" | ./test.py -k
Use the init_test.sh script to test shared key passing and privilage seperation
at the same time:
$ sudo ./init_test.sh
"""
import sys
import optparse
import logging
import socket
import bjsonrpc
import bjsonrpc.connection
import augeas
from exmachina import ExMachinaClient
# =============================================================================
# Command line handling
def main():
socket_path="/tmp/exmachina.sock"
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(socket_path)
secret_key = None
if sys.argv[-1] == "-k":
print "waiting for key on stdin..."
secret_key = sys.stdin.readline()
print "sent!"
print "========= Testing JSON-RPC connection"
c = bjsonrpc.connection.Connection(sock)
if secret_key:
c.call.authenticate(secret_key)
print "/*: %s" % c.call.augeas_match("/*")
print "/augeas/*: %s" % c.call.augeas_match("/augeas/*")
print "/etc/* files:"
for name in c.call.augeas_match("/files/etc/*"):
print "\t%s" % name
print c.call.initd_status("bluetooth")
print "hostname: %s" % c.call.augeas_get("/files/etc/hostname/*")
print "localhost: %s" % c.call.augeas_get("/files/etc/hosts/1/canonical")
sock.close()
print "========= Testing user client library"
client = ExMachinaClient(secret_key=secret_key)
print client.augeas.match("/files/etc/*")
#print client.initd.restart("bluetooth")
print client.initd.status("greentooth")
print "(expect Error on the above line)"
print client.initd.status("bluetooth")
client.close()
if __name__ == '__main__':
main()
......@@ -138,7 +138,7 @@ def apache():
@task
def deps():
"Basic plinth dependencies"
sudo('apt-get install --no-install-recommends -y python make python-cheetah pandoc python-simplejson python-pyme')
sudo('apt-get install --no-install-recommends -y python make python-cheetah pandoc python-simplejson python-pyme python-augeas python-bjsonrpc')
@task
def update():
......
......@@ -41,20 +41,18 @@ def valid_hostname(name):
def set_hostname(hostname):
"Sets machine hostname to hostname"
cfg.log.info("Writing '%s' to /etc/hostname" % hostname)
unslurp("/etc/hostname", hostname+"\n")
cfg.log.info("Writing '%s' to /etc/hostname with exmachina" % hostname)
try:
retcode = subprocess.call("/etc/init.d/hostname.sh start", shell=True)
if retcode < 0:
cfg.log.error("Hostname restart terminated by signal: return code is %s" % retcode)
else:
cfg.log.debug("Hostname restart returned %s" % retcode)
cfg.exmachina.augeas.set("/files/etc/hostname/*", hostname)
cfg.exmachina.augeas.save()
# don't persist/cache change unless it was saved successfuly
sys_store = filedict_con(cfg.store_file, 'sys')
sys_store['hostname'] = hostname
cfg.exmachina.initd.restart("hostname.sh") # is hostname.sh debian-only?
except OSError, e:
raise cherrypy.HTTPError(500, "Hostname restart failed: %s" % e)
sys_store = filedict_con(cfg.store_file, 'sys')
sys_store['hostname'] = hostname
class general(FormPlugin, PagePlugin):
url = ["/sys/config"]
order = 30
......@@ -72,8 +70,11 @@ class general(FormPlugin, PagePlugin):
def main(self, message='', **kwargs):
sys_store = filedict_con(cfg.store_file, 'sys')
hostname = cfg.exmachina.augeas.get("/files/etc/hostname/*")
# this layer of persisting configuration in sys_store could/should be
# removed -BLN
defaults = {'time_zone': "slurp('/etc/timezone').rstrip()",
'hostname': "gethostname()",
'hostname': "hostname",
}
for k,c in defaults.items():
if not k in kwargs:
......@@ -81,6 +82,8 @@ class general(FormPlugin, PagePlugin):
kwargs[k] = sys_store[k]
except KeyError:
exec("if not '%(k)s' in kwargs: sys_store['%(k)s'] = kwargs['%(k)s'] = %(c)s" % {'k':k, 'c':c})
# over-ride the sys_store cached value
kwargs['hostname'] = hostname
## Get the list of supported timezones and the index in that list of the current one
module_file = __file__
......@@ -120,7 +123,8 @@ class general(FormPlugin, PagePlugin):
old_val = sys_store['hostname']
try:
set_hostname(hostname)
except:
except Exception, e:
cfg.log.error(e)
cfg.log.info("Trying to restore old hostname value.")
set_hostname(old_val)
raise
......
......@@ -17,6 +17,9 @@ from util import *
from logger import Logger
#from modules.auth import AuthController, require, member_of, name_is
from exmachina.exmachina import ExMachinaClient
import socket
__version__ = "0.2.14"
__author__ = "James Vasile"
__copyright__ = "Copyright 2011, James Vasile"
......@@ -71,9 +74,17 @@ def parse_arguments():
parser = argparse.ArgumentParser(description='Plinth web interface for the FreedomBox.')
parser.add_argument('--pidfile', default="",
help='specify a file in which the server may write its pid')
parser.add_argument('--listen-exmachina-key', default=False, action='store_true',
help='listen for JSON-RPC shared secret key on stdin at startup')
args=parser.parse_args()
if args.pidfile:
cfg.pidfile = args.pidfile
if args.listen_exmachina_key:
# this is where we optionally try to read in a shared secret key to
# authenticate connections to exmachina
cfg.exmachina_secret_key = sys.stdin.readline().strip()
else:
cfg.exmachina_secret_key = None
def setup():
parse_arguments()
......@@ -85,6 +96,13 @@ def setup():
except AttributeError:
pass
try:
cfg.exmachina = ExMachinaClient(
secret_key=cfg.exmachina_secret_key or None)
except socket.error:
cfg.exmachina = None
print "couldn't connect to exmachina daemon, but continuing anyways..."
os.chdir(cfg.file_root)
cherrypy.config.update({'error_page.404': error_page_404})
cherrypy.config.update({'error_page.500': error_page_500})
......
......@@ -12,23 +12,53 @@
# This file is /etc/init.d/plinth
DAEMON=/usr/local/bin/plinth.py
EXMACHINA_DAEMON=/usr/local/bin/exmachina.py
PID_FILE=/var/run/plinth.pid
EXMACHINA_PID_FILE=/var/run/exmachina.pid
PLINTH_USER=www-data
PLINTH_GROUP=www-data
test -x $DAEMON || exit 0
test -x $EXMACHINA_DAEMON || exit 0
set -e
. /lib/lsb/init-functions
start_plinth (){
if [ -f $PID_FILE ]; then
echo Already running with a pid of `cat $PID_FILE`.
echo Already running with a pid of `cat $PID_FILE`.
else
$DAEMON --pidfile=$PID_FILE
if [ -f $EXMACHINA_PID_FILE ]; then
echo exmachina was already running with a pid of `cat $EXMACHINA_PID_FILE`.
kill -15 `cat $EXMACHINA_PID_FILE`
rm -rf $EXMACHINA_PID_FILE
fi
SHAREDKEY=`$EXMACHINA_DAEMON --random-key`
touch $PID_FILE
chown $PLINTH_USER:$PLINTH_GROUP $PID_FILE
echo $SHAREDKEY | $EXMACHINA_DAEMON --pidfile=$EXMACHINA_PID_FILE || rm $PID_FILE
sleep 0.5
echo $SHAREDKEY | sudo -u $PLINTH_USER -g $PLINTH_GROUP $DAEMON --pidfile=$PID_FILE
fi
}
stop_plinth () {
if [ -f $PID_FILE ]; then
kill -15 `cat $PID_FILE`
kill -15 `cat $PID_FILE` || true
rm -rf $PID_FILE
echo "killed plinth"
else
echo "No pid file at $PID_FILE suggests plinth is not running."
fi
if [ -f $EXMACHINA_PID_FILE ]; then
kill -15 `cat $EXMACHINA_PID_FILE` || true
rm -rf $EXMACHINA_PID_FILE
echo "killed exmachina"
else
echo "No pid file at $EXMACHINA_PID_FILE suggests exmachina is not running."
fi
}
test -x $DAEMON || exit 0
......@@ -45,5 +75,12 @@ case "$1" in
$0 stop
$0 start
;;
status)
status_of_proc -p $PID_FILE "$DAEMON" plinth && exit 0 || exit $?
;;
*)
echo "Usage: $NAME {start|stop|restart|status}" >&2
exit 1
;;
esac
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment