mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-20 10:34:30 +00:00
commit
d0e0285892
6
.gitignore
vendored
6
.gitignore
vendored
@ -20,3 +20,9 @@ templates/*.py
|
||||
TODO
|
||||
\#*
|
||||
.#*
|
||||
cfg.py
|
||||
cherrypy.config
|
||||
data/users.sqlite3
|
||||
predepend
|
||||
build/
|
||||
*.pid
|
||||
27
INSTALL
27
INSTALL
@ -2,20 +2,27 @@
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
To start Plinth, run:
|
||||
|
||||
$ ./start.sh
|
||||
|
||||
and point your web browser at `localhost:8000`. The default username is "admin"
|
||||
and the default password is "secret".
|
||||
|
||||
## 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.
|
||||
@ -42,5 +53,3 @@ The documentation has some dependencies too.
|
||||
Documentation has been collected into a pdf that can be built using
|
||||
`make doc`. It also gets built into smaller files and other formats,
|
||||
including one suitable for install as a man page.
|
||||
|
||||
<a name="installing_systemwide" />
|
||||
|
||||
18
Makefile
18
Makefile
@ -1,5 +1,6 @@
|
||||
#SHELL := /bin/bash
|
||||
MAKE=make
|
||||
BUILD_DIR = build
|
||||
|
||||
#TODO: add install target
|
||||
|
||||
@ -9,9 +10,22 @@ COMPRESSED_CSS := $(patsubst %.css,%.tiny.css,$(CSS))
|
||||
PWD=`pwd`
|
||||
|
||||
## Catch-all tagets
|
||||
default: cfg cherrypy.config dirs template css docs dbs
|
||||
default: predepend cfg cherrypy.config dirs template css docs dbs $(BUILD_DIR)/exmachina #$(BUILD_DIR)/bjsonrpc
|
||||
all: default
|
||||
|
||||
build:
|
||||
mkdir -p $(BUILD_DIR)
|
||||
|
||||
predepend:
|
||||
sudo sh -c "apt-get install augeas-tools python-bjsonrpc python-augeas python-simplejson pandoc python-cheetah"
|
||||
touch predepend
|
||||
|
||||
$(BUILD_DIR)/exmachina: build
|
||||
git clone git://github.com/tomgalloway/exmachina $(BUILD_DIR)/exmachina
|
||||
|
||||
$(BUILD_DIR)/bjsonrpc: build
|
||||
git clone git://github.com/deavid/bjsonrpc.git $(BUILD_DIR)/bjsonrpc
|
||||
|
||||
dbs: data/users.sqlite3
|
||||
|
||||
data/users.sqlite3: data/users.sqlite3.distrib
|
||||
@ -69,3 +83,5 @@ clean:
|
||||
@find . -name "*.bak" -exec rm {} \;
|
||||
@$(MAKE) -s -C doc clean
|
||||
@$(MAKE) -s -C templates clean
|
||||
rm -rf $(BUILD_DIR)
|
||||
rm -f predepend
|
||||
|
||||
12
NOTES
12
NOTES
@ -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
|
||||
|
||||
3
README
3
README
@ -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.
|
||||
|
||||
@ -8,10 +8,12 @@ user_db = os.path.join(data_dir, "users")
|
||||
status_log_file = os.path.join(data_dir, "status.log")
|
||||
access_log_file = os.path.join(data_dir, "access.log")
|
||||
users_dir = os.path.join(data_dir, "users")
|
||||
pidfile = os.path.join(data_dir, "pidfile.pid")
|
||||
|
||||
product_name = "Plinth"
|
||||
box_name = "FreedomBox"
|
||||
|
||||
host = "127.0.0.1"
|
||||
port = 8000
|
||||
|
||||
## Do not edit below this line ##
|
||||
|
||||
2
fabfile.py
vendored
2
fabfile.py
vendored
@ -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():
|
||||
|
||||
42
issues/links.org
Normal file
42
issues/links.org
Normal file
@ -0,0 +1,42 @@
|
||||
# -*- mode: org; mode: auto-fill; fill-column: 80 -*-
|
||||
|
||||
#+TITLE: Make Links Portable
|
||||
#+OPTIONS: d:t
|
||||
#+LINK_UP: ./
|
||||
#+LINK_HOME: ../
|
||||
|
||||
* Issue
|
||||
|
||||
Currently, all the links in Plinth point to 127.0.0.1/(something), and that
|
||||
sucks for serving Plinth on a local network, like most use cases imply.
|
||||
|
||||
* Fixes [0/1]
|
||||
|
||||
** TODO Links work when accessed from remote clients.
|
||||
|
||||
Investigate the following:
|
||||
|
||||
: grep -nHr basehref ../*
|
||||
|
||||
: grep -nHr 127.0 ../*
|
||||
|
||||
#+begin_ascii
|
||||
fabfile.py:40: if env.host == "localhost" or env.host=="127.0.0.1":
|
||||
fabfile.py:46: if env.host == "localhost" or env.host=="127.0.0.1":
|
||||
fabfile.py:102: hidden_service_config = "HiddenServiceDir %s\nHiddenServicePort 80 127.0.0.1:%d" % (tor_dir, santiago_port)
|
||||
modules/installed/santiago/santiago.py:48: hidden_service_config = "HiddenServiceDir %s\nHiddenServicePort 80 127.0.0.1:%d" % (self.tor_dir, santiago_port)
|
||||
plinth.py:119: server.socket_host = '127.0.0.1'
|
||||
#+end_ascii
|
||||
|
||||
Also, why is base_href blank in [[file:~/programs/freedombox/plinth/cfg.sample.py][cfg.sample.py]]?
|
||||
|
||||
* Discussion
|
||||
|
||||
* Metadata
|
||||
:PROPERTIES:
|
||||
:Status: Incomplete
|
||||
:Priority: 0
|
||||
:Owner: Nick Daly
|
||||
:Description:
|
||||
:Tags:
|
||||
:END:
|
||||
9
model.py
9
model.py
@ -1,14 +1,15 @@
|
||||
class User(dict):
|
||||
""" Every user must have keys for a username, name, password (this
|
||||
""" Every user must have keys for a username, name, passphrase (this
|
||||
is a md5 hash of the password), groups, and an email address. They can be
|
||||
blank or None, but the keys must exist. """
|
||||
def __init__(self, dict=None):
|
||||
for key in ['username', 'name', 'password', 'email']:
|
||||
for key in ['username', 'name', 'passphrase', 'email']:
|
||||
self[key] = ''
|
||||
for key in ['groups']:
|
||||
self[key] = []
|
||||
for key in dict:
|
||||
self[key] = dict[key]
|
||||
if dict:
|
||||
for key in dict:
|
||||
self[key] = dict[key]
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return None
|
||||
|
||||
@ -12,10 +12,42 @@ class UserStore(UserStoreModule, sqlite_db):
|
||||
self.db_file = cfg.user_db
|
||||
sqlite_db.__init__(self, self.db_file, autocommit=True)
|
||||
self.__enter__()
|
||||
|
||||
def close(self):
|
||||
self.__exit__()
|
||||
def expert(self):
|
||||
return False
|
||||
self.__exit__(None,None,None)
|
||||
|
||||
def expert(self, username=None):
|
||||
groups = self.attr(username,"groups")
|
||||
if not groups:
|
||||
return False
|
||||
return 'expert' in groups
|
||||
|
||||
def attr(self, username=None, field=None):
|
||||
return self.get(username)[field]
|
||||
|
||||
def get(self,username=None):
|
||||
return User(sqlite_db.get(self,username))
|
||||
|
||||
def exists(self, username=None):
|
||||
try:
|
||||
user = self.get(username)
|
||||
if not user:
|
||||
return False
|
||||
elif user["username"]=='':
|
||||
return False
|
||||
return True
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
def remove(self,username=None):
|
||||
self.__delitem__(username)
|
||||
self.commit()
|
||||
|
||||
def get_all(self):
|
||||
return self.items()
|
||||
|
||||
def set(self,username=None,user=None):
|
||||
sqlite_db.__setitem__(self,username, user)
|
||||
|
||||
class UserStoreOld():
|
||||
#class UserStore(UserStoreModule):
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import os, shutil, subprocess
|
||||
import os, subprocess
|
||||
from socket import gethostname
|
||||
import cherrypy
|
||||
import simplejson as json
|
||||
@ -10,6 +10,7 @@ import cfg
|
||||
from forms import Form
|
||||
from model import User
|
||||
from util import *
|
||||
import platform
|
||||
|
||||
class Config(PagePlugin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -41,20 +42,21 @@ 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)
|
||||
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
|
||||
if platform.linux_distribution()[0]=="Ubuntu" :
|
||||
cfg.exmachina.service.start("hostname")
|
||||
else:
|
||||
cfg.log.debug("Hostname restart returned %s" % retcode)
|
||||
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 +74,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 +86,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 +127,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
|
||||
@ -128,8 +136,8 @@ class general(FormPlugin, PagePlugin):
|
||||
message += msg
|
||||
if time_zone != sys_store['time_zone']:
|
||||
src = os.path.join("/usr/share/zoneinfo", time_zone)
|
||||
cfg.log.info("Copying %s to /etc/localtime" % src)
|
||||
shutil.copy(src, "/etc/localtime")
|
||||
cfg.log.info("Setting timezone to %s" % time_zone)
|
||||
cfg.exmachina.misc.set_timezone(time_zone)
|
||||
sys_store['time_zone'] = time_zone
|
||||
return message or "Settings updated."
|
||||
|
||||
|
||||
@ -5,22 +5,23 @@ from plugin_mount import PagePlugin, FormPlugin
|
||||
import cfg
|
||||
from forms import Form
|
||||
from util import *
|
||||
from model import User
|
||||
|
||||
class users(PagePlugin):
|
||||
order = 20 # order of running init in PagePlugins
|
||||
def __init__(self, *args, **kwargs):
|
||||
PagePlugin.__init__(self, *args, **kwargs)
|
||||
self.register_page("sys.users")
|
||||
self.register_page("sys.users.add")
|
||||
self.register_page("sys.users.edit")
|
||||
|
||||
@cherrypy.expose
|
||||
@require()
|
||||
def index(self):
|
||||
parts = self.forms('/sys/config')
|
||||
parts['title']=_("Manage Users and Groups")
|
||||
return self.fill_template(**parts)
|
||||
return self.fill_template(title="Manage Users and Groups", sidebar_right="""<strong><a href="/sys/users/add">Add User</a></strong><br/><strong><a href="/sys/users/edit">Edit Users</a></strong>""")
|
||||
|
||||
class add(FormPlugin, PagePlugin):
|
||||
url = ["/sys/users"]
|
||||
url = ["/sys/users/add"]
|
||||
order = 30
|
||||
|
||||
sidebar_left = ''
|
||||
@ -40,33 +41,35 @@ class add(FormPlugin, PagePlugin):
|
||||
form.text_input(_("Username"), name="username", value=username)
|
||||
form.text_input(_("Full name"), name="name", value=name)
|
||||
form.text_input(_("Email"), name="email", value=email)
|
||||
form.text_input(_("Password"), name="password")
|
||||
form.text_input(_("Password"), name="password", type="password")
|
||||
form.text_input(name="md5_password", type="hidden")
|
||||
form.submit(label=_("Create User"), name="create")
|
||||
return form.render()
|
||||
|
||||
def process_form(self, username=None, name=None, email=None, md5_password=None, **kwargs):
|
||||
msg = ''
|
||||
msg = Message()
|
||||
|
||||
if not username: msg = add_message(msg, _("Must specify a username!"))
|
||||
if not md5_password: msg = add_message(msg, _("Must specify a password!"))
|
||||
if not username: msg.add = _("Must specify a username!")
|
||||
if not md5_password: msg.add = _("Must specify a password!")
|
||||
|
||||
if username in cfg.users:
|
||||
msg = add_message(msg, _("User already exists!"))
|
||||
if username in cfg.users.get_all():
|
||||
msg.add = _("User already exists!")
|
||||
else:
|
||||
try:
|
||||
cfg.users[username]= User(dict={'username':username, 'name':name, 'email':email, 'password':md5_password})
|
||||
di = {'username':username, 'name':name, 'email':email, 'passphrase':md5_password}
|
||||
new_user = User(di)
|
||||
cfg.users.set(username,new_user)
|
||||
except:
|
||||
msg = add_message(msg, _("Error storing user!"))
|
||||
msg.add = _("Error storing user!")
|
||||
|
||||
if not msg:
|
||||
msg = add_message(msg, "%s saved." % username)
|
||||
|
||||
main = self.make_form(username, name, email, message=msg)
|
||||
return self.fill_template(title="", main=main, sidebar_left=self.sidebar_left, sidebar_right=self.sidebar_right)
|
||||
msg.add = _("%s saved." % username)
|
||||
cfg.log(msg.text)
|
||||
main = self.main(username, name, email, msg=msg.text)
|
||||
return self.fill_template(title="Manage Users and Groups", main=main, sidebar_left=self.sidebar_left, sidebar_right=self.sidebar_right)
|
||||
|
||||
class edit(FormPlugin, PagePlugin):
|
||||
url = ["/sys/users"]
|
||||
url = ["/sys/users/edit"]
|
||||
order = 35
|
||||
|
||||
sidebar_left = ''
|
||||
@ -77,14 +80,15 @@ class edit(FormPlugin, PagePlugin):
|
||||
system.</p><p>Deleting users is permanent!</p>""" % (cfg.product_name, cfg.box_name))
|
||||
|
||||
def main(self, msg=''):
|
||||
users = cfg.users.keys()
|
||||
users = cfg.users.get_all()
|
||||
add_form = Form(title=_("Edit or Delete User"), action="/sys/users/edit", message=msg)
|
||||
add_form.html('<span class="indent"><strong>Delete</strong><br /></span>')
|
||||
for uname in sorted(users.keys()):
|
||||
for uname in users:
|
||||
user = User(uname[1])
|
||||
add_form.html('<span class="indent"> %s ' %
|
||||
add_form.get_checkbox(name=uname) +
|
||||
add_form.get_checkbox(name=user['username']) +
|
||||
'<a href="/sys/users/edit?username=%s">%s (%s)</a><br /></span>' %
|
||||
(uname, users[uname]['name'], uname))
|
||||
(user['username'], user['name'], user['username']))
|
||||
add_form.submit(label=_("Delete User"), name="delete")
|
||||
return add_form.render()
|
||||
|
||||
@ -95,12 +99,12 @@ class edit(FormPlugin, PagePlugin):
|
||||
cfg.log.info("%s asked to delete %s" % (cherrypy.session.get(cfg.session_key), usernames))
|
||||
if usernames:
|
||||
for username in usernames:
|
||||
if username in cfg.users:
|
||||
if cfg.users.exists(username):
|
||||
try:
|
||||
del cfg.users[username]
|
||||
cfg.users.remove(username)
|
||||
msg.add(_("Deleted user %s." % username))
|
||||
except IOError, e:
|
||||
if 'username' in cfg.users:
|
||||
if cfg.users.exists(username):
|
||||
m = _("Error on deletion, user %s not fully deleted: %s" % (username, e))
|
||||
cfg.log.error(m)
|
||||
msg.add(m)
|
||||
@ -113,8 +117,8 @@ class edit(FormPlugin, PagePlugin):
|
||||
msg.add(_("User %s does not exist." % username))
|
||||
else:
|
||||
msg.add = _("Must specify at least one valid, existing user.")
|
||||
main = self.make_form(msg=msg.text)
|
||||
return self.fill_template(title="", main=main, sidebar_left=self.sidebar_left, sidebar_right=self.sidebar_right)
|
||||
main = self.main(msg=msg.text)
|
||||
return self.fill_template(title="Manage Users and Groups", main=main, sidebar_left=self.sidebar_left, sidebar_right=self.sidebar_right)
|
||||
|
||||
sidebar_right = ''
|
||||
u = cfg.users[kwargs['username']]
|
||||
@ -125,4 +129,4 @@ class edit(FormPlugin, PagePlugin):
|
||||
|
||||
main = _("""<strong>Edit User '%s'</strong>""" % u['username'])
|
||||
sidebar_right = ''
|
||||
return self.fill_template(title="", main=main, sidebar_left=self.sidebar_left, sidebar_right=sidebar_right)
|
||||
return self.fill_template(title="Manage Users and Groups", main=main, sidebar_left=self.sidebar_left, sidebar_right=sidebar_right)
|
||||
|
||||
31
plinth.py
31
plinth.py
@ -17,6 +17,9 @@ from util import *
|
||||
from logger import Logger
|
||||
#from modules.auth import AuthController, require, member_of, name_is
|
||||
|
||||
from exmachina import ExMachinaClient
|
||||
import socket
|
||||
|
||||
__version__ = "0.2.14"
|
||||
__author__ = "James Vasile"
|
||||
__copyright__ = "Copyright 2011, James Vasile"
|
||||
@ -51,7 +54,7 @@ class Root(plugin_mount.PagePlugin):
|
||||
raise cherrypy.InternalRedirect('/router')
|
||||
else:
|
||||
raise cherrypy.InternalRedirect('/help/about')
|
||||
|
||||
|
||||
def load_modules():
|
||||
"""Import all the symlinked .py files in the modules directory and
|
||||
all the .py files in directories linked in the modules directory
|
||||
@ -71,9 +74,20 @@ 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
|
||||
else:
|
||||
if not cfg.pidfile:
|
||||
cfg.pidfile = "plinth.pid"
|
||||
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 +99,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})
|
||||
@ -103,7 +124,7 @@ def setup():
|
||||
server.subscribe()
|
||||
|
||||
# Configure default server
|
||||
cherrypy.config.update({'server.socket_host': '127.0.0.1',
|
||||
cherrypy.config.update({'server.socket_host': cfg.host,
|
||||
'server.socket_port': cfg.port,
|
||||
'server.thread_pool':10,
|
||||
'tools.staticdir.root': cfg.file_root,
|
||||
@ -112,7 +133,7 @@ def setup():
|
||||
'tools.sessions.storage_type':"file",
|
||||
'tools.sessions.timeout':90,
|
||||
'tools.sessions.storage_path':"%s/data/cherrypy_sessions" % cfg.file_root,
|
||||
|
||||
|
||||
})
|
||||
|
||||
config = {'/': {'tools.staticdir.root': '%s/static' % cfg.file_root,
|
||||
@ -124,11 +145,11 @@ def setup():
|
||||
}
|
||||
cherrypy.tree.mount(cfg.html_root, '/', config=config)
|
||||
cherrypy.engine.signal_handler.subscribe()
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
setup()
|
||||
print "localhost %d" % cfg.port
|
||||
print "%s %d" % (cfg.host, cfg.port)
|
||||
|
||||
cherrypy.engine.start()
|
||||
cherrypy.engine.block()
|
||||
|
||||
@ -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 --group=$PLINTH_GROUP || 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
|
||||
|
||||
10
start.sh
Executable file
10
start.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#! /bin/sh
|
||||
|
||||
PYTHONPATH=build/exmachina:$PYTHONPATH
|
||||
|
||||
export PYTHONPATH
|
||||
|
||||
sudo killall exmachina.py
|
||||
sudo build/exmachina/exmachina.py -v &
|
||||
python plinth.py
|
||||
sudo killall exmachina.py
|
||||
9
test.sh
Executable file
9
test.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#! /bin/sh
|
||||
|
||||
PYTHONPATH=build/exmachina:$PYTHONPATH
|
||||
PYTHONPATH=modules/installed/lib:$PYTHONPATH
|
||||
PYTHONPATH=vendor:$PYTHONPATH
|
||||
|
||||
export PYTHONPATH
|
||||
|
||||
python tests/test_user_store.py
|
||||
86
tests/test_user_store.py
Normal file
86
tests/test_user_store.py
Normal file
@ -0,0 +1,86 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- mode: python; mode: auto-fill; fill-column: 80 -*-
|
||||
|
||||
import user_store
|
||||
from logger import Logger
|
||||
import cfg
|
||||
import unittest
|
||||
import cherrypy
|
||||
import plugin_mount
|
||||
import os
|
||||
from model import User
|
||||
cfg.log = Logger()
|
||||
|
||||
cherrypy.log.access_file = None
|
||||
|
||||
class UserStore(unittest.TestCase):
|
||||
"""Test each function of user_store to confirm they work as expected"""
|
||||
|
||||
def setUp(self):
|
||||
cfg.user_db = os.path.join(cfg.file_root, "tests/testdata/users");
|
||||
self.userstore = plugin_mount.UserStoreModule.get_plugins()[0]
|
||||
|
||||
def tearDown(self):
|
||||
for user in self.userstore.get_all():
|
||||
self.userstore.remove(user[0])
|
||||
self.userstore.close()
|
||||
|
||||
def test_user_does_not_exist(self):
|
||||
self.assertEqual(self.userstore.exists("notausername"),False)
|
||||
|
||||
def test_user_does_exist(self):
|
||||
self.add_user("isausername", False)
|
||||
self.assertEqual(self.userstore.exists("isausername"),True)
|
||||
|
||||
def test_add_user(self):
|
||||
self.assertEqual(len(self.userstore.items()),0)
|
||||
self.add_user("test_user", False)
|
||||
self.assertEqual(len(self.userstore.items()),1)
|
||||
|
||||
def test_user_is_in_expert_group(self):
|
||||
self.add_user("test_user", True)
|
||||
self.assertEqual(self.userstore.expert("test_user"),True)
|
||||
|
||||
def test_user_is_not_in_expert_group(self):
|
||||
self.add_user("test_user", False)
|
||||
self.assertEqual(self.userstore.expert("test_user"),False)
|
||||
|
||||
def test_user_removal(self):
|
||||
self.assertEqual(len(self.userstore.items()),0)
|
||||
self.add_user("test_user", False)
|
||||
self.assertEqual(len(self.userstore.items()),1)
|
||||
self.userstore.remove("test_user")
|
||||
self.assertEqual(len(self.userstore.items()),0)
|
||||
|
||||
def test_get_user_email_attribute(self):
|
||||
self.add_user("test_user", False,"test@home")
|
||||
self.assertEqual(self.userstore.attr("test_user","email"),"test@home")
|
||||
|
||||
def test_get_user(self):
|
||||
test_user = self.add_user("test_user", False)
|
||||
self.assertEqual(self.userstore.get("test_user"),test_user)
|
||||
|
||||
def test_get_all_users(self):
|
||||
self.add_user("test_user1", False)
|
||||
self.add_user("test_user2", False)
|
||||
self.assertEqual(len(self.userstore.get_all()),2)
|
||||
|
||||
def add_user(self, test_username, add_to_expert_group, email=''):
|
||||
test_user = self.create_user(test_username, email)
|
||||
if add_to_expert_group:
|
||||
test_user = self.add_user_to_expert_group(test_user)
|
||||
self.userstore.set(test_username,test_user)
|
||||
return test_user
|
||||
|
||||
def create_user(self, username, email=''):
|
||||
test_user = User()
|
||||
test_user["username"] = username
|
||||
test_user["email"] = email
|
||||
return test_user
|
||||
|
||||
def add_user_to_expert_group(self, user):
|
||||
user["groups"] = ["expert"]
|
||||
return user
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
BIN
tests/testdata/users.sqlite3
vendored
Normal file
BIN
tests/testdata/users.sqlite3
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user