From: Michael Tremer Date: Thu, 16 Dec 2010 21:55:56 +0000 (+0100) Subject: Add netboot feature. X-Git-Url: http://git.ipfire.org/?p=people%2Fshoehn%2Fipfire.org.git;a=commitdiff_plain;h=5470bdf1476455ed91768072398a8117ce4a116b Add netboot feature. --- diff --git a/www/boot.py b/www/boot.py new file mode 100644 index 0000000..d975d50 --- /dev/null +++ b/www/boot.py @@ -0,0 +1,165 @@ +#!/usr/bin/python + +import logging +import os +import tornado.httpserver +import tornado.ioloop +import tornado.locale +import tornado.options +import tornado.web + +from webapp import backend + +BASEDIR = os.path.dirname(__file__) + +# Enable logging +tornado.options.enable_pretty_logging() +tornado.options.parse_command_line() + + +class BaseHandler(tornado.web.RequestHandler): + @property + def netboot(self): + return backend.NetBoot() + + +class MenuGPXEHandler(BaseHandler): + """ + menu.gpxe + """ + def get(self): + # XXX Check if version of the bootloader is allright + + # Devliver content + self.set_header("Content-Type", "text/plain") + self.write("#!gpxe\n") + self.write("chain menu.c32 premenu.cfg\n") + + +class MenuCfgHandler(BaseHandler): + def _menu_string(self, menu, level=0): + s = "" + + for entry in menu: + s += self._menu_entry(entry, level=level) + + return s + + def _menu_entry(self, entry, level=0): + lines = [] + + ident = "\t" * level + + if entry.type == "seperator": + lines.append(ident + "menu seperator") + lines.append("") + + elif entry.type == "header": + lines.append(ident + "menu begin %d" % entry.id) + lines.append(ident + "\tmenu title %s" % entry.title) + + # Add "Back..." entry + lines.append(ident + "\tlabel %d.back" % entry.id) + lines.append(ident + "\t\tmenu label Back...") + lines.append(ident + "\t\tmenu exit") + lines.append(ident + "\tmenu seperator") + + lines.append(ident + "%s" % self._menu_string(entry.submenu, level=level+1)) + lines.append(ident + "menu end") + lines.append("") + + elif entry.type == "config": + lines.append(ident + "label %d" % entry.id) + lines.append(ident + "\tmenu label %s" % entry.title) + if entry.description: + lines.append(ident + "\ttext help") + lines.append(entry.description) + lines.append(ident + "\tendtext") + lines.append(ident + "\tkernel /config/%s/boot.gpxe" % entry.item) + lines.append("") + + return "\n".join(lines) + + def get(self): + self.set_header("Content-Type", "text/plain") + + menu = self._menu_string(self.netboot.get_menu(1)) + + self.render("menu.cfg", menu=menu) + + +class BootGPXEHandler(BaseHandler): + def get(self, id): + config = self.netboot.get_config(id) + if not config: + raise tornado.web.HTTPError(404, "Configuration with ID '%s' was not found" % id) + + lines = ["#!gpxe", "imgfree",] + + line = "kernel -n img %s" % config.image1 + if line.endswith(".iso"): + line += " iso" + lines.append(line) + + if config.image2: + lines.append("initrd -n img %s" % config.image2) + + lines.append("boot img") + + for line in lines: + self.write("%s\n" % line) + + +class Application(tornado.web.Application): + def __init__(self): + settings = dict( + debug = True, + gzip = True, + static_path = os.path.join(BASEDIR, "static/netboot"), + template_path = os.path.join(BASEDIR, "templates/netboot"), + ) + + tornado.web.Application.__init__(self, **settings) + + self.add_handlers(r"boot.ipfire.org", [ + # Configurations + (r"/files/menu.gpxe", MenuGPXEHandler), + (r"/files/menu.cfg", MenuCfgHandler), + (r"/config/([0-9]+)/boot.gpxe", BootGPXEHandler), + + # Static files + (r"/files/(boot.png|premenu.cfg|vesamenu.c32|menu.c32)", + tornado.web.StaticFileHandler, { "path" : self.settings["static_path"] }), + ]) + + @property + def ioloop(self): + return tornado.ioloop.IOLoop.instance() + + def shutdown(self, *args): + logging.debug("Caught shutdown signal") + self.ioloop.stop() + + def run(self, port=8002): + logging.debug("Going to background") + + # All requests should be done after 30 seconds or they will be killed. + self.ioloop.set_blocking_log_threshold(30) + + http_server = tornado.httpserver.HTTPServer(self, xheaders=True) + + # If we are not running in debug mode, we can actually run multiple + # frontends to get best performance out of our service. + if not self.settings["debug"]: + http_server.bind(port) + http_server.start(num_processes=4) + else: + http_server.listen(port) + + self.ioloop.start() + +if __name__ == "__main__": + a = Application() + + a.run() + diff --git a/www/static/netboot/boot.png b/www/static/netboot/boot.png new file mode 100644 index 0000000..dc38f44 Binary files /dev/null and b/www/static/netboot/boot.png differ diff --git a/www/static/netboot/menu.c32 b/www/static/netboot/menu.c32 new file mode 100644 index 0000000..ffea9cc Binary files /dev/null and b/www/static/netboot/menu.c32 differ diff --git a/www/static/netboot/premenu.cfg b/www/static/netboot/premenu.cfg new file mode 100644 index 0000000..2104926 --- /dev/null +++ b/www/static/netboot/premenu.cfg @@ -0,0 +1,46 @@ +menu color title * #FFFFFFFF * +menu color sel * #ffffffff #999999ff * +menu color hotsel 1;7;37;40 #ffffffff #999999ff * +menu color tabmsg * #ffffffff #00000000 * +menu margin 20 +menu vshift 5 +menu rows 5 +menu width 80 +menu helpmsgrow 16 +prompt 0 +allowoptions 0 +menu hidden +timeout 30 + +menu title Select menu type +menu autoboot Press any key for options or wait # second{,s}... + +label vesa + menu default + text help + The default graphical boot menu. + endtext + menu label Graphical menu + kernel vesamenu.c32 + append menu.cfg + +label text + menu label Text menu + text help + Use this menu if the graphical menu does not work on your system. + endtext + kernel menu.c32 + append menu.cfg + +menu separator + +label chain + menu label Boot a configuration directly... + text help + Select this option and enter a configuration ID to boot it directly. + endtext + kernel custom.gpxe + +label exit + menu label Quit to gPXE command line + menu quit diff --git a/www/static/netboot/vesamenu.c32 b/www/static/netboot/vesamenu.c32 new file mode 100644 index 0000000..a5a3775 Binary files /dev/null and b/www/static/netboot/vesamenu.c32 differ diff --git a/www/templates/netboot/menu.cfg b/www/templates/netboot/menu.cfg new file mode 100644 index 0000000..828eb2b --- /dev/null +++ b/www/templates/netboot/menu.cfg @@ -0,0 +1,19 @@ +menu hshift 0 +menu width 49 +menu margin 8 +menu color title * #FFFFFFFF * +menu color border * #00000000 #00000000 none +menu color sel * #ffffffff #999999ff * +menu color hotsel 1;37;7;40 #ffffffff #999999ff * +menu color tabmsg * #ffffffff #00000000 * +menu vshift 8 +menu rows 10 +menu helpmsgrow 16 +menu background /files/boot.png +prompt 0 +allowoptions 0 + +menu title IPFire boot menu + +{{ menu }} + diff --git a/www/webapp/backend/__init__.py b/www/webapp/backend/__init__.py index 3fb3ebb..8712bf7 100644 --- a/www/webapp/backend/__init__.py +++ b/www/webapp/backend/__init__.py @@ -5,6 +5,7 @@ from banners import Banners from geoip import GeoIP from menu import Menu from mirrors import Mirrors +from netboot import NetBoot from news import News from planet import Planet from releases import Releases diff --git a/www/webapp/backend/netboot.py b/www/webapp/backend/netboot.py new file mode 100644 index 0000000..f78094e --- /dev/null +++ b/www/webapp/backend/netboot.py @@ -0,0 +1,61 @@ +#!/usr/bin/python + +from databases import Databases +from misc import Singleton + + +class MenuEntry(object): + def __init__(self, _data): + self._data = _data + self.submenu = None + + @property + def id(self): + return self._data.get("id") + + @property + def type(self): + return self._data.get("type", "root") + + @property + def title(self): + return self._data.get("title", "") + + @property + def description(self): + return self._data.get("description", "") + + @property + def item(self): + if self.type == "config": + return self._data.get("item") + + @property + def submenu_level(self): + if self.type == "header": + return int(self._data.get("item")) + + +class NetBoot(object): + __metaclass__ = Singleton + + @property + def db(self): + return Databases().webapp + + def get_menu(self, level=0): + menu = [] + + for m in self.db.query("SELECT * FROM boot_menu WHERE level = %d ORDER by level,prio" % level): + m = MenuEntry(m) + + if m.type == "header": + m.submenu = self.get_menu(m.submenu_level) + + menu.append(m) + + return menu + + def get_config(self, id): + id = int(id) + return self.db.get("SELECT * FROM boot WHERE id = %s", id)