summaryrefslogtreecommitdiffstats
path: root/build_tools/l7/larch0/cli
diff options
context:
space:
mode:
Diffstat (limited to 'build_tools/l7/larch0/cli')
-rwxr-xr-xbuild_tools/l7/larch0/cli/archin.py378
-rw-r--r--build_tools/l7/larch0/cli/backend.py444
-rwxr-xr-xbuild_tools/l7/larch0/cli/boot_iso.py154
-rw-r--r--build_tools/l7/larch0/cli/config.py92
-rwxr-xr-xbuild_tools/l7/larch0/cli/larchify.py588
-rwxr-xr-xbuild_tools/l7/larch0/cli/live_iso.py154
-rwxr-xr-xbuild_tools/l7/larch0/cli/live_part.py221
-rw-r--r--build_tools/l7/larch0/cli/media_common.py450
-rwxr-xr-xbuild_tools/l7/larch0/cli/test.py42
-rw-r--r--build_tools/l7/larch0/cli/userinfo.py93
10 files changed, 2616 insertions, 0 deletions
diff --git a/build_tools/l7/larch0/cli/archin.py b/build_tools/l7/larch0/cli/archin.py
new file mode 100755
index 0000000..7e00831
--- /dev/null
+++ b/build_tools/l7/larch0/cli/archin.py
@@ -0,0 +1,378 @@
+#!/usr/bin/env python
+#
+# archin.py
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# larch 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.19
+
+# This is a command line script to perform an Arch Linux installation
+# based on a list of packages. All needed parameters are passed as options.
+
+import os
+from glob import glob
+from config import *
+from backend import *
+
+class Installation:
+ def __init__(self, options):
+ self.options = options
+ self.installation_dir = get_installation_dir()
+ if self.installation_dir == '/':
+ errout(_("Operations on '/' are not supported ..."))
+
+ self.profile_dir = get_profile()
+ self.pacman_cmd = self.make_pacman_command()
+ self.make_pacman_conf()
+
+
+ def make_pacman_command(self):
+ """Construct pacman command. Return the command, including options.
+ This includes options for installation path, cache directory and
+ for suppressing the progress bar. It assumes a temporary location
+ for pacman.conf, which must also be set up.
+ If there is no pacman executable in the system PATH, check that
+ it is available in the larch directory.
+ """
+ pacman = runcmd('bash -c "which pacman || echo _FAIL_"')[1][-1].strip()
+ if pacman == '_FAIL_':
+ # If the host is not Arch, there will probably be no pacman
+ # (if there is some other program called 'pacman' that's
+ # a real spanner in the works).
+ # The alternative is to provide it in the larch directory.
+ pacman = base_dir + '/pacman'
+ if not os.path.isfile(pacman):
+ errout(_("No pacman executable found"))
+
+ pacman += (' -r %s --config %s --noconfirm'
+ % (self.installation_dir, PACMAN_CONF))
+ if self.options.noprogress:
+ pacman += ' --noprogressbar'
+ if self.options.cache:
+ pacman += ' --cachedir ' + self.options.cache
+ return pacman
+
+
+ def make_pacman_conf(self, final=False):
+ """Construct the pacman.conf file used by larch.
+ To make it a little easier to manage upstream changes to the default
+ pacman.conf, a separate file (pacman.conf.repos) is used to specify
+ the repositories to use. The contents of this file are used to modify
+ the basic pacman.conf file, which may be the default version or one
+ provided in the profile.
+ The 'final' parameter determines whether the version for the resulting
+ live system (True) or for the installation process (False) is
+ generated. If generating the installation version, it is possible
+ to specify alternative repositories, via the 'repofile' option,
+ which allows the pacman.conf used for the installation to be
+ different from the version in the resulting live system.
+ The return value is a list of the names of the repositories which
+ are included.
+ It is also possible to specify just a customized mirrorlist for the
+ installation by placing it in the working directory.
+ """
+ # Allow use of '*platform*' in pacman.conf.repos
+ platform = os.uname()[4]
+ if platform != 'x86_64':
+ platform = 'i686'
+
+ # Get pacman.conf header part
+ pc0 = self.profile_dir + '/pacman.conf.options'
+ if not os.path.isfile(pc0):
+ pc0 = base_dir + '/data/pacman.conf'
+ pacmanconf = self.pacmanoptions(readfile(pc0))
+
+ # Get file with repository entries
+ pc1 = self.profile_dir + '/pacman.conf.repos'
+ if not os.path.isfile(pc1):
+ pc1 = base_dir + '/data/pacman.conf.repos'
+ if self.options.repofile and not final:
+ pc1 = os.path.realpath(self.options.repofile)
+
+ # Get repository path
+ if final:
+ default = 'Include = /etc/pacman.d/mirrorlist'
+ else:
+ mlist = cwd + '/mirrorlist'
+ if not os.path.isfile(mlist):
+ mlist = '/etc/pacman.d/mirrorlist'
+ if not os.path.isfile(mlist):
+ mlist = base_dir + '/data/mirrorlist'
+ default = 'Include = ' + mlist
+
+ # Read repository entries
+ repos = []
+ for line in readfile(pc1).splitlines():
+ line = line.strip()
+ if (not line) or (line[0] == '#'):
+ continue
+ r, s = [t.strip() for t in line.split(':', 1)]
+ repos.append(r)
+ s = s.replace('*default*', default)
+ pacmanconf += ('\n[%s]\n%s\n'
+ % (r, s.replace('*platform*', platform)))
+
+
+ writefile(pacmanconf, self.installation_dir + '/etc/pacman.conf'
+ if final else PACMAN_CONF)
+ return repos
+
+
+ def install(self):
+ """Clear the chosen installation directory and install the base
+ set of packages, together with any additional ones listed in the
+ file 'addedpacks' (in the profile), removing the packages in
+ 'vetopacks' from the list.
+ """
+ if not query_yn(_("Install Arch to '%s'?") % self.installation_dir):
+ return False
+ # Can't delete the whole directory because it might be a mount point
+ if os.path.isdir(self.installation_dir):
+ if script('cleardir %s' % self.installation_dir):
+ return False
+
+ # Ensure installation directory exists and check that device nodes
+ # can be created (creating /dev/null is also a workaround for an
+ # Arch bug - which may have been fixed, but this does no harm)
+ if not (runcmd('bash -c "mkdir -p %s/{dev,proc,sys}"'
+ % self.installation_dir)[0]
+ and runcmd('mknod -m 666 %s/dev/null c 1 3'
+ % self.installation_dir)[0]):
+ errout(_("Couldn't write to the installation path (%s)")
+ % self.installation_dir)
+ if not runcmd('bash -c "echo test >%s/dev/null"'
+ % self.installation_dir)[0]:
+ errout(_("The installation path (%s) is mounted 'nodev'.")
+ % self.installation_dir)
+
+ # I should also check that it is possible to run stuff in the
+ # installation directory.
+ runcmd('bash -c "cp $( which echo ) %s"' % self.installation_dir)
+ if not runcmd('%s/echo "yes"' % self.installation_dir)[0]:
+ errout(_("The installation path (%s) is mounted 'noexec'.")
+ % self.installation_dir)
+ runcmd('rm %s/echo' % self.installation_dir)
+
+ # Fetch package database
+ runcmd('mkdir -p %s/var/lib/pacman' % self.installation_dir)
+ self.refresh()
+
+ # Get list of vetoed packages.
+ self.packages = []
+ self.veto_packages = []
+ self.add_packsfile(self.profile_dir, 'vetopacks', must=False)
+ self.veto_packages = self.packages
+
+ # Include 'required' packages (these can still be vetoed, but
+ # in some cases that will mean a larch system cannot be built)
+ self.packages = []
+ self.add_packsfile(base_dir + '/data', 'requiredpacks')
+
+ # Add additional packages and groups, from 'addedpacks' file.
+ self.add_packsfile(self.profile_dir, 'addedpacks')
+
+ # Now do the actual installation.
+ ok = self.pacmancall('-Sf', ' '.join(self.packages))
+ if not ok:
+ errout(_("Package installation failed"))
+
+ # Some chroot scripts might need /etc/mtab
+ runcmd('bash -c ":> %s/etc/mtab"' % self.installation_dir)
+
+ # Build the final version of pacman.conf
+ self.make_pacman_conf(True)
+ comment(" *** %s ***" % _("Arch installation completed"))
+ return True
+
+
+ def add_packsfile(self, dir, packs_file, must=True):
+ path = dir + '/' + packs_file
+ if must and not os.path.isfile(path):
+ errout(_("No '%s' file") % path)
+ fh = open(path)
+ for line in fh:
+ line = line.strip()
+ if line and (line[0] != '#'):
+ if line[0] == '*':
+ self.add_group(line[1:].split()[0])
+ elif line[0] == '+':
+ # Include directive
+ line = line[1:].split()[0]
+ if line[0] != '/':
+ line = dir + '/' + line
+ d, pf = line.rsplit('/', 1)
+ if not d:
+ errout(_("Invalid package file include: %s"))
+ self.add_packsfile(d, pf)
+ elif line.startswith('!!!'):
+ # Ignore everything (!) entered previously.
+ # Allows requiredpacks to be overridden in addedpacks.
+ self.packages = []
+ else:
+ line = line.split()[0]
+ if ((line not in self.packages)
+ and (line not in self.veto_packages)):
+ self.packages.append(line)
+ fh.close()
+
+
+ def add_group(self, gname):
+ """Add the packages belonging to a group to the installaion list,
+ removing any in the veto list.
+ """
+ # In the next line the call could be done as a normal user.
+ for line in runcmd('%s -Sg %s' % (self.pacman_cmd, gname))[1]:
+ l = line.split()
+ if l and (l[0] == gname) and (l[1] not in self.veto_packages):
+ self.packages.append(l[1])
+
+
+ def refresh(self):
+ """This updates or creates the pacman-db in the installation.
+ This is done using using 'pacman ... -Sy' together with the
+ customized pacman.conf file.
+ """
+ if not runcmd(self.pacman_cmd + ' -Sy',
+ filter=pacman_filter_gen())[0]:
+ errout(_("Couldn't synchronize pacman database (pacman -Sy)"))
+ return True
+
+
+ def pacmancall(self, op, arg):
+ """Mount-bind the sys and proc directories before calling the
+ pacman command built by make_pacman_command to perform operation
+ 'op' (e.g. '-S') with argument(s) 'arg' (a string).
+ Then unmount sys and proc and return True if the command succeeded.
+ """
+ # (a) Prepare the destination environment (bind mounts)
+ mount("/sys", "%s/sys" % self.installation_dir, "--bind")
+ mount("/proc", "%s/proc" % self.installation_dir, "--bind")
+
+ # (b) Call pacman
+ ok = runcmd("%s %s %s" % (self.pacman_cmd, op, arg),
+ filter=pacman_filter_gen())[0]
+
+ # (c) Remove bound mounts
+ unmount(("%s/sys" % self.installation_dir,
+ "%s/proc" % self.installation_dir))
+ return ok
+
+
+ def pacmanoptions(self, text):
+ """A filter for pacman.conf to remove the repository info.
+ """
+ texto = ""
+ block = ""
+ for line in text.splitlines():
+ block += line + "\n"
+ if line.startswith("#["):
+ break
+ if line.startswith("[") and not line.startswith("[options]"):
+ break
+ if not line.strip():
+ texto += block
+ block = ""
+ return texto
+
+
+ def sync(self, *packs):
+ return self.pacmancall('-S', ' '.join(packs))
+
+
+ def update(self, *files):
+ return self.pacmancall('-U', ' '.join(files))
+
+
+ def remove(self, *packs):
+ return self.pacmancall('-Rs', ' '.join(packs))
+
+
+
+if __name__ == "__main__":
+ start_translator()
+ cwd = os.getcwd()
+
+ operations = 'install|sync|update|remove|refresh'
+ from optparse import OptionParser, OptionGroup
+ parser = OptionParser(usage=(_("usage: %%prog [options] %s [packages]")
+ % operations))
+
+ parser.add_option("-p", "--profile", action="store", type="string",
+ default="", dest="profile",
+ help=_("Profile: 'user:profile-name' or path to profile directory"))
+ parser.add_option("-i", "--installation-dir", action="store", type="string",
+ default="", dest="idir",
+ help=_("Path to directory to be larchified (default %s)")
+ % INSTALLATION)
+ parser.add_option("-s", "--slave", action="store_true", dest="slave",
+ default=False, help=_("Run as a slave from a controlling program"
+ " (e.g. from a gui)"))
+ parser.add_option("-q", "--quiet", action="store_true", dest="quiet",
+ default=False, help=_("Suppress output messages, except errors"
+ " (no effect if -s specified)"))
+
+
+ parser.add_option("-f", "--force", action="store_true", dest="force",
+ default=False, help=_("Don't ask for confirmation"))
+
+ parser.add_option("-r", "--repofile", action="store", type="string",
+ default="", dest="repofile",
+ help=_("Supply a substitute repository list (pacman.conf.repos)"
+ " for the installation only"))
+ parser.add_option("-c", "--cache-dir", action="store", type="string",
+ default="", dest="cache",
+ help=_("pacman cache directory (default /var/cache/pacman/pkg)"))
+ parser.add_option("-n", "--noprogress", action="store_true",
+ dest="noprogress",
+ default=False, help=_("Don't show pacman's progress bar"))
+## I think pacman is going to get support for something like '$arch', at
+## which stage I could again consider architecture switching support in larch.
+# parser.add_option("-a", "--arch", action="store", type="string",
+# default="", dest="arch",
+# help=_("processor architecture (x86_64|i686) - defaults to"
+# " that of the host."
+# " This is an untested feature, which is probably only partially"
+# " implemented and may well not work."))
+
+ (options, args) = parser.parse_args()
+ if not args:
+ print _("You must specify which operation to perform:\n")
+ parser.print_help()
+ sys.exit(1)
+
+ if os.getuid() != 0:
+ print _("This application must be run as root")
+ sys.exit(1)
+
+ init('archin', options)
+ op = args[0]
+ if op not in operations.split('|'):
+ print (_("Invalid operation: '%s'\n") % op)
+ parser.print_help()
+ sys.exit(1)
+
+ installation = Installation(options)
+ method = getattr(installation, op)
+
+ if method(*args[1:]):
+ sys.exit(0)
+ else:
+ sys.exit(1)
+
diff --git a/build_tools/l7/larch0/cli/backend.py b/build_tools/l7/larch0/cli/backend.py
new file mode 100644
index 0000000..95b01bd
--- /dev/null
+++ b/build_tools/l7/larch0/cli/backend.py
@@ -0,0 +1,444 @@
+# backend.py - for the cli modules: handles processes and io
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# larch 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.14
+
+# There was also the vague idea of a web interface, using a sort of state-
+# based approach. Connecting to a running larch process would then require
+# the ability to get the logging history, but presumably not the whole
+# history on every ui update, which would need to be incremental.
+# The logging function would need to be modified to accommodate this.
+
+import os, sys, signal, atexit, __builtin__
+import traceback, pwd
+from subprocess import Popen, PIPE, STDOUT
+import pexpect
+try:
+ import json as serialize
+except:
+ import simplejson as serialize
+from config import *
+
+def init(app, options, app_quit=None):
+ global _options, _quit_function, _log, _controlled, _dontask, _quiet
+ _options = options
+ _quit_function = app_quit
+ _controlled = options.slave
+ _dontask = options.force
+ _log = None
+ _quiet = False if _controlled else options.quiet
+
+ atexit.register(sys_quit)
+
+
+ def sigint(num, frame):
+ """A handler for SIGINT. Tidy up properly and quit.
+ """
+ errout("INTERRUPTED - killing subprocesses", 0)
+ if _sub_process and _sub_process.pid:
+ Popen(["pkill", "-g", str(_sub_process.pid)],
+ stdout=PIPE).communicate()
+ errout("QUITTING", 2)
+ signal.signal(signal.SIGINT, sigint)
+
+
+ # Check no other instance of the script is running
+ if os.path.isfile(LOCKFILE):
+ app0 = readfile(LOCKFILE)
+ if not query_yn(_(
+ "larch (%s) seems to be running already."
+ "\nIf you are absolutely sure this is not the case,"
+ "\nyou may continue. Otherwise you should cancel."
+ "\n\nShall I continue?") % app0):
+ sys.exit(102)
+ writefile(app, LOCKFILE)
+ _log = open(LOGFILE + app, 'w')
+
+ # For systems without /sbin and /usr/sbin in the normal PATH
+ p = os.environ['PATH']
+ ps = p.split(':')
+ for px in ('/sbin', '/usr/sbin'):
+ if px not in ps:
+ p = px + ':' + p
+ os.environ['PATH'] = p
+
+
+def _out(text, force=False):
+ """Send the string to standard output.
+ How it is output depends on the '-s' command line option (whether the
+ script is being run on the console or as a subprocess of another script).
+ In the latter case the text will be slightly encoded - to avoid newline
+ characters - and sent as a single unit.
+ Otherwise output the lines as they are, but all lines except
+ the first get a '--' prefix.
+ """
+ lines = text.encode('utf-8').splitlines()
+ if _log:
+ _log.write(lines[0] + '\n')
+ for l in lines[1:]:
+ _log.write('--' + l + '\n')
+
+ if force or not _quiet:
+ if _controlled:
+ sys.stdout.write(serialize.dumps(text) + '\n')
+ else:
+ prefix = ''
+ for line in lines:
+ sys.stdout.write(prefix + line + '\n')
+ prefix = '--'
+ sys.stdout.flush()
+
+
+def sys_quit():
+ unmount()
+ if _quit_function:
+ _quit_function()
+ if _errorcount:
+ _out('!! ' + (_("The backend reported %d failed calls,"
+ " you may want to investigate") % _errorcount))
+ if _log:
+ _log.close()
+ os.remove(LOCKFILE)
+
+
+def comment(text):
+ _out('##' + text)
+
+
+def query_yn(message, default=False):
+ _out('?>' + message)
+ result = default
+ if _dontask:
+ result = True
+ elif not _quiet:
+ if _controlled:
+ result = (raw_input().strip() == '??YES')
+
+ else:
+ # The character after '_' is the response key
+ # The default will be capitalized automatically
+ prompt = _("_yes|_no").split('|')
+ promptkey = [word[word.index('_') + 1] for word in prompt]
+ if default:
+ py = prompt[0].upper()
+ pn = prompt[1]
+ else:
+ py = prompt[0]
+ pn = prompt[1].upper()
+ resp = raw_input(" [ %s / %s ]: " % (py, pn)).strip()
+ if resp:
+ testkey = promptkey[1] if default else promptkey[0]
+ resp == resp.lower()
+ if resp == prompt[0]:
+ result = True
+ elif resp == prompt[1]:
+ result = False
+ elif testkey in resp:
+ result = not default
+
+ _out('#>%s' % ('Yes' if result else 'No'))
+ return result
+
+
+def errout(message="ERROR", quit=1):
+ _out('!>' + message, True)
+ if quit:
+ sys_quit()
+ os._exit(quit)
+
+
+def error0(message):
+ errout(message, 0)
+__builtin__.error0 = error0
+
+
+# Catch all unhandled errors.
+def errortrap(type, value, tb):
+ etext = "".join(traceback.format_exception(type, value, tb))
+ errout(_("Something went wrong:\n") + etext, 100)
+sys.excepthook = errortrap
+
+
+_sub_process = None
+_errorcount = 0
+def runcmd(cmd, filter=None):
+ global _sub_process, _errorcount
+ _out('>>' + cmd)
+ _sub_process = pexpect.spawn(cmd)
+ result = []
+ line0 = ''
+ # A normal end-of-line is '\r\n', so split on '\r' but don't
+ # process a line until the next character is available.
+ while True:
+ try:
+ line0 += _sub_process.read_nonblocking(size=256, timeout=None)
+ except:
+ break
+
+ while True:
+ lines = line0.split('\r', 1)
+ if (len(lines) > 1) and lines[1]:
+ line = lines[0]
+ line0 = lines[1]
+ nl = (line0[0] == '\n')
+ if nl:
+ # Strip the '\n'
+ line0 = line0[1:]
+ if filter:
+ line = filter(line, nl)
+ if line == '/*/':
+ continue
+ if nl:
+ line = line.rstrip()
+ _out('>_' + line)
+ result.append(line)
+ else:
+ # Probably a progress line
+ if _controlled:
+ _out('>-' + line)
+ else:
+ sys.stdout.write(line + '\r')
+ sys.stdout.flush()
+
+ else:
+ break
+
+ _sub_process.close()
+ rc = _sub_process.exitstatus
+ ok = (rc == 0)
+ if not ok:
+ _errorcount += 1
+ _out(('>?%s' % repr(rc)) + ('' if ok else (' $$$ %s $$$' % cmd)))
+ return (ok, result)
+
+
+def script(cmd):
+ s = runcmd("%s/%s" % (script_dir, cmd))
+ if s[0]:
+ return ""
+ else:
+ return "SCRIPT ERROR: (%s)\n" % cmd + "".join(s[1])
+
+
+def chroot(ip, cmd, mnts=[], filter=None):
+ if ip:
+ for m in mnts:
+ mount("/" + m, "%s/%s" % (ip, m), "--bind")
+ cmd = "chroot %s %s" % (ip, cmd)
+
+ s = runcmd(cmd, filter)
+
+ if ip:
+ unmount(["%s/%s" % (ip, m) for m in mnts])
+
+ if s[0]:
+ if s[1]:
+ return s[1]
+ else:
+ return True
+ return False
+
+
+_mounts = []
+def mount(src, dst, opts=""):
+ if runcmd("mount %s %s %s" % (opts, src, dst))[0]:
+ _mounts.append(dst)
+ return True
+ return False
+
+
+def unmount(dst=None):
+ if dst == None:
+ mnts = list(_mounts)
+ elif type(dst) in (list, tuple):
+ mnts = list(dst)
+ else:
+ mnts = [dst]
+
+ r = True
+ for m in mnts:
+ if runcmd("umount %s" % m)[0]:
+ _mounts.remove(m)
+ else:
+ r = False
+ return r
+
+
+def get_installation_dir():
+ return os.path.realpath(_options.idir if _options.idir
+ else INSTALLATION)
+
+
+def get_profile():
+ """Get the absolute path to the profile folder given its path in any
+ acceptable form, including 'user:profile-name'
+ """
+ pd = (_options.profile if _options.profile
+ else base_dir + '/profiles/default')
+ p = pd.split(':')
+ if len(p) == 1:
+ pd = os.path.realpath(pd)
+ else:
+ try:
+ pd = (pwd.getpwnam(p[0])[5] + PROFILE_DIR
+ + '/' + p[1])
+ except:
+ errout(_("Invalid profile: %s") % pd, quit=0)
+ raise
+ if not os.path.isfile(pd + '/addedpacks'):
+ errout(_("Invalid profile folder: %s") % pd)
+ return pd
+
+
+
+#+++++++++++++++++++++++++++++++++++++++++
+#Regular expression search strings for progress reports
+import re
+#lit: give []() a \-prefix
+#grp: surround string in ()
+#opt: surround string in []
+
+def _lit(s):
+ for c in r'[()]':
+ s = s.replace(c, '\\' + c)
+ return s
+
+def _grp(s, x=''):
+ return '(' + s + ')' + x
+
+def _grp0(s, x=''):
+ return '(?:' + s + ')' + x
+
+def _opt(s, x=''):
+ return '[' + s + ']' + x
+
+
+_re_pacman = re.compile( _grp0(_lit('(') +
+ _grp(_opt('^/', '+') + '/' + _opt('^)', '+')) +
+ _lit(')'), '?') +
+ _grp('.*?') +
+ _lit('[') + _grp(_opt('-#', '+')) + _lit(r']\s+') +
+ _grp(_opt('0-9', '+')) +
+ '%'
+ )
+
+_re_mksquashfs = re.compile(_lit('[.*]') +
+ _grp('.* ' +
+ _grp(_opt('0-9', '+')) +
+ '%')
+ )
+
+_re_mkisofs = re.compile(_opt(' 1') + _opt(' \d') + '\d\.\d\d%')
+
+#-----------------------------------------
+class pacman_filter_gen:
+ """Return a function to detect and process the progress output of
+ pacman.
+ """
+ def __init__(self):
+ self.progress = ''
+
+ def __call__(self, line, nl):
+ ms = _re_pacman.match(line)
+ if ms:
+ p = ms.group(3)
+ if (self.progress != p) or nl:
+ self.progress = p
+ if _controlled:
+ xfromy = ms.group(1)
+ if not xfromy:
+ xfromy = ''
+ return 'pacman:%s|%s|%s' % (xfromy, ms.group(2),
+ ms.group(4))
+ if nl:
+ sys.stdout.write(' '*80 + '\r')
+ return line.rsplit(None, 1)[0]
+ else:
+ return '/*/'
+ return line
+
+
+class mksquashfs_filter_gen:
+ """Return a function to detect and process the progress output of
+ mksquashfs.
+ """
+ def __init__(self):
+ self.progress = ''
+
+ def __call__(self, line, nl):
+ ms = _re_mksquashfs.match(line)
+ if ms:
+ percent = ms.group(2)
+ if (self.progress != percent) or nl:
+ self.progress = percent
+ if _controlled:
+ return 'mksquashfs:' + ms.group(1)
+ return re.sub(r'=[-\\/|]', '= ', line)
+ else:
+ return '/*/'
+ return line
+
+
+class mkisofs_filter_gen:
+ """Return a function to detect and process the progress output of
+ mkisofs.
+ """
+ def __call__(self, line, nl):
+ ms = _re_mkisofs.match(line)
+ if ms:
+ if _controlled:
+ return 'mkisofs:' + line
+ sys.stdout.write(line + '\r')
+ sys.stdout.flush()
+ return '/*/'
+ return line
+
+
+def readdata(filename):
+ return readfile(base_dir + '/data/' + filename)
+
+
+def readfile(fpath):
+ try:
+ fh = open(fpath)
+ text = fh.read()
+ fh.close()
+ except:
+ errout(_("Couldn't read file: %s") % fpath)
+ return None
+ return text
+
+
+def writefile(text, path):
+ try:
+ pd = os.path.dirname(path)
+ if not os.path.isdir(pd):
+ os.makedirs(pd)
+ fh = None
+ fh = open(path, 'w')
+ fh.write(text)
+ return True
+ except:
+ return False
+ finally:
+ if fh:
+ fh.close()
+
diff --git a/build_tools/l7/larch0/cli/boot_iso.py b/build_tools/l7/larch0/cli/boot_iso.py
new file mode 100755
index 0000000..19c3510
--- /dev/null
+++ b/build_tools/l7/larch0/cli/boot_iso.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+#
+# live_part.py
+#
+# (c) Copyright 2009 - 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# larch 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.12
+
+"""This is a command line script to prepare a boot 'iso' for a USB larch
+device, to make this bootable via CD on systems which cannot boot from USB
+devices.
+Parameters are passed as options and arguments.
+"""
+
+import os
+from config import *
+from backend import *
+from media_common import *
+
+def build_bootiso(options, devicename):
+ """Create a boot iso for the specified medium.
+ 'devicename' is the name (e.g. 'sdb1', without '/dev/') of the
+ source partition.
+ """
+ # Check device
+ device = '/dev/' + devicename
+ if not os.path.exists(device):
+ errout(_("Invalid device: %s") % device)
+ options.source = device
+ medium = Medium(options)
+ ipath = medium.chrootpath
+ build = medium.build
+
+ # Need to get the label
+ label = medium.get_device_label(device)
+
+ # Write bootloader configuration file
+ bootconfig(build, options.syslinux, label, device, options.detection)
+
+ # Select bootloader
+ if options.syslinux:
+ # Get fresh isolinux.bin
+ runcmd('cp %s/boot/isolinux/isolinux.bin0 %s/boot/isolinux/isolinux.bin'
+ % (build, build))
+ parms = '-b boot/isolinux/isolinux.bin -c boot/isolinux/isolinux.boot'
+ # Select bootloader
+ else:
+ parms = '-b boot/grub/stage2_eltorito'
+
+ # Need to adjust paths to cater for chroot!
+ source0 = medium.chroot_medium_dir()
+ isopath0 = medium.iso_path()
+
+ # Actually the volume label can be 32 bytes, but 16 is compatible
+ # with ext2 (though a little longer than vfat)
+ label = check_label(options.label, 16)
+ # Build iso
+ if not chroot(ipath, ('mkisofs -R -l %s -no-emul-boot -boot-load-size 4'
+ ' -boot-info-table -input-charset=UTF-8'
+ ' -V "%s"'
+ ' -o "%s"'
+ ' "%s"') % (parms, label, isopath0, BUILD0),
+ filter=mkisofs_filter_gen()):
+
+ errout(_("iso build failed"))
+
+ medium.unmount()
+ runcmd('rm -rf %s' % build)
+
+ comment(" *** %s ***" % (_("%s was successfully created")
+ % (options.isofile)))
+
+
+
+if __name__ == '__main__':
+ start_translator()
+
+ from optparse import OptionParser, OptionGroup
+ parser = OptionParser(usage=_("usage: %prog [options] partition"
+ " (e.g. sdb1)"))
+
+ parser.add_option("-o", "--isofile", action="store", type="string",
+ default=BOOTISO, dest="isofile",
+ help=_("Specify the output file (default '%s'). It will be"
+ " generated to the current directory.") % BOOTISO)
+ parser.add_option("-D", "--setdir", action="store", type="string",
+ dest='setdir', default='',
+ help=_("Set current directory,"
+ " so that the 'iso' can be placed there"))
+ parser.add_option("-d", "--detect", action="store", type="string",
+ default="label", dest="detection",
+ help=(_("Method for boot partition detection: %s (default: label)")
+ % detection_methods))
+ parser.add_option("-b", "--syslinux", action="store_true", dest="syslinux",
+ default=False, help=_("Use the syslinux bootloader"
+ " (the default is GRUB)"))
+
+ parser.add_option("-i", "--installation-dir", action="store", type="string",
+ default="", dest="idir",
+ help=_("Path to larchified directory (default %s)") % INSTALLATION)
+ parser.add_option("-s", "--slave", action="store_true", dest="slave",
+ default=False, help=_("Run as a slave from a controlling program"
+ " (e.g. from a gui)"))
+ parser.add_option("-q", "--quiet", action="store_true", dest="quiet",
+ default=False, help=_("Suppress output messages, except errors"
+ " (no effect if -s specified)"))
+
+ parser.add_option("-f", "--force", action="store_true", dest="force",
+ default=False, help=_("Don't ask for confirmation"))
+
+ parser.add_option("-l", "--label", action="store", type="string",
+ default=ISOLABEL, dest="label",
+ help=_("Volume label for boot iso (default %s)")
+ % ISOLABEL)
+ parser.add_option("-C", "--chroot", action="store_true",
+ dest="chroot", default=False,
+ help=_("Use chroot for build"))
+
+
+ (options, args) = parser.parse_args()
+ if not args:
+ print _("You must specify the source partition\n")
+ parser.print_help()
+ sys.exit(1)
+
+ if os.getuid() != 0:
+ print _("This application must be run as root")
+ sys.exit(1)
+
+ init('live_part', options)
+
+ # To avoid error messages
+ options.profile = None
+ options.nochroot = None
+ options.testmedium = False
+
+ build_bootiso(options, devicename=args[0])
diff --git a/build_tools/l7/larch0/cli/config.py b/build_tools/l7/larch0/cli/config.py
new file mode 100644
index 0000000..c62cf9d
--- /dev/null
+++ b/build_tools/l7/larch0/cli/config.py
@@ -0,0 +1,92 @@
+# config.py
+#
+# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# larch 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.13
+
+# Basic (default) configuration information for larch
+# Some of these can be changed ...????
+
+# Default volume label
+LABEL = 'LARCH-7.2'
+# Default iso file
+ISOFILE = 'larch7.iso'
+# Default volume label for boot iso
+ISOLABEL = 'LARCH-7-BOOT'
+# Default boot iso file
+BOOTISO = 'larch7boot.iso'
+# Used as a mount point
+MP = '/tmp/larch_mnt'
+# Mount point (within chroot) for bind-mounting iso build directory
+ISOBUILDMNT = '/tmp/isodir_mnt'
+# Mount point for medium sources
+SOURCEMOUNT = '/tmp/larch_mntsrc'
+# Temporary directory for building medium boot directory
+BUILD0 = '/tmp/larch_build'
+
+SYSLINUXDIR = '/usr/lib/syslinux'
+GRUBDIR = '/usr/lib/grub/i386-pc'
+
+# A customized pacman.conf file is used for the installation, generated
+# dynamically according to the options and the profile.
+PACMAN_CONF = '/tmp/larch_pacman.conf'
+
+# Medium detection alternatives
+detection_methods = 'label|uuid|device|search'
+
+# Some basic paths
+import os, sys
+module_dir = os.path.dirname(os.path.realpath(__file__))
+base_dir = os.path.dirname(module_dir)
+script_dir = base_dir + '/buildscripts'
+
+# File to prevent two instances of larch from running
+LOCKFILE = '/tmp/larch_lock'
+# File (stem) for log
+LOGFILE = '/tmp/larch_log_'
+# The path to the Arch installation which is to be larchified
+INSTALLATION = '/home/larchbuild'
+
+# These paths are intended for use in 'chroot installation_dir', etc.
+# The base directory of the larchified stuff
+CHROOT_DIR_BUILD = '/.larch'
+# This is the base of all stuff to be cleared on a rerun of larchify
+CHROOT_DIR_LARCHIFY = CHROOT_DIR_BUILD + '/larchify'
+# The base directory of the medium building area
+CHROOT_DIR_MEDIUM = CHROOT_DIR_LARCHIFY + '/medium'
+# Area for building the (mods.sqf) overlay
+CHROOT_DIR_OVERLAY = CHROOT_DIR_LARCHIFY + '/overlay'
+# Location for saving the system.sqf (outside of the larchify area)
+CHROOT_SYSTEMSQF = CHROOT_DIR_BUILD + '/system.sqf'
+
+def debug(text):
+ sys.stderr.write("DEBUG: " + text.strip() + "\n")
+ sys.stderr.flush()
+
+def start_translator(switchC=True):
+ import gettext, __builtin__
+ gettext.install('larch', base_dir+'/i18n', unicode=1)
+ __builtin__.lang = (os.environ.get("LANGUAGE") or os.environ.get("LC_ALL")
+ or os.environ.get("LC_MESSAGES") or os.environ.get("LANG"))
+ if switchC:
+ # If subprocesses must be run without i18n because the text
+ # output is parsed.
+ os.environ["LANGUAGE"] = "C"
+
diff --git a/build_tools/l7/larch0/cli/larchify.py b/build_tools/l7/larch0/cli/larchify.py
new file mode 100755
index 0000000..15a4d6a
--- /dev/null
+++ b/build_tools/l7/larch0/cli/larchify.py
@@ -0,0 +1,588 @@
+#!/usr/bin/env python
+#
+# larchify.py
+#
+# (c) Copyright 2009-2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# larch 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.13
+
+# This is a command line script to prepare a larch live system from an
+# Arch Linux installation. All needed parameters are passed as options.
+
+import os, sys
+from config import *
+from backend import *
+from userinfo import Userinfo
+from glob import glob
+import random, crypt
+from subprocess import Popen, PIPE, STDOUT
+
+
+class Builder:
+ """This class manages 'larchifying' an Arch Linux installation.
+ """
+ def __init__(self, options):
+ self.installation_dir = get_installation_dir()
+ self.installation0 = (self.installation_dir
+ if self.installation_dir != "/" else "")
+ testfile = self.installation0 + '/etc/pacman.conf'
+ if not os.path.isfile(testfile):
+ errout(_("File '%s' doesn't exist:\n"
+ " '%s' not an Arch installation?")
+ % (testfile, self.installation_dir))
+
+ self.profile_dir = get_profile()
+
+
+ def build(self, options):
+ if not (self.installation0 or query_yn(_(
+ "Building a larch live medium from the running system is"
+ "\nan error prone process. Changes to the running system"
+ "\nmade while running this function may be only partially"
+ "\nincorporated into the compressed system images."
+ "\n\nDo you wish to continue?")), True):
+ return False
+
+ # Define the working area - it must be inside the installation
+ # because of the use of chroot for some functions
+ self.larchify_dir = self.installation0 + CHROOT_DIR_LARCHIFY
+ # Location for the live medium image
+ self.medium_dir = self.installation0 + CHROOT_DIR_MEDIUM
+ # And potentially a saved system.sqf
+ self.system_sqf = self.installation0 + CHROOT_SYSTEMSQF
+ # Needed for a potentially saved locales directory
+ self.locales_base = self.installation0 + CHROOT_DIR_BUILD
+ # For building the (mods.sqf) overlay
+ self.overlay = self.installation0 + CHROOT_DIR_OVERLAY
+ comment("Initializing larchify process")
+
+ if options.oldsqf:
+ if os.path.isfile(self.medium_dir + "/larch/system.sqf"):
+ runcmd("mv %s/larch/system.sqf %s" %
+ (self.medium_dir, self.system_sqf))
+ else:
+ runcmd("rm -f %s" % self.system_sqf)
+
+ # Clean out larchify area and create overlay directory
+ runcmd('rm -rf %s' % self.larchify_dir)
+ runcmd('mkdir -p %s' % self.overlay)
+
+ if not self.find_kernel():
+ return False
+
+ if not self.system_check():
+ return False
+
+ comment("Beginning to build larch medium files")
+ # Clear out the directory
+ runcmd('rm -rf %s' % self.medium_dir)
+ # The base medium boot directory, bootloader independent.
+ runcmd('mkdir -p %s/boot' % self.medium_dir)
+ # The main larch direcory
+ runcmd('mkdir -p %s/larch' % self.medium_dir)
+
+ # kernel
+ runcmd("cp -f %s/boot/%s %s/boot/larch.kernel" %
+ (self.installation0, self.kname, self.medium_dir))
+ # Remember file name (to ease update handling)
+ runcmd('bash -c "echo \'%s\' > %s/larch/kernelname"'
+ % (self.kname, self.medium_dir))
+
+ # if no saved system.sqf, squash the Arch installation at self.installation_dir
+ if not os.path.isfile(self.system_sqf):
+ comment("Generating system.sqf")
+ # root directories which are not included in the squashed system.sqf
+ ignoredirs = "boot dev mnt media proc sys tmp .livesys "
+ ignoredirs += CHROOT_DIR_BUILD.lstrip("/")
+ # /var stuff
+ ignoredirs += " var/log var/tmp var/lock"
+ # others
+ ignoredirs += " usr/lib/locale"
+
+ # Additional directories to ignore can also be specified in the
+ # profile. This is a nasty option. It was requested, and might
+ # be useful under certain special circumstances, but I recommend
+ # not using it unless you are really sure what you are doing.
+ veto_file = self.profile_dir + '/vetodirs'
+ if os.path.isfile(veto_file):
+ fh = open(veto_file)
+ for line in fh:
+ line = line.strip()
+ if line and (line[0] != '#'):
+ ignoredirs += ' ' + line.lstrip('/')
+ fh.close()
+
+ if not chroot(self.installation0,
+ "/sbin/mksquashfs '/' '%s' -e %s"
+ % (CHROOT_SYSTEMSQF, ignoredirs),
+ filter=mksquashfs_filter_gen()):
+ errout(_("Squashing system.sqf failed"))
+ # remove execute attrib
+ runcmd("chmod oga-x %s" % self.system_sqf)
+
+ # move system.sqf to medium directory
+ runcmd("mv %s %s/larch" % (self.system_sqf, self.medium_dir))
+
+ # prepare overlay
+ comment("Generating larch overlay")
+ # Copy over the overlay from the selected profile
+ if os.path.isdir("%s/rootoverlay" % self.profile_dir):
+ runcmd('bash -c "cp -rf %s/rootoverlay/* %s"'
+ % (self.profile_dir, self.overlay))
+ # Ensure there is an /etc directory in the overlay
+ runcmd("mkdir -p %s/etc" % self.overlay)
+ # fix sudoers if any
+ if os.path.isfile("%s/etc/sudoers" % self.overlay):
+ runcmd("chmod 0440 %s/etc/sudoers" % self.overlay)
+ runcmd("chown root:root %s/etc/sudoers" % self.overlay)
+
+ # Prepare inittab
+ inittab = self.overlay + "/etc/inittab"
+ itsave = inittab + ".larchsave"
+ it0 = self.installation0 + "/etc/inittab"
+ itl = self.overlay + "/etc/inittab.larch"
+ if not os.path.isfile(itl):
+ itl = self.installation0 + "/etc/inittab.larch"
+ if not os.path.isfile(itl):
+ itl = None
+ # Save the original inittab if there is an inittab.larch file,
+ # ... if there isn't already a saved one
+ if itl:
+ if ((not os.path.isfile(it0 + ".larchsave"))
+ and (not os.path.isfile(itsave))):
+ runcmd("cp %s %s" % (it0, itsave))
+ # Use the .larch version in the live system
+ runcmd("cp -f %s %s" % (itl, inittab))
+
+ comment("Generating larch initcpio")
+ if not self.gen_initramfs():
+ return False
+
+ lpath = self.locales_base + '/locale'
+ if self.installation0:
+ if options.oldlocales and os.path.isdir(lpath):
+ comment("Copying saved glibc locales")
+ runcmd('rm -rf %s/usr/lib/locale' % self.overlay)
+ runcmd('mkdir -p %s/usr/lib' % self.overlay)
+ runcmd('cp -a %s %s/usr/lib' % (lpath, self.overlay))
+ else:
+ comment("Generating glibc locales")
+ runcmd('rm -rf %s' % lpath)
+ script('larch-locales "%s" "%s"' % (self.installation0,
+ self.overlay))
+ # Save the generated locales for possible reuse
+ runcmd('cp -a %s/usr/lib/locale %s' % (self.overlay,
+ self.locales_base))
+
+ if (os.path.isfile(self.installation0 + '/usr/bin/ssh-keygen')
+ and not os.path.isfile(self.profile_dir + '/nosshkeys')):
+ # ssh initialisation - done here so that it doesn't need to
+ # be done when the live system boots
+ comment("Generating ssh keys to overlay")
+ sshdir = CHROOT_DIR_OVERLAY + "/etc/ssh"
+ runcmd("mkdir -p %s" % (self.installation0 + sshdir))
+ for k, f in [("rsa1", "ssh_host_key"), ("rsa", "ssh_host_rsa_key"),
+ ("dsa", "ssh_host_dsa_key")]:
+ chroot(self.installation0,
+ "ssh-keygen -t %s -N '' -f %s/%s"
+ % (k, sshdir, f), ["dev"])
+
+ # Ensure the hostname is in /etc/hosts
+ script("larch-hosts %s %s" % (self.installation0, self.overlay))
+
+ # Handle /mnt
+ runcmd("mkdir -p %s/mnt" % self.overlay)
+ for d in os.listdir("%s/mnt" % self.installation0):
+ if os.path.isdir("%s/mnt/%s" % (self.installation0, d)):
+ runcmd("mkdir %s/mnt/%s" % (self.overlay, d))
+
+ # Ensure there is a /boot directory
+ runcmd("mkdir -p %s/boot" % self.overlay)
+
+ # Run customization script
+ tweak = self.profile_dir + '/build-tweak'
+ if os.path.isfile(tweak):
+ comment("(WARNING): Running user's build customization script")
+ if runcmd(tweak + ' %s %s' % (self.installation0,
+ self.overlay))[0]:
+ comment("Customization script completed")
+ else:
+ errout(_("Build customization script failed"))
+
+ # Get root password
+ rootpwf = self.profile_dir + '/rootpw'
+ if os.path.isfile(rootpwf):
+ rootpw = readfile(rootpwf).strip()
+ if rootpw == '!':
+ # Lock the password
+ rootcmd = 'usermod -L'
+ else:
+ rootcmd = "usermod -p '%s'" % encryptPW(rootpw)
+ else:
+ rootcmd = None
+
+ # Add users and set root password
+ if self.installation0 and not self.add_users(rootcmd):
+ return False
+
+ comment("Squashing mods.sqf")
+ if not chroot(self.installation0,
+ "/sbin/mksquashfs '%s' '%s/larch/mods.sqf'"
+ % (CHROOT_DIR_OVERLAY, CHROOT_DIR_MEDIUM),
+ filter=mksquashfs_filter_gen()):
+ errout(_("Squashing mods.sqf failed"))
+ # remove execute attrib
+ runcmd("chmod oga-x %s/larch/mods.sqf" % self.medium_dir)
+
+ runcmd("rm -rf %s" % self.overlay)
+
+ comment(" *** %s ***" % _("larchify-process completed"))
+ return True
+
+
+ def add_users(self, rootcmd):
+ userinfo = Userinfo(self.profile_dir)
+ userlist = []
+ for user in userinfo.allusers():
+ # Only include if the user does not yet exist
+ if runcmd('bash -c "grep \"^%s\" %s/etc/passwd || echo -"'
+ % (user, self.installation_dir))[1][0] != '-':
+ comment("(WARNING): User '%s' exists already" % user)
+ else:
+ userlist.append(user)
+
+ # Only continue if there are new users in the list
+ if rootcmd:
+ clist = [('root', rootcmd + ' %s')]
+ else:
+ if userlist == []:
+ return True
+ clist = []
+
+ # Save system files and replace them by the overlay versions
+ savedir = self.larchify_dir + '/save_etc'
+ runcmd('rm -rf %s' % savedir)
+ runcmd('mkdir -p %s/default' % savedir)
+ savelist = 'group,gshadow,passwd,shadow,login.defs,skel'
+ runcmd('bash -c "cp -a %s/etc/{%s} %s"'
+ % (self.installation0, savelist, savedir))
+ runcmd('cp -a %s/etc/default/useradd %s/default'
+ % (self.installation0, savedir))
+ for f in ('group', 'gshadow', 'passwd', 'shadow', 'login.defs'):
+ if os.path.isfile(self.overlay + '/etc/%s'):
+ runcmd('cp %s/etc/%s %s/etc'
+ % (self.overlay, f, self.installation0))
+ if os.path.isfile(self.overlay + '/etc/default/useradd'):
+ runcmd('cp %s/etc/default/useradd %s/etc/default'
+ % (self.overlay, self.installation0))
+ if os.path.isdir(self.overlay + '/etc/skel'):
+ runcmd('cp -r %s/etc/skel %s/etc'
+ % (self.overlay, self.installation0))
+
+ # Build the useradd command
+ userdir0 = '/users'
+ userdir = self.larchify_dir + userdir0
+ userdirs = []
+ runcmd('mkdir -p %s/home' % self.overlay)
+ for u in userlist:
+ cline = 'useradd -m'
+ pgroup = userinfo.get(u, 'maingroup')
+ if pgroup:
+ cline += ' -g ' + pgroup
+ uid = userinfo.get(u, 'uid')
+ if uid:
+ cline += ' -u ' + uid
+ pw = userinfo.get(u, 'pw')
+ if (pw == ''):
+ # Passwordless login
+ pwcrypt = ''
+ else:
+ # Normal MD5 password
+ pwcrypt = encryptPW(pw)
+ cline += " -p '%s'" % pwcrypt
+ skeldir = userinfo.get(u, 'skel')
+ if skeldir:
+ # Custom home initialization directories in the profile
+ # always start with 'skel_'
+ skel = 'skel_' + skeldir
+ if skel not in userdirs:
+ userdirs.append(skel)
+ cline += ' -k %s/%s' % (CHROOT_DIR_LARCHIFY + userdir0,
+ skel)
+ # Allow for expert tweaking
+ cline += ' ' + userinfo.get(u, 'expert')
+ # The user and the command to be run
+ clist.append((u, cline + ' %s'))
+ xgroups = userinfo.get(u, 'xgroups')
+ if xgroups:
+ xgl = []
+ for g in xgroups.split(','):
+ clist.append((u, 'usermod -a -G %s %%s' % g))
+
+ if userdirs:
+ # Copy custom 'skel' directories to build space
+ runcmd('rm -rf %s' % userdir)
+ runcmd('mkdir -p %s' % userdir)
+ for ud in userdirs:
+ runcmd('cp -r %s/%s %s/%s' %
+ (self.profile_dir, ud, userdir, ud))
+
+ nfail = 0
+ ok = True
+ for u, cmd in clist:
+ if not chroot(self.installation0, cmd % u):
+ nfail += 1
+ # Errors adding users to groups are not fatal:
+ if not cmd.startswith('usermod -a -G'):
+ ok = False
+ if os.path.isdir('%s/home/%s' % (self.installation0, u)):
+ runcmd('mv %s/home/%s %s/home'
+ % (self.installation0, u, self.overlay))
+
+ if nfail > 0:
+ errout(_("%d user account operation(s) failed") % nfail, 0)
+ # Move changed /etc/{group,gshadow,passwd,shadow} to overlay
+ runcmd('bash -c "mv %s/etc/{group,gshadow,passwd,shadow} %s/etc"'
+ % (self.installation0, self.overlay))
+ # Restore system files in base installation
+ runcmd('rm -rf %s/etc/skel' % self.installation0)
+ runcmd('bash -c "cp -a %s/* %s/etc"'
+ % (savedir, self.installation0))
+ return ok
+
+
+ def system_check(self):
+ comment("Testing for necessary packages and kernel modules")
+ fail = ""
+ warn = ""
+ nplist = ["larch-live"]
+
+ mdep = (self.installation0 +
+ "/lib/modules/%s/modules.dep" % self.kversion)
+ if Popen(["grep", "/squashfs.ko", mdep], stdout=PIPE,
+ stderr=STDOUT).wait() != 0:
+ fail += _("No squashfs module found\n")
+
+ if Popen(["grep", "/aufs.ko", mdep], stdout=PIPE,
+ stderr=STDOUT).wait() == 0:
+ self.ufs='_aufs'
+ nplist.append("aufs2-util")
+
+ elif Popen(["grep", "/unionfs.ko", mdep], stdout=PIPE,
+ stderr=STDOUT).wait() == 0:
+ self.ufs='_unionfs'
+
+ else:
+ fail += _("No aufs or unionfs module found\n")
+
+ for p in nplist:
+ if not self.haspack(p):
+ fail += _("Package '%s' is needed by larch systems\n") % p
+
+ if not self.haspack("syslinux"):
+ warn += _("Without package 'syslinux' you will not be able\n"
+ "to create syslinux or isolinux booting media\n")
+
+ if (not self.haspack("cdrkit")) and (not self.haspack("cdrtools")):
+ warn += _("Without package 'cdrkit' (or 'cdrtools') you will\n"
+ "not be able to create CD/DVD media\n")
+
+ if not self.haspack("eject"):
+ warn += _("Without package 'eject' you will have problems\n"
+ "using CD/DVD media\n")
+
+ if warn:
+ cont = query_yn(_("WARNING:\n%s"
+ "\n Continue building?") % warn)
+ else:
+ cont = True
+
+ if fail:
+ errout(_("ERROR:\n%s") % fail)
+
+ return cont
+
+
+ def haspack(self, package):
+ """Check whether the given package is installed.
+ """
+ for p in os.listdir(self.installation0 + '/var/lib/pacman/local'):
+ if p.rsplit("-", 2)[0] == package:
+ return True
+ return False
+
+
+ def find_kernel(self):
+ # The uncomfortable length of this function is deceptive,
+ # most of it is for dealing with errors.
+ comment("Seeking kernel information")
+ kscript = "%s/kernel" % self.profile_dir
+ if os.path.isfile(kscript):
+ p = Popen([kscript], stdout=PIPE, stderr=STDOUT)
+ r = p.communicate()[0]
+ if p.returncode == 0:
+ self.kname, self.kversion = r.split()
+
+ else:
+ errout(_("Problem running %s:\n %s") % (kscript, r))
+ else:
+ kernels = glob(self.installation0 + '/boot/vmlinuz*')
+ if len(kernels) > 1:
+ errout(_("More than one kernel found:\n %s") %
+ "\n ".join(kernels))
+ elif not kernels:
+ errout(_("No kernel found"))
+ self.kname = os.path.basename(kernels[0])
+
+ self.kversion = None
+ for kv in os.listdir(self.installation0 + '/lib/modules'):
+ if os.path.isfile(self.installation0
+ + ('/lib/modules/%s/modules.dep' % kv)):
+ if self.kversion:
+ errout(_("More than one set of kernel modules in %s")
+ % (self.installation0 + '/lib/modules'))
+ self.kversion = kv
+ else:
+ kmpath = self.installation0 + ('/lib/modules/%s' % kv)
+ comment("Unexpected kernel files at %s" % kmpath)
+ # Try to find packages concerned
+ p = Popen(["find", ".", "-name", "*.ko"], cwd=kmpath,
+ stdout=PIPE, stderr=STDOUT)
+ r = p.communicate()[0]
+ if p.returncode == 0:
+ packs = []
+ for km in r.split():
+ a = chroot(self.installation0,
+ 'pacman -Qoq /lib/modules/%s/%s'
+ % (kv, km))
+
+ if a:
+ pack = "-".join(a[0].split())
+ if pack not in packs:
+ packs.append(pack)
+ comment(" Package: %s" % pack)
+
+ else:
+ comment("Couldn't determine guilty packages")
+
+ if not query_yn(_("WARNING:"
+ "\n You seem to have installed a package containing modules"
+ "\nwhich aren't compatible with your kernel (see log)."
+ "\nPlease check that this won't cause problems."
+ "\nMaybe you need the corresponding package for your kernel?"
+ "\n\n Continue building?")):
+ return False
+
+ if not self.kversion:
+ errout(_("Couldn't find kernel modules"))
+
+ comment("Kernel: %s - version: %s" % (self.kname, self.kversion))
+ chroot(self.installation0, "depmod %s" % self.kversion)
+ return True
+
+
+ def gen_initramfs(self):
+ # Fix up larch mkinitcpio.conf for unionfs/aufs
+ conf = self.overlay + "/etc/mkinitcpio.conf.larch"
+ if os.path.isfile(conf + "0"):
+ conf0 = conf + "0"
+ else:
+ conf0 = self.installation0 + "/etc/mkinitcpio.conf.larch0"
+ runcmd('bash -c "sed \'s|___aufs___|%s|g\' <%s >%s"' % (self.ufs, conf0, conf))
+
+ presets = [os.path.basename(f) for f in glob(
+ self.installation0 + "/etc/mkinitcpio.d/kernel26*.preset")]
+ if len(presets) != 1:
+ errout(_("Couldn't find usable mkinitcpio preset: %s") %
+ self.installation0 + "/etc/mkinitcpio.d/kernel26*.preset")
+
+ # Save original preset file (unless a '*.larchsave' is already present)
+ idir = self.installation0 + "/etc/mkinitcpio.d"
+ oldir = self.overlay + "/etc/mkinitcpio.d"
+ if not os.path.isfile("%s/%s.larchsave" % (idir, presets[0])):
+ runcmd("mkdir -p %s" % oldir)
+ runcmd("cp %s/%s %s/%s.larchsave" %
+ (idir, presets[0], oldir, presets[0]))
+
+ # Adjust larch.preset file for custom kernels
+ runcmd('bash -c "sed \'s|___|%s|\' <%s/larch.preset0 >%s/larch.preset"'
+ % (presets[0].rsplit(".", 1)[0], idir, oldir))
+
+ # Replace 'normal' preset in overlay
+ runcmd("cp %s/larch.preset %s/%s" % (oldir, oldir, presets[0]))
+
+ # Generate initramfs
+ return chroot(self.installation0,
+ "mkinitcpio -k %s -c %s -g %s" %
+ (self.kversion,
+ CHROOT_DIR_OVERLAY + "/etc/mkinitcpio.conf.larch",
+ CHROOT_DIR_MEDIUM + "/boot/larch.img"))
+
+
+def encryptPW(pw):
+ """Encrypt a password - needed for user account generation.
+ """
+ salt = '$1$'
+ for i in range(8):
+ salt += random.choice("./0123456789abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+ return crypt.crypt(pw, salt)
+
+
+
+if __name__ == "__main__":
+ start_translator()
+
+ from optparse import OptionParser, OptionGroup
+ parser = OptionParser(usage=_("usage: %prog [options]"))
+
+ parser.add_option("-p", "--profile", action="store", type="string",
+ default="", dest="profile",
+ help=_("Profile: 'user:profile-name' or path to profile directory"))
+ parser.add_option("-i", "--installation-dir", action="store", type="string",
+ default="", dest="idir",
+ help=_("Path to directory to be larchified (default %s)")
+ % INSTALLATION)
+ parser.add_option("-s", "--slave", action="store_true", dest="slave",
+ default=False, help=_("Run as a slave from a controlling program"
+ " (e.g. from a gui)"))
+ parser.add_option("-q", "--quiet", action="store_true", dest="quiet",
+ default=False, help=_("Suppress output messages, except errors"
+ " (no effect if -s specified)"))
+ parser.add_option("-o", "--oldsqf", action="store_true", dest="oldsqf",
+ default=False, help=_("Reuse previously generated system.sqf"))
+ parser.add_option("-l", "--oldlocales", action="store_true",
+ dest="oldlocales", default=False,
+ help=_("Reuse previously generated locales"))
+ parser.add_option("-f", "--force", action="store_true", dest="force",
+ default=False, help=_("Don't ask for confirmation"))
+
+ (options, args) = parser.parse_args()
+# Should there be arguments?
+
+ if os.getuid() != 0:
+ print _("This application must be run as root")
+ sys.exit(1)
+ init('larchify', options)
+ builder = Builder(options)
+ if builder.build(options):
+ sys.exit(0)
+ else:
+ sys.exit(1)
diff --git a/build_tools/l7/larch0/cli/live_iso.py b/build_tools/l7/larch0/cli/live_iso.py
new file mode 100755
index 0000000..e6899c9
--- /dev/null
+++ b/build_tools/l7/larch0/cli/live_iso.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+#
+# live_iso.py
+#
+# (c) Copyright 2009 - 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# larch 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.12
+
+# This is a command line script to prepare an 'iso' file from an
+# already larchified Arch Linux installation. Alternatively, another
+# larch medium can be used as source (with the '-S' option).
+# All needed parameters are passed as options.
+
+import os
+from config import *
+from backend import *
+from media_common import *
+
+
+def build_iso(options):
+ """Create a bootable iso from the source larch image. If a chroot
+ build is used (default unless -S is specified) the resulting iso file
+ will be relative to the larch build area (CHROOT_DIR_BUILD) in the
+ chroot directory, otherwise as specified to the -o option.
+ """
+ medium = Medium(options)
+ ipath = medium.chrootpath
+ build = medium.build
+
+ # Write bootloader configuration file
+ bootconfig(build, options.syslinux)
+
+ if not os.path.isfile(medium.medium_dir + '/larch/larchboot'):
+ add_larchboot(build)
+
+ # Need to adjust paths to cater for chroot!
+ source0 = medium.chroot_medium_dir()
+ isopath0 = medium.iso_path()
+
+ # Select bootloader
+ if options.syslinux:
+ # Get fresh isolinux.bin
+ runcmd('cp %s/boot/isolinux/isolinux.bin0 %s/boot/isolinux/isolinux.bin'
+ % (build, build))
+ parms = '-b boot/isolinux/isolinux.bin -c boot/isolinux/isolinux.boot'
+ # Select bootloader
+ else:
+ parms = '-b boot/grub/stage2_eltorito'
+
+ # Actually the volume label can be 32 bytes, but 16 is compatible
+ # with ext2 (though a little longer than vfat)
+ label = check_label(options.label, 16)
+ # Build iso
+ if not chroot(ipath, ('mkisofs -R -l %s -no-emul-boot -boot-load-size 4'
+ ' -boot-info-table -input-charset=UTF-8'
+ ' -V "%s"'
+ ' -o "%s"'
+ ' -x "%s/boot"'
+ ' -x "%s/larch/save"'
+ ' "%s" "%s"') % (parms, label, isopath0,
+ source0, source0, source0, BUILD0),
+ filter=mkisofs_filter_gen()):
+
+ errout(_("iso build failed"))
+
+ # Process iso so that it can be 'dd'ed to a USB-stick
+ if options.syslinux and not chroot(ipath, ('isohybrid %s' % isopath0)):
+ errout(_("Couldn't perform 'isohybrid' operation on larch 'iso'"
+ " (Not Critical!)"), 0)
+ medium.unmount()
+ runcmd('rm -rf %s' % build)
+
+ comment(" *** %s ***" % (_("%s was successfully created")
+ % (options.isofile)))
+
+
+
+if __name__ == "__main__":
+ start_translator()
+
+ from optparse import OptionParser, OptionGroup
+ parser = OptionParser(usage=_("usage: %prog [options]"))
+
+ parser.add_option("-p", "--profile", action="store", type="string",
+ default="", dest="profile",
+ help=_("Profile: 'user:profile-name' or path to profile directory"
+ " (conflicts with -S)"))
+ parser.add_option("-S", "--source", action="store", type="string",
+ default="", dest="source",
+ help=_("Source: path to larch medium image (conflicts with -p)."
+ " It can also be a device ('/dev/...') or an 'iso' file."))
+ parser.add_option("-i", "--installation-dir", action="store", type="string",
+ default="", dest="idir",
+ help=_("Path to larchified directory (default %s)") % INSTALLATION)
+ parser.add_option("-s", "--slave", action="store_true", dest="slave",
+ default=False, help=_("Run as a slave from a controlling program"
+ " (e.g. from a gui)"))
+ parser.add_option("-q", "--quiet", action="store_true", dest="quiet",
+ default=False, help=_("Suppress output messages, except errors"
+ " (no effect if -s specified)"))
+
+ parser.add_option("-f", "--force", action="store_true", dest="force",
+ default=False, help=_("Don't ask for confirmation"))
+
+ parser.add_option("-b", "--isolinux", action="store_true", dest="syslinux",
+ default=False, help=_("Use the isolinux bootloader"
+ " (the default is GRUB)"))
+ parser.add_option("-l", "--label", action="store", type="string",
+ default=LABEL, dest="label",
+ help=_("Volume label for iso (default '%s')") % LABEL)
+ parser.add_option("-o", "--isofile", action="store", type="string",
+ default=ISOFILE, dest="isofile",
+ help=_("Specify the output file (default '%s'). It will be"
+ " generated to the current directory.") % ISOFILE)
+ parser.add_option("-D", "--setdir", action="store", type="string",
+ dest='setdir', default='',
+ help=_("Set current directory,"
+ " so that the 'iso' can be placed there"))
+ parser.add_option("-C", "--chroot", action="store_true",
+ dest="chroot", default=False,
+ help=_("Use chroot for build (default when -S not specified)"))
+ parser.add_option("-c", "--nochroot", action="store_true",
+ dest="nochroot", default=False,
+ help=_("Don't use chroot for build (default when -S specified)"))
+
+ parser.add_option('-T', '--testmedium', action='store_true',
+ dest='testmedium', default=False,
+ help=_("Test source medium only (used by gui)"))
+
+ (options, args) = parser.parse_args()
+
+ if os.getuid() != 0:
+ print _("This application must be run as root")
+ sys.exit(1)
+
+ init('live_iso', options)
+ build_iso(options)
diff --git a/build_tools/l7/larch0/cli/live_part.py b/build_tools/l7/larch0/cli/live_part.py
new file mode 100755
index 0000000..22af5d3
--- /dev/null
+++ b/build_tools/l7/larch0/cli/live_part.py
@@ -0,0 +1,221 @@
+#!/usr/bin/env python
+#
+# live_part.py
+#
+# (c) Copyright 2009 - 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# larch 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.18
+
+# This is a command line script to prepare a larch live medium from an
+# already larchified Arch Linux installation, or another larch medium.
+# Parameters are passed as options and arguments.
+
+import os
+from config import *
+from backend import *
+from media_common import *
+
+
+def build_partition(options, devicename):
+ """Install the larchified system to the partition specified by the
+ options.
+ 'devicename' is the name (e.g. 'sdb1', without '/dev/') of the
+ partition to receive the larch image
+ """
+ medium = Medium(options)
+ ipath = medium.chrootpath
+ build = medium.build
+
+ # Check and format device
+ device = '/dev/' + devicename
+ if not os.path.exists(device):
+ errout(_("Invalid device: %s") % device)
+ if options.format:
+ # label
+ if options.syslinux:
+ labellength = 11
+ opt = 'n'
+ else:
+ labellength = 16
+ opt = 'L'
+ l = ('-%s "%s"' % (opt, check_label(options.label, labellength))
+ if options.label else '')
+ # set partition type, reload partition table, and format
+ if not (chroot(ipath, 'bash -c "echo -e \',,%s,*\\n\' |'
+ ' sfdisk --no-reread %s -N%s"' %
+ ('0c' if options.syslinux else 'L',
+ device[:8], device[8:]), ['dev'])
+ and chroot(ipath, 'partprobe %s' % device[:8], ['dev'])
+ and chroot(ipath, 'mkfs.%s %s %s' %
+ ('vfat' if options.syslinux else 'ext2', l, device),
+ ['dev'])):
+
+ errout(_("Couldn't format %s") % device)
+
+ # Need to get the label - if not formatting (an option for experts)
+ # it is probably not a good idea to change the volume label, so
+ # use the old one.
+ label = medium.get_device_label(device)
+
+ # Check device format
+ ok, lines = runcmd('blkid -c /dev/null -o value -s TYPE %s' % device)
+ if not ok:
+ errout(_("Couldn't get format information for %s") % device)
+ fstype = lines[0]
+ if options.syslinux:
+ if fstype != 'vfat':
+ errout(_("syslinux is only supported on vfat"))
+ elif not fstype.startswith('ext'):
+ errout(_("GRUB is at present only supported on extN"))
+
+ # Rename the syslinux boot directory if necessary
+ if os.path.isdir(build + '/boot/isolinux'):
+ runcmd('mv %s/boot/isolinux %s/boot/syslinux' % (build, build))
+ # Write bootloader configuration file
+ bootconfig(build, options.syslinux, label, device, options.detection)
+
+ # Mount partition and remove larch and boot dirs
+ runcmd('rm -rf %s' % MP)
+ runcmd('mkdir -p %s' % MP)
+ if not mount(device, MP):
+ errout(_("Couldn't mount larch partition, %s") % device)
+ runcmd('rm -rf %s/larch' % MP)
+ runcmd('rm -rf %s/boot' % MP)
+
+ # Copy files to device
+ runcmd('cp -r %s/larch %s' % (medium.medium_dir, MP))
+ runcmd('cp -r %s/boot %s' % (build, MP))
+ medium.unmount()
+
+ # To boot in 'search' mode the file larch/larchboot must be present
+ # (though at present this is only relevant for partitions, CDs will
+ # be booted even without this file).
+ # To enable session saving the file larch/save must be present
+ # (only relevant if not building an iso).
+ runcmd('bash -c "rm -f %s/larch/{larchboot,save}"' % MP)
+ if options.larchboot:
+ add_larchboot(MP)
+
+ if options.nosave:
+ if options.save:
+ errout(_("Option '-a' conflicts with option '-A'"))
+ ssave = False
+ else:
+ ssave = not os.path.isfile(MP + '/larch/nosave')
+
+ if options.save or ssave:
+ writefile("The presence of the file 'larch/save'"
+ " enables session saving.\n", MP + '/larch/save')
+
+ # Unmount partition
+ unmount(MP)
+
+ # Now set up bootloader in MBR
+ if options.mbr:
+ if options.syslinux:
+ runcmd('dd if=%s/boot/syslinux/mbr.bin of=%s'
+ % (build, device[:8]))
+ chroot(ipath, 'syslinux %s' % device, ('dev', 'proc'))
+ else:
+ script('larch-mbr-grub %s %s' % (ipath if ipath else '/', device))
+ runcmd('rm -rf %s' % build)
+ comment(" *** %s ***" % (_("%s was successfully written") % device))
+
+
+
+if __name__ == '__main__':
+ start_translator()
+
+ from optparse import OptionParser, OptionGroup
+ parser = OptionParser(usage=_("usage: %prog [options] partition"
+ " (e.g. sdb1)"))
+
+ parser.add_option("-p", "--profile", action="store", type="string",
+ default="", dest="profile",
+ help=_("Profile: 'user:profile-name' or path to profile directory"
+ " (conflicts with -S)"))
+ parser.add_option("-S", "--source", action="store", type="string",
+ default="", dest="source",
+ help=_("Source: path to larch medium image (conflicts with -p)."
+ " It can also be a device ('/dev/...') or an 'iso' file."))
+ parser.add_option("-i", "--installation-dir", action="store", type="string",
+ default="", dest="idir",
+ help=_("Path to larchified directory (default %s)") % INSTALLATION)
+ parser.add_option("-s", "--slave", action="store_true", dest="slave",
+ default=False, help=_("Run as a slave from a controlling program"
+ " (e.g. from a gui)"))
+ parser.add_option("-q", "--quiet", action="store_true", dest="quiet",
+ default=False, help=_("Suppress output messages, except errors"
+ " (no effect if -s specified)"))
+
+ parser.add_option("-f", "--force", action="store_true", dest="force",
+ default=False, help=_("Don't ask for confirmation"))
+
+ parser.add_option("-d", "--detect", action="store", type="string",
+ default="label", dest="detection",
+ help=(_("Method for boot partition detection: %s (default: label)")
+ % detection_methods))
+ parser.add_option("-b", "--syslinux", action="store_true", dest="syslinux",
+ default=False, help=_("Use the syslinux bootloader"
+ " (the default is GRUB)"))
+ parser.add_option("-l", "--label", action="store", type="string",
+ default=LABEL, dest="label",
+ help=_("Volume label for boot partition (default %s)")
+ % LABEL)
+ parser.add_option("-n", "--nosearchboot", action="store_false",
+ dest="larchboot", default=True,
+ help=_("Don't generate 'larch/larchboot' file"))
+ parser.add_option("-a", "--save", action="store_true",
+ dest="save", default=False,
+ help=_("Override profile larch/nosave"
+ " (force enable session saving) - conflicts with '-A'"))
+ parser.add_option("-A", "--nosave", action="store_true",
+ dest="nosave", default=False,
+ help=_("Force disabling of session saving - conflicts with '-a'"))
+ parser.add_option("-x", "--noformat", action="store_false",
+ dest="format", default=True,
+ help=_("Don't format partition (only for experts!)"))
+ parser.add_option("-m", "--noboot", action="store_false",
+ dest="mbr", default=True,
+ help=_("Don't install the bootloader (to the MBR)"))
+ parser.add_option("-C", "--chroot", action="store_true",
+ dest="chroot", default=False,
+ help=_("Use chroot for build (default when -S not specified)"))
+ parser.add_option("-c", "--nochroot", action="store_true",
+ dest="nochroot", default=False,
+ help=_("Don't use chroot for build (default when -S specified)"))
+
+ (options, args) = parser.parse_args()
+
+ # To avoid error messages
+ options.testmedium = False
+ options.setdir = ''
+
+ if not args:
+ print _("You must specify the partition to receive larch\n")
+ parser.print_help()
+ sys.exit(1)
+
+ if os.getuid() != 0:
+ print _("This application must be run as root")
+ sys.exit(1)
+
+ init('live_part', options)
+ build_partition(options, devicename=args[0])
diff --git a/build_tools/l7/larch0/cli/media_common.py b/build_tools/l7/larch0/cli/media_common.py
new file mode 100644
index 0000000..b39e67d
--- /dev/null
+++ b/build_tools/l7/larch0/cli/media_common.py
@@ -0,0 +1,450 @@
+#!/usr/bin/env python
+#
+# media_common.py
+#
+# (c) Copyright 2009 - 2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# larch 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.14
+
+"""This code is used by both iso generator and partition creator modules.
+The larch medium creation stage first uses the Medium class to prepare the
+bootloader directories. build_iso and build_partition can also be used to
+rebuild media starting from existing larch media, e.g. for conversions from
+CD to usb-stick and vice-versa. Even a switch of bootloader is possible as
+the bootloader configs for both GRUB and syslinux/isolinux are normally
+included on the medium.
+If not using a larch installation directory, the bootloader and/or mkisofs
+must be supported by the host (according to medium and bootloader).
+"""
+
+import os
+from config import *
+from backend import *
+
+class Medium:
+ """This class represents a boot medium image.
+ It checks the options for conflicts.
+ If necessary (no -S option provided on the command line) it
+ converts a larchified system to a boot medium image, by preparing
+ the bootloader directories and adding customization stuff
+ from the profile, but it does not write to any medium.
+ """
+ def __init__(self, options):
+ self.options = options
+ if options.setdir:
+ try:
+ wd = os.path.realpath(options.setdir)
+ os.chdir(wd)
+ except:
+ errout(_("Couldn't switch directory to '%s'") % wd)
+ self.iso = False
+ self.installation_dir = get_installation_dir()
+ self.installation0 = (self.installation_dir
+ if self.installation_dir != '/' else '')
+
+ # Check options
+ if options.chroot and options.nochroot:
+ errout(_("Option -C conflicts with -c"))
+
+ if options.source:
+ # Using a specified source image
+ if options.profile:
+ errout(_("Option -S conflicts with -p"))
+
+ # Use chroot?
+ self.chrootpath = self.installation0 if options.chroot else ''
+
+ else:
+ # Using the larch build system
+
+ # Use chroot?
+ self.chrootpath = '' if options.nochroot else self.installation0
+
+ # Mount point for source, if necessary
+ self.mps = self.chrootpath + SOURCEMOUNT
+ runcmd('rm -rf %s' % self.mps)
+ runcmd('mkdir -p %s' % self.mps)
+
+ # Create a temporary work area
+ self.build = self.chrootpath + BUILD0
+ runcmd('rm -rf %s' % self.build)
+ runcmd('mkdir -p %s' % self.build)
+
+ if options.source:
+ # Location of medium
+ self.medium_dir = self._get_source(options.source,
+ options.syslinux, self.chrootpath, test=options.testmedium)
+
+ else:
+ # Location for the live medium image
+ self.medium_dir = self.installation0 + CHROOT_DIR_MEDIUM
+ self._check_larchimage(self.medium_dir)
+ self.profile_dir = get_profile()
+
+ self._bootdir(options.syslinux)
+ self._customizelarchdir()
+
+ # The boot directory needs to be outside of the medium directory
+ # for some of the next steps
+ runcmd('cp -a %s/boot %s' % (self.medium_dir, self.build))
+
+
+ def unmount(self):
+ if self.medium_dir == self.mps:
+ unmount(self.mps)
+
+ if self.iso:
+ if self.chrootpath:
+ unmount(self.chrootpath + ISOBUILDMNT)
+ # Change owner of iso to match current directory
+ cwdstat = os.stat(os.getcwd())
+ os.lchown(self.iso, cwdstat.st_uid, cwdstat.st_gid)
+
+
+ def get_device_label(self, device):
+ l = runcmd('blkid -c /dev/null -o value -s LABEL %s'
+ % device)[1][0]
+ if self.options.detection not in detection_methods.split('|'):
+ errout(_("Invalid detection method (-d option)"))
+ return l
+
+
+ def chroot_medium_dir(self):
+ """Get the medium path relative within the (possible) chroot.
+ """
+ return self.medium_dir[len(self.chrootpath):]
+
+
+ def iso_path(self):
+ """If building an iso within a chroot the build directory must
+ be bind-mounted into the accessible area.
+ """
+ self.iso = self.options.isofile.replace('/', '_')
+ basedir = os.getcwd()
+ if self.chrootpath:
+ runcmd('mkdir -p %s' % (self.chrootpath + ISOBUILDMNT))
+ if not mount(basedir, self.chrootpath + ISOBUILDMNT, '--bind'):
+ errout(_("Couldn't bind-mount current directory"))
+ basedir = ISOBUILDMNT
+ return os.path.join(basedir, self.iso)
+
+
+ def _check_larchimage(self, mediumdir):
+ testfile = mediumdir + '/larch/system.sqf'
+ if not os.path.isfile(testfile):
+ errout(_("File '%s' doesn't exist, '%s' is not a larch medium")
+ % (testfile, mediumdir))
+
+
+ def _get_source(self, source, syslinux, ipath='', test=False):
+ source = os.path.realpath(source)
+ testcode = 64 if test else 0 # For various tests, below
+
+ # Is source an iso?
+ if os.path.isfile(source):
+ # Mount it
+ if mount(source, self.mps, '-o loop'):
+ source = self.mps
+ else:
+ errout(_("Couldn't mount '%s'. Not an iso?") % source)
+
+ # Is source a device?
+ elif source.startswith('/dev/'):
+ mp = _get_mountpoint(source)
+ if mp:
+ source = mp
+ elif mount(source, self.mps):
+ source = self.mps
+ else:
+ errout(_("Couldn't mount '%s'") % source)
+
+ # Simple check that it is really a larch image
+ self._check_larchimage(source)
+
+ # Check bootloader support
+ if not (os.path.isdir(source + '/boot/isolinux')
+ or os.path.isdir(source + '/boot/syslinux')):
+ if test or syslinux:
+ errout(_("Source doesn't support syslinux"), 0)
+ testcode += 8
+ if not os.path.isdir(source + '/boot/grub'):
+ if test or not syslinux:
+ errout(_("Source doesn't support GRUB"), 0)
+ testcode += 16
+
+ if testcode:
+ if os.path.isfile(source + 'larch/nosave'):
+ testcode += 2
+
+ unmount()
+ sys.exit(testcode)
+ return None
+
+ # Is source within the chroot directory?
+ if ipath and not source.startswith(ipath + '/'):
+ if mount(source, self.mps, '--bind'):
+ source = self.mps
+ else:
+ errout(_("Couldn't bind-mount '%s'") % source)
+
+ # Copy boot files
+ runcmd('cp -a %s/boot %s' % (source, self.build))
+
+ # Remove old config files
+ runcmd('rm -f %s/boot/syslinux/syslinux.cfg' % self.build)
+ runcmd('rm -f %s/boot/isolinux/isolinux.cfg' % self.build)
+ runcmd('rm -f %s/boot/grub/menu.lst' % self.build)
+ runcmd('rm -f %s/boot/isolinux/isolinux.bin' % self.build)
+ runcmd('rm -f %s/boot/isolinux/isolinux.boot' % self.build)
+
+ # Rename the syslinux boot directory if necessary
+ if os.path.isdir(self.build + '/boot/syslinux'):
+ runcmd('mv %s/boot/syslinux %s/boot/isolinux'
+ % (self.build, self.build))
+
+ return source
+
+
+ def _bootdir(self, syslinux):
+ """Prepare boot directories for the various bootloaders. The
+ bootloader configuration files are not generated yet, as these
+ depend on the medium.
+ """
+ comment("Fetch kernel and initramfs")
+ if not runcmd('cp -r %s/boot %s' % (self.medium_dir, self.build))[0]:
+ errout(_("No kernel and/or initramfs"))
+
+ comment("Preparing bootloader directories")
+ # Only include the directories if the corresponding bootloader
+ # package ('grub' and/or 'syslinux') is installed
+ grub = os.path.isfile(self.installation0 + GRUBDIR
+ + '/stage2_eltorito')
+ isolinux = os.path.isfile(self.installation0 + SYSLINUXDIR
+ + '/isolinux.bin')
+ # Check bootloader support
+ if syslinux:
+ if not isolinux:
+ errout(_("Installation doesn't support syslinux"))
+ elif not grub:
+ errout(_("Installation doesn't support GRUB"))
+
+ # Bootloader independent files are provided in larch at
+ # cd-root/boot0. The contents of this directory are placed in the
+ # medium's 'boot' directory.
+ # Individual files can be added or substituted by
+ # supplying them in the profile at cd-root/boot.
+ # It is also possible to completely replace the basic boot directory
+ # by having cd-root/boot0 in the profile - then the default
+ # larch version will not be used.
+ source0 = '%s/cd-root/boot0' % self.profile_dir
+ if not os.path.isdir(source0):
+ source0 = '%s/cd-root/boot0' % base_dir
+ runcmd('bash -c "cp -r %s/* %s/boot"' % (source0, self.build))
+ # Copy any additional profile stuff
+ psource = '%s/cd-root/boot' % self.profile_dir
+ if os.path.isdir(psource):
+ runcmd('cp -rf %s/* %s' % (psource, self.build))
+ # Get the boot options file
+ bootlines = self.profile_dir + '/bootlines'
+ if not os.path.isfile(bootlines):
+ bootlines = base_dir + '/data/bootlines'
+ runcmd('cp -f %s %s/boot' % (bootlines, self.build))
+
+ # The idea is that a basic boot directory for each boot-loader is
+ # provided in larch at cd-root/{grub0,isolinux0}. These are copied
+ # to the medium as 'boot/grub' and 'boot/isolinux'. Individual files
+ # can be added or substituted by supplying them in the profile at
+ # cd-root/{grub,isolinux}.
+ # It is also possible to completely replace the basic boot directory
+ # by having cd-root/{grub0,isolinux0} in the profile - then the default
+ # larch versions will not be used.
+ for ok, boot_dir in ((grub, 'grub'), (isolinux, 'isolinux')):
+ if ok:
+ # Copy bootloader specific stuff
+ source0 = '%s/cd-root/%s0' % (self.profile_dir, boot_dir)
+ if not os.path.isdir(source0):
+ source0 = '%s/cd-root/%s0' % (base_dir, boot_dir)
+ runcmd('cp -rf %s %s/boot/%s'
+ % (source0, self.build, boot_dir))
+
+ # Copy any additional profile stuff
+ psource = '%s/cd-root/%s' % (self.profile_dir, boot_dir)
+ if os.path.isdir(psource):
+ runcmd('cp -rf %s %s/boot'
+ % (psource, self.build))
+
+ # Copy the grub boot files to the medium's grub directory
+ # and rename base config file
+ if grub:
+ runcmd('bash -c "cp %s/* %s/boot/grub"' %
+ (self.installation0 + GRUBDIR, self.build))
+ runcmd('mv %s/boot/grub/menu.lst %s/boot/grub/menu.lst_0'
+ % (self.build, self.build))
+
+ # Copy mbr.bin, vesamenu.c32 and isolinux.bin (renamed)
+ # to the boot directory and rename base config file
+ if isolinux:
+ runcmd('cp %s/mbr.bin %s/boot/isolinux' %
+ (self.installation0 + SYSLINUXDIR, self.build))
+ runcmd('cp %s/vesamenu.c32 %s/boot/isolinux' %
+ (self.installation0 + SYSLINUXDIR, self.build))
+ runcmd('cp %s/isolinux.bin %s/boot/isolinux/isolinux.bin0' %
+ (self.installation0 + SYSLINUXDIR, self.build))
+ runcmd(('mv %s/boot/isolinux/isolinux.cfg'
+ ' %s/boot/isolinux/isolinux.cfg_0')
+ % (self.build, self.build))
+
+
+ def _customizelarchdir(self):
+ # Replace any existing larch/copy directory
+ runcmd('rm -rf %s/larch/copy' % self.medium_dir)
+ if os.path.isdir(self.profile_dir + '/cd-root/larch/copy'):
+ runcmd('cp -r %s/cd-root/larch/copy %s/larch' %
+ (self.profile_dir, self.medium_dir))
+
+ # Replace any existing larch/extra directory
+ runcmd('rm -rf %s/larch/extra' % self.medium_dir)
+ if os.path.isdir(self.profile_dir + '/cd-root/larch/extra'):
+ runcmd('cp -r %s/cd-root/larch/extra %s/larch' %
+ (self.profile_dir, self.medium_dir))
+
+ # Handle existence of larch/nosave
+ runcmd('rm -f %s/larch/nosave' % self.medium_dir)
+ if os.path.isfile(self.profile_dir + '/nosave'):
+ runcmd('cp %s/nosave %s/larch' %
+ (self.profile_dir, self.medium_dir))
+
+
+
+def _get_mountpoint(path):
+ ok, lines = runcmd('cat /etc/mtab')
+ for l in lines:
+ ll = l.split()
+ if (len(ll) > 4) and (ll[1] == path):
+ return ll[0]
+ return ''
+
+
+def check_label(l, n):
+ if isinstance(l, unicode):
+ l = l.encode('utf8')
+ if len(l) > n:
+ if query_yn(_("The volume label is too long. Use the default (%s)?")
+ % LABEL):
+ return LABEL
+ else:
+ errout(_("Cancelled"))
+ return l
+
+
+def add_larchboot(idir):
+ writefile("The presence of the file 'larch/larchboot' enables\n"
+ "booting the device in 'search' mode.\n", idir + '/larch/larchboot')
+
+
+def bootconfig(medium, syslinux, label='', device='', detection=''):
+ """Convert and complete the bootlines file.
+ """
+ # - add boot partition to options
+ if detection == 'uuid':
+ bootp = ('uuid=' +
+ runcmd('blkid -c /dev/null -o value -s UUID %s'
+ % device)[1][0].strip())
+ elif detection == 'label':
+ if not label:
+ errout(_("Can't boot to label - device has no label"))
+ bootp = 'label=' + label
+ elif detection == 'partition':
+ bootp = 'root=' + device
+ else:
+ bootp = ''
+
+ # - convert bootfiles to the correct format,
+ # inserting necessary info
+ bootconf = medium + '/boot/bootlines'
+ if not os.path.isfile(bootconf):
+ errout(_("Boot configuration file '%s' not found") % bootconf)
+ fhi = open(bootconf)
+ insert = ''
+ i = 0
+ block = ''
+ title = ''
+ opts = ''
+ for line in fhi:
+ line = line.strip()
+ if not line:
+ if title:
+ i += 1
+ # A block is ready
+
+ if syslinux:
+ # isolinux/syslinux
+ block += 'label %02d\n' % i
+ block += 'MENU LABEL %s\n' % title
+ block += 'kernel /boot/larch.kernel\n'
+ block += ('append initrd=/boot/larch.img %s %s\n'
+ % (bootp, opts))
+
+ else:
+ # GRUB
+ block += 'title %s\n' % title
+ block += ('kernel /boot/larch.kernel %s %s\n'
+ % (bootp, opts))
+ block += 'initrd /boot/larch.img\n'
+
+ if i > 1:
+ insert += '\n'
+ insert += block
+ block = ''
+ title = ''
+ opts = ''
+
+ elif line.startswith('comment:'):
+ block += '#%s\n' % (line.split(':', 1)[1])
+
+ elif line.startswith('title:'):
+ title = line.split(':', 1)[1].lstrip()
+
+ elif line.startswith('options:'):
+ opts = line.split(':', 1)[1].lstrip()
+ fhi.close()
+
+ # - insert the resulting string into the bootloader config file
+ if syslinux:
+ boot_dir = ('isolinux' if os.path.isdir(medium + '/boot/isolinux')
+ else 'syslinux')
+ configfile0 = 'isolinux.cfg_0'
+ configfile = boot_dir + '.cfg'
+ else:
+ boot_dir = 'grub'
+ configfile0 = 'menu.lst_0'
+ configfile = 'menu.lst'
+ configpath = '%s/boot/%s/%s' % (medium, boot_dir, configfile0)
+ if not os.path.isfile(configpath):
+ errout(_("Base configuration file (%s) not found") % configpath)
+ fhi = open(configpath)
+ fho = open('%s/boot/%s/%s' % (medium, boot_dir, configfile), 'w')
+ for line in fhi:
+ if line.startswith("###LARCH"):
+ fho.write(insert)
+ else:
+ fho.write(line)
+ fhi.close()
+ fho.close()
diff --git a/build_tools/l7/larch0/cli/test.py b/build_tools/l7/larch0/cli/test.py
new file mode 100755
index 0000000..18e8067
--- /dev/null
+++ b/build_tools/l7/larch0/cli/test.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+#
+# This is a little script for testing the dispatcher and termination of
+# subprocesses. It spawns several levels of itself, each instance just
+# outputs an incrementing count until it is terminated.
+
+import sys, os, time, signal
+
+try:
+ import json as serialize
+except:
+ import simplejson as serialize
+
+def out(text):
+ sys.stdout.write(serialize.dumps(text) + '\n')
+ sys.stdout.flush()
+
+def sigint(num, frame):
+ """A handler for SIGINT. Tidy up properly and quit.
+ """
+ out("!>INTERRUPTED %d" % level)
+ exit(1)
+signal.signal(signal.SIGINT, sigint)
+
+level = 3
+while level > 0:
+ if os.fork():
+ break
+ time.sleep(1)
+ level -= 1
+
+sys.stderr.write('%d: pid=%d ppid=%d pgrp=%d\n' % (level,
+ os.getpid(), os.getppid(), os.getpgrp()))
+sys.stderr.flush()
+time.sleep(5)
+
+n = 0
+while True:
+ n += 1
+ out('--%d: %04d' % (level, n))
+ time.sleep(4)
+
diff --git a/build_tools/l7/larch0/cli/userinfo.py b/build_tools/l7/larch0/cli/userinfo.py
new file mode 100644
index 0000000..f2045aa
--- /dev/null
+++ b/build_tools/l7/larch0/cli/userinfo.py
@@ -0,0 +1,93 @@
+# userinfo.py
+#
+# (c) Copyright 2009-2010 Michael Towers (larch42 at googlemail dot com)
+#
+# This file is part of the larch project.
+#
+# larch is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# larch 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with larch; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#----------------------------------------------------------------------------
+# 2010.07.11
+
+import os
+from ConfigParser import SafeConfigParser
+
+# Default list of 'additional' groups for a new user
+BASEGROUPS = 'video,audio,optical,storage,scanner,power,camera'
+
+class Userinfo:
+ def __init__(self, profile):
+ self.profile_dir = profile
+
+ def getusers(self):
+ """Read user information by means of a SafeConfigParser instance.
+ This is then available as self.userconf.
+ """
+ self.userconf = SafeConfigParser({'pw':'', 'maingroup':'', 'uid':'',
+ 'skel':'', 'xgroups':BASEGROUPS, 'expert':''})
+ users = self.profile_dir + '/users'
+ if os.path.isfile(users):
+ try:
+ self.userconf.read(users)
+ except:
+ error0(_("Invalid 'users' file"))
+
+ def allusers(self):
+ self.getusers()
+ return self.userconf.sections()
+
+ def get(self, user, field):
+ return self.userconf.get(user, field)
+
+ def userinfo(self, user, fields):
+ """Get an ordered list of the given field data for the given user.
+ """
+ return [self.userconf.get(user, f) for f in fields]
+
+ def userset(self, uname, field, text):
+ self.userconf.set(uname, field, text)
+
+ def newuser(self, user):
+ try:
+ self.userconf.add_section(user)
+ return self.saveusers()
+ except:
+ error0(_("Couldn't add user '%s'") % user)
+ return False
+
+ def deluser(self, user):
+ try:
+ self.userconf.remove_section(user)
+ return self.saveusers()
+ except:
+ error0(_("Couldn't remove user '%s'") % user)
+ return False
+
+ def saveusers(self):
+ """Save the user configuration data (in 'INI' format)
+ """
+ try:
+ fh = None
+ fh = open(self.profile_dir + '/users', 'w')
+ self.userconf.write(fh)
+ fh.close()
+ return True
+ except:
+ if fh:
+ fh.close()
+ error0(_("Couldn't save 'users' file"))
+ self.getusers()
+ return False
+