diff --git a/.gitignore b/.gitignore index d5be6856d..e5bdabe39 100644 --- a/.gitignore +++ b/.gitignore @@ -20,9 +20,8 @@ templates/*.py TODO \#* .#* -cfg.py -cherrypy.config +*~ data/users.sqlite3 predepend build/ -*.pid \ No newline at end of file +*.pid diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..9622603f6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "vendor/withsqlite"] + path = vendor/withsqlite + url = git://github.com/jvasile/withsqlite.git +[submodule "vendor/exmachina"] + path = vendor/exmachina + url = git://github.com/jvasile/exmachina.git diff --git a/COPYING b/COPYING index 9f218c2b5..694d4547f 100644 --- a/COPYING +++ b/COPYING @@ -1,9 +1,9 @@ -# License to Copy the Plinth front end to the Freedom Plug +# License to Copy Plinth -Plinth is Copyright 2011 James Vasile (). It +Plinth is Copyright 2011-2013 James Vasile (). It is distributed under the GNU Affero General Public License, Version 3 -or later. A copy of GPLv3 is available [from the Free Software -Foundation](http://www.gnu.org/licenses/gpl.html). +or later. A copy of AGPLv3 is available [from the Free Software +Foundation](http://www.gnu.org/licenses/agpl.html). In addition, the documentation to this software is distributed under a Creative Commons Attribution-ShareAlike 3.0 Unported, Version 3 diff --git a/INSTALL b/INSTALL index e5761a149..a8d6138d6 100644 --- a/INSTALL +++ b/INSTALL @@ -5,12 +5,7 @@ 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 - +apt-get install python-cheetah pandoc python-augeas python-bjsonrpc python-cherrypy3 python-simplejson Unzip and untar the source into a directory. Change to the directory containing the program. Run: diff --git a/Makefile b/Makefile index 8b62fe067..cf29a63a3 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,4 @@ -#SHELL := /bin/bash MAKE=make -BUILD_DIR = vendor - -#TODO: add install target CSS=$(wildcard *.css) CSS=$(subst .tiny,,$(shell find themes -type f -name '*.css')) @@ -10,21 +6,43 @@ COMPRESSED_CSS := $(patsubst %.css,%.tiny.css,$(CSS)) PWD=`pwd` ## Catch-all tagets -default: predepend cfg cherrypy.config dirs template css docs dbs $(BUILD_DIR)/exmachina #$(BUILD_DIR)/bjsonrpc +default: predepend config dirs template css docs dbs all: default -build: - mkdir -p $(BUILD_DIR) - predepend: - su -c "apt-get -y install augeas-tools python-bjsonrpc python-augeas python-simplejson pandoc python-cheetah" + sudo sh -c "apt-get install augeas-tools python-bjsonrpc python-augeas python-simplejson pandoc python-cheetah python-cherrypy3" + git submodule init + git submodule update touch predepend -$(BUILD_DIR)/exmachina: build - git clone git://github.com/tomgalloway/exmachina $(BUILD_DIR)/exmachina +install: default + mkdir -p $(DESTDIR)/etc/init.d $(DESTDIR)/etc/plinth + cp plinth.sample.fhs.config $(DESTDIR)/etc/plinth/plinth.config + mkdir -p $(DESTDIR)/usr/lib/python2.7/plinth $(DESTDIR)/usr/bin \ + $(DESTDIR)/usr/share/doc/plinth $(DESTDIR)/usr/share/man/man1 + rsync -L doc/* $(DESTDIR)/usr/share/doc/plinth/ + gzip $(DESTDIR)/usr/share/doc/plinth/plinth.1 + mv $(DESTDIR)/usr/share/doc/plinth/plinth.1.gz $(DESTDIR)/usr/share/man/man1 + rsync -rl *.py modules templates vendor themes static \ + --exclude static/doc --exclude ".git/*" --exclude "*.pyc" \ + $(DESTDIR)/usr/lib/python2.7/plinth + mkdir -p $(DESTDIR)/usr/lib/python2.7/plinth/static/doc + cp doc/*.html $(DESTDIR)/usr/lib/python2.7/plinth/static/doc + rm -f $(DESTDIR)/usr/lib/python2.7/plinth/plinth.config + ln -s ../../../../etc/plinth/plinth.config $(DESTDIR)/usr/lib/python2.7/plinth/plinth.config + cp share/init.d/plinth $(DESTDIR)/etc/init.d + rm -f $(DESTDIR)/usr/bin/plinth + ln -s ../lib/python2.7/plinth/plinth.py $(DESTDIR)/usr/bin/plinth + mkdir -p $(DESTDIR)/var/lib/plinth/cherrypy_sessions $(DESTDIR)/var/log/plinth $(DESTDIR)/var/run + cp -r data/* $(DESTDIR)/var/lib/plinth + rm -f $(DESTDIR)/var/lib/plinth/users/sqlite3.distrib -$(BUILD_DIR)/bjsonrpc: build - git clone git://github.com/deavid/bjsonrpc.git $(BUILD_DIR)/bjsonrpc +uninstall: + rm -rf $(DESTDIR)/usr/lib/python2.7/plinth $(DESTDIR)/usr/share/plinth/ \ + $(DESTDIR)/etc/plinth $(DESTDIR)/var/lib/plinth $(DESTDIR)/usr/share/doc/plinth/ \ + $(DESTDIR)/var/log/plinth + rm -f $(DESTDIR)/usr/bin/plinth $(DESTDIR)/etc/init.d/plinth \ + $(DESTDIR)/usr/share/man/man1/plinth.1.gz $(DESTDIR)/var/run/plinth.pid dbs: data/users.sqlite3 @@ -34,29 +52,8 @@ data/users.sqlite3: data/users.sqlite3.distrib dirs: @mkdir -p data/cherrypy_sessions -cfg: Makefile - test -f cfg.py || cp cfg.sample.py cfg.py - -cherrypy.config: Makefile - @echo [global]\\n\ -server.socket_host = \'0.0.0.0\'\\n\ -server.socket_port = 8000\\n\ -server.thread_pool = 10\\n\ -tools.staticdir.root = \"$(PWD)\"\\n\ -tools.sessions.on = True\\n\ -tools.auth.on = True\\n\ -tools.sessions.storage_type = \"file\"\\n\ -tools.sessions.timeout = 90\\n\ -tools.sessions.storage_path = \"$(PWD)/data/cherrypy_sessions\"\\n\ -\\n\ -[/static]\\n\ -tools.staticdir.on = True\\n\ -tools.staticdir.dir = \"static\"\\n\ -\\n\ -[/favicon.ico]\\n\ -tools.staticfile.on = True\\n\ -tools.staticfile.filename = \"$(PWD)/static/theme/favicon.ico\"\\n\ -> cherrypy.config +config: Makefile + @test -f plinth.config || cp plinth.sample.config plinth.config %.tiny.css: %.css @cat $< | python -c 'import re,sys;print re.sub("\s*([{};,:])\s*", "\\1", re.sub("/\*.*?\*/", "", re.sub("\s+", " ", sys.stdin.read())))' > $@ @@ -83,5 +80,5 @@ clean: @find . -name "*.bak" -exec rm {} \; @$(MAKE) -s -C doc clean @$(MAKE) -s -C templates clean - rm -rf $(BUILD_DIR) + rm -rf $(BUILDDIR) $(DESTDIR) rm -f predepend diff --git a/README b/README index 6e00a780e..a5e4bf906 100644 --- a/README +++ b/README @@ -36,19 +36,12 @@ to get newer, better shinier functions. The modules will automatically integrate into the existing menu system so you can control all of the box's parts from one central location. -The interface has a 'basic' and an 'expert' mode. In basic mode, much -of Plinth's configuration and capability are hidden. Sane defaults -are chosen whenever possible. In expert mode, you can get down into -the details and configure things the average user never thinks about. -For example, experts can turn off ntp or switch ntp servers. Basic -users should never even know those options exist. - -One caveat: expert mode is not as powerful as the commandline. We can -only do so much when translating free-form configuration files into -web forms. And if you manually change your configuration files, the -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. +The interface will eventually have a 'basic' and an 'expert' mode. In +basic mode, much of Plinth's configuration and capability are hidden. +Sane defaults are chosen whenever possible. In expert mode, you can +get down into the details and configure things the average user never +thinks about. For example, experts can turn off ntp or switch ntp +servers. Basic users should never even know those options exist. See comments in exmachina/exmachina.py for more details about the configuration management process seperation scheme. diff --git a/cfg.py b/cfg.py new file mode 100644 index 000000000..4d2480695 --- /dev/null +++ b/cfg.py @@ -0,0 +1,42 @@ +from menu import Menu +import os + +from ConfigParser import SafeConfigParser +parser = SafeConfigParser( + defaults={ + 'root':os.path.dirname(os.path.realpath(__file__)), + 'product_name':"", + 'box_name':"", + 'file_root':"", + 'data_dir':"", + 'store_file':"", + 'user_db':"", + 'status_log_file':"", + 'access_log_file':"", + 'users_dir':"", + 'host':"127.0.0.1", + 'pidfile':"", + 'port':"", + }) +parser.read(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'plinth.config')) + +product_name = parser.get('Name', 'product_name') +box_name = parser.get('Name', 'box_name') +root = parser.get('Path', 'root') +file_root = parser.get('Path', 'file_root') +data_dir = parser.get('Path', 'data_dir') +store_file = parser.get('Path', 'store_file') +user_db = parser.get('Path', 'user_db') +status_log_file = parser.get('Path', 'status_log_file') +access_log_file = parser.get('Path', 'access_log_file') +users_dir = parser.get('Path', 'users_dir') +pidfile = parser.get('Path', 'pidfile') +host = parser.get('Network', 'host') +port = int(parser.get('Network', 'port')) + +html_root = None +main_menu = Menu() +base_href = "" + +if store_file.endswith(".sqlite3"): + store_file = os.path.splitext(store_file)[0] diff --git a/cfg.sample.py b/cfg.sample.py deleted file mode 100644 index 8521ca653..000000000 --- a/cfg.sample.py +++ /dev/null @@ -1,23 +0,0 @@ -from menu import Menu -import os - -file_root = os.path.dirname(os.path.realpath(__file__)) -data_dir = os.path.join(file_root, "data") -store_file = os.path.join(data_dir, "store.sqlite3") -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 ## -html_root = None -main_menu = Menu() -base_href = "" - diff --git a/cherrypy b/cherrypy deleted file mode 120000 index 10eb6dcc0..000000000 --- a/cherrypy +++ /dev/null @@ -1 +0,0 @@ -vendor/CherryPy-3.2.0/py2/cherrypy \ No newline at end of file diff --git a/doc/hacking.mdwn b/doc/hacking.mdwn index 38e59fba1..a334fc51b 100644 --- a/doc/hacking.mdwn +++ b/doc/hacking.mdwn @@ -42,6 +42,9 @@ There are lots of bugs. We don't have a spec or tests, so a bug is really just any unexpected behavior. I am not easily surprised, but there are still lots of bugs. +There's an [issue tracker](https://github.com/jvasile/Plinth/issues). +Please add things and consult it for things to tackle. + ## Coding Practices @@ -91,7 +94,9 @@ directories. That makes removal easy. ## Todo -Plinth has a number of open todo items. Please help! +Plinth has a number of open todo items. And there are items in the +[issue tracker](https://github.com/jvasile/Plinth/issues) that need +tackling. Please help! * Implement the functions in the submenus of router.py * Unify our logging and cherrypy's. diff --git a/modules/first_boot.py b/modules/first_boot.py new file mode 120000 index 000000000..ae5c55471 --- /dev/null +++ b/modules/first_boot.py @@ -0,0 +1 @@ +installed/first_boot.py \ No newline at end of file diff --git a/modules/installed/first_boot.py b/modules/installed/first_boot.py new file mode 100644 index 000000000..9cf747454 --- /dev/null +++ b/modules/installed/first_boot.py @@ -0,0 +1,120 @@ +from urlparse import urlparse +import os, cherrypy, re +from gettext import gettext as _ +from plugin_mount import PagePlugin, PluginMount, FormPlugin +from modules.auth import require +from forms import Form +import util as u +from vendor.withsqlite.withsqlite import sqlite_db +import cfg + +class FirstBoot(PagePlugin): + def __init__(self, *args, **kwargs): + PagePlugin.__init__(self, *args, **kwargs) + self.register_page("firstboot") # this is the url this page will hang off of (/firstboot) + + @cherrypy.expose + def index(self, *args, **kwargs): + return self.state0(*args, **kwargs) + + ## TODO: flesh out these tests values + def valid_box_name_p(self, name): + name = name.strip() + if re.search("\W", name): + return False + return True + def valid_box_key_p(self, key): + return True + def generate_box_key(self): + return "fake key" + + @cherrypy.expose + def state0(self, message="", box_name="", box_key="", submitted=False): + """ + In this state, we do time config over HTTP, name the box and + server key selection. + + All the parameters are form inputs. They get passed in when + the form is submitted. This method checks the inputs and if + they validate, uses them to take action. If they do not + validate, it displays the form to give the user a chance to + input correct values. It might display an error message (in + the message parameter). + + message is an optional string that we can display to the + user. It's a good place to put error messages. + """ + + ## Until LDAP is in place, we'll put the box name and key in the cfg.store_file + ## Must resist the sick temptation to write an LDAP interface to the sqlite file + with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as db: + db['about'] = "This table is for information about this FreedomBox" + if box_name: + if self.valid_box_name_p(box_name): + db['box_name'] = box_name + else: + message += _("Invalid box name.") + elif 'box_name' in db and db['box_name']: + box_name = db['box_name'] + #TODO: set /etc/hostname to box name via ex machina + + if box_key: + if self.valid_box_key_p(box_key): + db['box_key'] = box_key + else: + message += _("Invalid key!") + elif 'box_key' in db and db['box_key']: + box_key = _("We already have a key for this box on file.") #TODO: Think this through and handle more gracefully + elif submitted and not box_key: + box_key = self.generate_box_key() + db['box_key'] = box_key + + + if box_name and box_key and self.valid_box_name_p(box_name) and self.valid_box_key_p(box_key): + ## Update state to 1 and head there + with sqlite_db(cfg.store_file, table="firstboot", autocommit=True) as db: + db['state']=1 + raise cherrypy.InternalRedirect('/firstboot/state1') + + main = "

Welcome. It looks like this FreedomBox isn't set up yet. We'll need to ask you a just few questions to get started.

" + form = Form(title="Welcome to Your FreedomBox!", + action="/firstboot", + name="whats_my_name", + message=message) + if not box_name: + box_name = cfg.box_name + form.html("

For convenience, your FreedomBox needs a name. It should be something short that doesn't contain spaces or punctuation. 'Willard' would be a good name. 'Freestyle McFreedomBox!!!' would not.

") + form.text_input('Name your FreedomBox', id="box_name", value=box_name) + form.html("

%(box_name)s uses cryptographic keys so it can prove its identity when talking to you. %(box_name)s can make a key for itself, but if one already exists (from a prior FreedomBox, for example), you can paste it below. This key should not be the same as your key because you are not your FreedomBox!

" % {'box_name':cfg.box_name}) + form.text_box("If you want, paste your box's key here.", id="box_key", value=box_key) + form.hidden(name="submitted", value="True") + form.submit("Box it up!") + + main += form.render() + return self.fill_template(template="base", title=_("First Boot!"), main=main, + sidebar_right=_("""Getting Help

We've done our best to make your FreedomBox easy to use. If you have questions during setup, there are a few places to turn for help. TODO: add links to such help.

""")) + + @cherrypy.expose + def state1(self, message=None): + """ + State 1 is when we have a box name and key. In this state, + our task is to provide a certificate and maybe to guide the + user through installing it. We automatically move to State 2, + which is an HTTPS connection. + + TODO: HTTPS failure in State 2 should returns to state 1. + """ + main = """

Here's a certificate. +TODO: explain all this cert stuff to the user.

+

TODO: add instrux for installing certificate.

+

After you have installed +""" + if False: + ## Update state to 2 and head there + with sqlite_db(cfg.store_file, table="firstboot", autocommit=True) as db: + db['state']=1 + #TODO: switch to HTTPS + raise cherrypy.InternalRedirect('/firstboot/state1') + + return self.fill_template(template="base", title=_("Installing the Certificate"), main=main, + sidebar_right=_("""Getting Help

We've done our best to make your FreedomBox easy to use. If you have questions during setup, there are a few places to turn for help. TODO: add links to such help.

""")) diff --git a/modules/installed/help/help.py b/modules/installed/help/help.py index 049f600d4..e05123bf5 100644 --- a/modules/installed/help/help.py +++ b/modules/installed/help/help.py @@ -83,7 +83,6 @@ class View(PagePlugin): if page not in ['design', 'plinth', 'hacking', 'faq']: raise cherrypy.HTTPError(404, "The path '/help/view/%s' was not found." % page) return self.fill_template(template="err", main="

Sorry, as much as I would like to show you that page, I don't seem to have a page named %s!

" % page) - IF = open(os.path.join("doc", "%s.part.html" % page), 'r') - main = IF.read() - IF.close() - return self.fill_template(template="two_col", title=_("%s Documentation" % cfg.product_name), main=main) + with open(os.path.join("doc", "%s.part.html" % page), 'r') as IF: + main = IF.read() + return self.fill_template(title=_("%s Documentation" % cfg.product_name), main=main) diff --git a/modules/installed/lib/forms.py b/modules/installed/lib/forms.py index df78eea11..2be810f18 100644 --- a/modules/installed/lib/forms.py +++ b/modules/installed/lib/forms.py @@ -1,9 +1,32 @@ +""" +The Form class is a helper class that takes parameters and method +calls and can return html for a form with appropriate hooks for css +styling. It should allow you to display a form but have the +formatting and styling added by the class. You can worry less about +how it looks while still getting consistent, decent-looking forms. + +Take a look at the FirstBoot class for an example of forms in action. + +Copyright 2011-2013 James Vasile + +This software is released to you (yes, you) under the terms of the GNU +Affero General Public License, version 3 or later, available at +. + +This program 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 Affero General Public License for more details. +""" class Form(): def __init__(self, action=None, cls='form', title=None, onsubmit=None, name=None, message='', method="get"): """Note that there appears to be a bug in cherrypy whereby forms submitted via post don't have their fields included in kwargs for the default index method. So we use get by - default, though it's not as neat.""" + default, though it's not as neat. + + TODO: file bug on this w/ CherryPy project + """ action = self.get_form_attrib_text('action', action) onsubmit = self.get_form_attrib_text('onsubmit', onsubmit) @@ -65,13 +88,15 @@ class Form(): %s """ % (label, type, name, id, value, size) - def text_box(self, label='', name=None, id=None): + def hidden(self, name=None, id=None, value=''): + self.text_input(type="hidden", name=name, id=id, value=value) + def text_box(self, label='', name=None, id=None, value=""): name, id = self.name_or_id(name, id) self.text += """ """ % (label, name, id) + + """ % (label, name, id, value) def submit(self, label='', name=None, id=None): name, id = self.name_or_id(name, id) self.text += """ diff --git a/plinth.py b/plinth.py index 79d96748c..9250b676b 100755 --- a/plinth.py +++ b/plinth.py @@ -1,7 +1,6 @@ #!/usr/bin/env python import os, sys, argparse -#import logging from gettext import gettext as _ import cfg if not os.path.join(cfg.file_root, "vendor") in sys.path: @@ -13,27 +12,32 @@ from cherrypy.process.plugins import Daemonizer Daemonizer(cherrypy.engine).subscribe() import plugin_mount -from util import * +import util as u + from logger import Logger #from modules.auth import AuthController, require, member_of, name_is +from vendor.withsqlite.withsqlite import sqlite_db +from vendor.exmachina.exmachina import ExMachinaClient import socket __version__ = "0.2.14" __author__ = "James Vasile" -__copyright__ = "Copyright 2011, James Vasile" +__copyright__ = "Copyright 2011-2013, James Vasile" __license__ = "GPLv3 or later" __maintainer__ = "James Vasile" __email__ = "james@jamesvasile.com" __status__ = "Development" def error_page(status, dynamic_msg, stock_msg): - return page_template(template="err", title=status, main="

%s

%s" % (dynamic_msg, stock_msg)) + return u.page_template(template="err", title=status, main="

%s

%s" % (dynamic_msg, stock_msg)) def error_page_404(status, message, traceback, version): - return error_page(status, message, """

If you believe this missing page should exist, please file a - bug with either the Plinth project or the people responsible for - the module you are trying to access.

+ return error_page(status, message, """

If you believe this + missing page should exist, please file a bug with either the Plinth + project (it has + an issue tracker) or the people responsible for the module you + are trying to access.

Sorry for the mistake.

""") @@ -42,13 +46,20 @@ def error_page_500(status, message, traceback, version): cfg.log.error("500 Internal Server Error. Trackback is above.") more="""

This is an internal error and not something you caused or can fix. Please report the error on the bug tracker so + href="https://github.com/jvasile/Plinth/issues">bug tracker so we can fix it.

""" return error_page(status, message, "

%s

%s
" % (more, "\n".join(traceback.split("\n")))) class Root(plugin_mount.PagePlugin): @cherrypy.expose def index(self): + ## TODO: firstboot hijacking root should probably be in the firstboot module with a hook in plinth.py + with sqlite_db(cfg.store_file, table="firstboot") as db: + if not 'state' in db: + raise cherrypy.InternalRedirect('/firstboot') + elif db['state'] < 5: + cfg.log("First Boot state = %d" % db['state']) + raise cherrypy.InternalRedirect('/firstboot/state%d' % db['state']) if cherrypy.session.get(cfg.session_key, None): raise cherrypy.InternalRedirect('/router') else: @@ -79,8 +90,12 @@ def parse_arguments(): if args.pidfile: cfg.pidfile = args.pidfile else: - if not cfg.pidfile: + try: + if not cfg.pidfile: cfg.pidfile = "plinth.pid" + except AttributeError: + 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 @@ -129,25 +144,24 @@ def setup(): # Configure default server cherrypy.config.update( - { 'server.socket_host': cfg.host, - 'server.socket_port': cfg.port, - 'server.thread_pool':10, - 'tools.staticdir.root': cfg.file_root, - 'tools.sessions.on':True, - 'tools.auth.on':True, - 'tools.sessions.storage_type':"file", - 'tools.sessions.timeout':90, - 'tools.sessions.storage_path': - "%s/data/cherrypy_sessions" % cfg.file_root, - }) + {'server.socket_host': cfg.host, + 'server.socket_port': cfg.port, + 'server.thread_pool':10, + 'tools.staticdir.root': cfg.file_root, + 'tools.sessions.on':True, + 'tools.auth.on':True, + 'tools.sessions.storage_type':"file", + 'tools.sessions.timeout':90, + 'tools.sessions.storage_path':"%s/cherrypy_sessions" % cfg.data_dir,}) - config = {'/': {'tools.staticdir.root': '%s/static' % cfg.file_root, - 'tools.proxy.on':True,}, - '/static': {'tools.staticdir.on': True, - 'tools.staticdir.dir':"."}, - '/favicon.ico':{'tools.staticfile.on':True, - 'tools.staticfile.filename': - "%s/static/theme/favicon.ico" % cfg.file_root}} + config = { + '/': {'tools.staticdir.root': '%s/static' % cfg.file_root, + 'tools.proxy.on': True,}, + '/static': {'tools.staticdir.on': True, + 'tools.staticdir.dir': "."}, + '/favicon.ico':{'tools.staticfile.on': True, + 'tools.staticfile.filename': + "%s/static/theme/favicon.ico" % cfg.file_root}} cherrypy.tree.mount(cfg.html_root, '/', config=config) cherrypy.engine.signal_handler.subscribe() diff --git a/plinth.sample.config b/plinth.sample.config new file mode 100644 index 000000000..ece4d06d7 --- /dev/null +++ b/plinth.sample.config @@ -0,0 +1,17 @@ +[Name] +product_name = Plinth +box_name = FreedomBox + +[Path] +file_root = %(root)s +data_dir = %(file_root)s/data +store_file = %(data_dir)s/store.sqlite3 +user_db = %(data_dir)s/users +status_log_file = %(data_dir)s/status.log +access_log_file = %(data_dir)s/access.log +users_dir = %(data_dir)s/users +pidfile = %(data_dir)s/pidfile.pid + +[Network] +host = 127.0.0.1 +port = 8000 diff --git a/plinth.sample.fhs.config b/plinth.sample.fhs.config new file mode 100644 index 000000000..3e226da4c --- /dev/null +++ b/plinth.sample.fhs.config @@ -0,0 +1,17 @@ +[Name] +product_name = Plinth +box_name = FreedomBox + +[Path] +file_root = /usr/lib/python2.7/plinth +data_dir = /var/lib/plinth +store_file = %(data_dir)s/store.sqlite3 +user_db = %(data_dir)s/users +status_log_file = /var/log/plinth/status.log +access_log_file = /var/log/plinth/access.log +users_dir = %(data_dir)s/users +pidfile = /var/run/plinth.pid + +[Network] +host = 127.0.0.1 +port = 8000 diff --git a/plugin_mount.py b/plugin_mount.py index 5ad414a8b..cf0f26880 100644 --- a/plugin_mount.py +++ b/plugin_mount.py @@ -67,6 +67,7 @@ class PagePlugin: def __init__(self, *args, **kwargs): """If cfg.html_root is none, then this is the html_root.""" if not cfg.html_root: + cfg.log('Setting html root to %s' % self.__class__.__name__) cfg.html_root = self def register_page(self, url): @@ -165,6 +166,10 @@ class UserStoreModule: user_store.py: get, get_all, set, exists, remove, attr, expert. See source code for doc strings. + This is designed as a plugin so mutiple types of user store can be + supported. But the project is moving towards LDAP for + compatibility with third party software. A future version of + Plinth is likely to require LDAP. """ - __metaclass__ = PluginMountSingular + __metaclass__ = PluginMountSingular # singular because we can only use one user store at a time diff --git a/start.sh b/start.sh index 4be20c1b6..8b97c0bcd 100755 --- a/start.sh +++ b/start.sh @@ -1,10 +1,10 @@ #! /bin/sh -PYTHONPATH=build/exmachina:$PYTHONPATH +#PYTHONPATH=vendor/exmachina:$PYTHONPATH export PYTHONPATH sudo killall exmachina.py -sudo build/exmachina/exmachina.py -v & +sudo vendor/exmachina/exmachina.py -v & python plinth.py sudo killall exmachina.py diff --git a/templates/base.tmpl b/templates/base.tmpl index 5a37f537b..08443b0bc 100644 --- a/templates/base.tmpl +++ b/templates/base.tmpl @@ -118,17 +118,8 @@ FreedomBoxFreedomBox Dashboard - @@ -136,18 +127,26 @@
+ #if $nav or $sidebar_right or $sidebar_left
+ #if $nav -
+ #end if + #if $sidebar_left + + #end if + #if $sidebar_right + + #end if +
+ #end if
-
+

#block title_block $title @@ -157,15 +156,6 @@ $main #end block main_block

- -
-
- #block sidebar_right_block - $sidebar_right - #end block sidebar_right_block -
-
-
diff --git a/templates/err.tmpl b/templates/err.tmpl index 74dc12cc4..02106184b 100644 --- a/templates/err.tmpl +++ b/templates/err.tmpl @@ -1,4 +1,4 @@ -#extends templates.two_col +#extends templates.login_nav #def title_block Error: $title diff --git a/templates/login_nav.tmpl b/templates/login_nav.tmpl new file mode 100644 index 000000000..05780d05b --- /dev/null +++ b/templates/login_nav.tmpl @@ -0,0 +1,31 @@ +#extends templates.base + +#def add_nav_and_login +