]> git.ipfire.org Git - people/shoehn/ipfire.org.git/commitdiff
Add netboot feature.
authorMichael Tremer <michael.tremer@ipfire.org>
Thu, 16 Dec 2010 21:55:56 +0000 (22:55 +0100)
committerMichael Tremer <michael.tremer@ipfire.org>
Thu, 16 Dec 2010 21:55:56 +0000 (22:55 +0100)
www/boot.py [new file with mode: 0644]
www/static/netboot/boot.png [new file with mode: 0644]
www/static/netboot/menu.c32 [new file with mode: 0644]
www/static/netboot/premenu.cfg [new file with mode: 0644]
www/static/netboot/vesamenu.c32 [new file with mode: 0644]
www/templates/netboot/menu.cfg [new file with mode: 0644]
www/webapp/backend/__init__.py
www/webapp/backend/netboot.py [new file with mode: 0644]

diff --git a/www/boot.py b/www/boot.py
new file mode 100644 (file)
index 0000000..d975d50
--- /dev/null
@@ -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 (file)
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 (file)
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 (file)
index 0000000..2104926
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..828eb2b
--- /dev/null
@@ -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 }}
+
index 3fb3ebba0bcdbce7726dd38f688b972f36bcc080..8712bf7400f79c97f6d829e28099eb0a7ae17530 100644 (file)
@@ -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 (file)
index 0000000..f78094e
--- /dev/null
@@ -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)