From: "Brian C. Lane" bcl@redhat.com
The wait is over. I think this stuff is now in good enough shape for others to start using it and providing feedback. The README.livemedia-creator file attempts to document things, or failing that, read the source :)
I plan on adding this as a sub-package in the lorax.spec, I haven't done that yet because I wanted to get this out there with enough time for some feedback before vacation next week.
The idea behind this is to integrate all live media creation (isos, disk images, ami images, appliances, etc.) into lorax and anaconda. In its current state it will create bootable iso's which can be installed if you set LIVE_BLOCK=/dev/loop1 before running liveinst. See the README for more info and for future plans.
Brian C. Lane (6): Changes needed for livecd creation Add execWith utils from anaconda Allow a None to be passed as size to create_runtime Add livemedia-creator Add config files for live media Add livemedia-creator README and example ks
README.livemedia-creator | 170 ++++++++ docs/fedora-livemedia.ks | 388 +++++++++++++++++++ share/config_files/live/x86/boot.msg | 5 + share/config_files/live/x86/grub.conf | 8 + share/config_files/live/x86/isolinux.cfg | 98 +++++ src/pylorax/executils.py | 379 ++++++++++++++++++ src/pylorax/imgutils.py | 64 +++ src/pylorax/treebuilder.py | 14 +- src/sbin/livemedia-creator | 623 ++++++++++++++++++++++++++++++ 9 files changed, 1747 insertions(+), 2 deletions(-) create mode 100644 README.livemedia-creator create mode 100644 docs/fedora-livemedia.ks create mode 100644 share/config_files/live/x86/boot.msg create mode 100644 share/config_files/live/x86/grub.conf create mode 100644 share/config_files/live/x86/isolinux.cfg create mode 100644 src/pylorax/executils.py create mode 100755 src/sbin/livemedia-creator
From: "Brian C. Lane" bcl@redhat.com
Allow passing of size to create_runtime, add PartitionMount context to use kpartx to mount partitioned file images. Add resetting the selinux context on the newly created rootfs. --- src/pylorax/imgutils.py | 64 ++++++++++++++++++++++++++++++++++++++++++++ src/pylorax/treebuilder.py | 11 ++++++- 2 files changed, 73 insertions(+), 2 deletions(-)
diff --git a/src/pylorax/imgutils.py b/src/pylorax/imgutils.py index e7526b0..3dd6460 100644 --- a/src/pylorax/imgutils.py +++ b/src/pylorax/imgutils.py @@ -24,6 +24,7 @@ import os, tempfile from os.path import join, dirname from pylorax.sysutils import cpfile from subprocess import * +import traceback
######## Functions for making container images (cpio, squashfs) ##########
@@ -191,6 +192,69 @@ class Mount(object): def __exit__(self, exc_type, exc_value, traceback): umount(self.mnt)
+class PartitionMount(object): + """ Mount a partitioned image file using kpartx """ + def __init__(self, disk_img, mount_ok=None): + """ + disk_img is the full path to a partitioned disk image + mount_ok is a function that is passed the mount point and + returns True if it should be mounted. + """ + self.mount_dir = None + self.disk_img = disk_img + self.mount_ok = mount_ok + + # Default is to mount partition with /etc/passwd + if not self.mount_ok: + self.mount_ok = lambda mount_dir: os.path.isfile(mount_dir+"/etc/passwd") + + # Example kpartx output + # kpartx -p p -v -a /tmp/diskV2DiCW.im + # add map loop2p1 (253:2): 0 3481600 linear /dev/loop2 2048 + # add map loop2p2 (253:3): 0 614400 linear /dev/loop2 3483648 + cmd = [ "kpartx", "-v", "-p", "p", "-a", self.disk_img ] + logger.debug(cmd) + kpartx_output = check_output(cmd) + logger.debug(kpartx_output) + + # list of (deviceName, sizeInBytes) + self.loop_devices = [] + for line in kpartx_output.splitlines(): + # add map loop2p3 (253:4): 0 7139328 linear /dev/loop2 528384 + # 3rd element is size in 512 byte blocks + if line.startswith("add map "): + fields = line[8:].split() + self.loop_devices.append( (fields[0], int(fields[3])*512) ) + + def __enter__(self): + # Mount the device selected by mount_ok, if possible + mount_dir = tempfile.mkdtemp() + for dev, size in self.loop_devices: + try: + mount( "/dev/mapper/"+dev, mnt=mount_dir ) + if self.mount_ok(mount_dir): + self.mount_dir = mount_dir + self.mount_dev = dev + self.mount_size = size + break + umount( mount_dir ) + except CalledProcessError: + logging.debug(traceback.format_exc()) + if self.mount_dir: + logging.info("Partition mounted on {0} size={1}".format(self.mount_dir, self.mount_size)) + else: + logging.debug("Unable to mount anything from {0}".format(self.disk_img)) + os.rmdir(mount_dir) + return self + + def __exit__(self, exc_type, exc_value, traceback): + if self.mount_dir: + umount( self.mount_dir ) + os.rmdir(self.mount_dir) + self.mount_dir = None + call(["kpartx", "-d", self.disk_img]) + + ######## Functions for making filesystem images ##########################
def mkfsimage(fstype, rootdir, outfile, size=None, mkfsargs=[], mountargs="", graft={}): diff --git a/src/pylorax/treebuilder.py b/src/pylorax/treebuilder.py index 57cf530..b1b5b0c 100644 --- a/src/pylorax/treebuilder.py +++ b/src/pylorax/treebuilder.py @@ -127,13 +127,20 @@ class RuntimeBuilder(object): check_call(["depmod", "-a", "-F", ksyms, "-b", root, kver]) generate_module_info(moddir+kver, outfile=moddir+"module-info")
- def create_runtime(self, outfile="/tmp/squashfs.img", compression="xz", compressargs=[]): + def create_runtime(self, outfile="/tmp/squashfs.img", compression="xz", compressargs=[], size=2): # make live rootfs image - must be named "LiveOS/rootfs.img" for dracut workdir = joinpaths(os.path.dirname(outfile), "runtime-workdir") - fssize = 2 * (1024*1024*1024) # 2GB sparse file compresses down to nothin' + fssize = size * (1024*1024*1024) # 2GB sparse file compresses down to nothin' os.makedirs(joinpaths(workdir, "LiveOS")) imgutils.mkext4img(self.vars.root, joinpaths(workdir, "LiveOS/rootfs.img"), label="Anaconda", size=fssize) + + # Reset selinux context on new rootfs + with imgutils.LoopDev( joinpaths(workdir, "LiveOS/rootfs.img") ) as loopdev: + with imgutils.Mount(loopdev) as mnt: + cmd = ["chroot", mnt, "setfiles", "-e", "/proc", "-e", "/sys", "-e", "/dev", "-e", "/selinux", "/etc/selinux/targeted/contexts/files/file_contexts", "/"] + check_call(cmd) + # squash the live rootfs and clean up workdir imgutils.mksquashfs(workdir, outfile, compression, compressargs) remove(workdir)
From: "Brian C. Lane" bcl@redhat.com
The anaconda execWithRedirect and execWithCapture functions are too useful not to include. They also allow you to log all the execuatable's output to a logfile. Added them under executils.py module which uses the pylorax and a new program logger. --- src/pylorax/executils.py | 379 ++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 379 insertions(+), 0 deletions(-) create mode 100644 src/pylorax/executils.py
diff --git a/src/pylorax/executils.py b/src/pylorax/executils.py new file mode 100644 index 0000000..f1be30c --- /dev/null +++ b/src/pylorax/executils.py @@ -0,0 +1,379 @@ +# +# executil.py - subprocess execution utility functions +# +# Copyright (C) 1999-2011 +# Red Hat, Inc. All rights reserved. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +# Author(s): Erik Troan ewt@redhat.com +# + +import os, sys +import subprocess +import threading + +import logging +log = logging.getLogger("pylorax") +program_log = logging.getLogger("program") + +class ExecProduct(object): + def __init__(self, rc, stdout, stderr): + self.rc = rc + self.stdout = stdout + self.stderr = stderr + +#Python reimplementation of the shell tee process, so we can +#feed the pipe output into two places at the same time +class tee(threading.Thread): + def __init__(self, inputdesc, outputdesc, logmethod, command): + threading.Thread.__init__(self) + self.inputdesc = os.fdopen(inputdesc, "r") + self.outputdesc = outputdesc + self.logmethod = logmethod + self.running = True + self.command = command + + def run(self): + while self.running: + try: + data = self.inputdesc.readline() + except IOError: + self.logmethod("Can't read from pipe during a call to %s. " + "(program terminated suddenly?)" % self.command) + break + if data == "": + self.running = False + else: + self.logmethod(data.rstrip('\n')) + os.write(self.outputdesc, data) + + def stop(self): + self.running = False + return self + +## Run an external program and redirect the output to a file. +# @param command The command to run. +# @param argv A list of arguments. +# @param stdin The file descriptor to read stdin from. +# @param stdout The file descriptor to redirect stdout to. +# @param stderr The file descriptor to redirect stderr to. +# @param root The directory to chroot to before running command. +# @return The return code of command. +def execWithRedirect(command, argv, stdin = None, stdout = None, + stderr = None, root = '/'): + def chroot (): + os.chroot(root) + + stdinclose = stdoutclose = stderrclose = lambda : None + + argv = list(argv) + if isinstance(stdin, str): + if os.access(stdin, os.R_OK): + stdin = os.open(stdin, os.O_RDONLY) + stdinclose = lambda : os.close(stdin) + else: + stdin = sys.stdin.fileno() + elif isinstance(stdin, int): + pass + elif stdin is None or not isinstance(stdin, file): + stdin = sys.stdin.fileno() + + if isinstance(stdout, str): + stdout = os.open(stdout, os.O_RDWR|os.O_CREAT) + stdoutclose = lambda : os.close(stdout) + elif isinstance(stdout, int): + pass + elif stdout is None or not isinstance(stdout, file): + stdout = sys.stdout.fileno() + + if isinstance(stderr, str): + stderr = os.open(stderr, os.O_RDWR|os.O_CREAT) + stderrclose = lambda : os.close(stderr) + elif isinstance(stderr, int): + pass + elif stderr is None or not isinstance(stderr, file): + stderr = sys.stderr.fileno() + + program_log.info("Running... %s" % (" ".join([command] + argv),)) + + #prepare os pipes for feeding tee proceses + pstdout, pstdin = os.pipe() + perrout, perrin = os.pipe() + + env = os.environ.copy() + env.update({"LC_ALL": "C"}) + + try: + #prepare tee proceses + proc_std = tee(pstdout, stdout, program_log.info, command) + proc_err = tee(perrout, stderr, program_log.error, command) + + #start monitoring the outputs + proc_std.start() + proc_err.start() + + proc = subprocess.Popen([command] + argv, stdin=stdin, + stdout=pstdin, + stderr=perrin, + preexec_fn=chroot, cwd=root, + env=env) + + proc.wait() + ret = proc.returncode + + #close the input ends of pipes so we get EOF in the tee processes + os.close(pstdin) + os.close(perrin) + + #wait for the output to be written and destroy them + proc_std.join() + del proc_std + + proc_err.join() + del proc_err + + stdinclose() + stdoutclose() + stderrclose() + except OSError as e: + errstr = "Error running %s: %s" % (command, e.strerror) + log.error(errstr) + program_log.error(errstr) + #close the input ends of pipes so we get EOF in the tee processes + os.close(pstdin) + os.close(perrin) + proc_std.join() + proc_err.join() + + stdinclose() + stdoutclose() + stderrclose() + raise RuntimeError, errstr + + return ret + +## Run an external program and capture standard out. +# @param command The command to run. +# @param argv A list of arguments. +# @param stdin The file descriptor to read stdin from. +# @param stderr The file descriptor to redirect stderr to. +# @param root The directory to chroot to before running command. +# @return The output of command from stdout. +def execWithCapture(command, argv, stdin = None, stderr = None, root='/'): + def chroot(): + os.chroot(root) + + def closefds (): + stdinclose() + stderrclose() + + stdinclose = stderrclose = lambda : None + rc = "" + argv = list(argv) + + if isinstance(stdin, str): + if os.access(stdin, os.R_OK): + stdin = os.open(stdin, os.O_RDONLY) + stdinclose = lambda : os.close(stdin) + else: + stdin = sys.stdin.fileno() + elif isinstance(stdin, int): + pass + elif stdin is None or not isinstance(stdin, file): + stdin = sys.stdin.fileno() + + if isinstance(stderr, str): + stderr = os.open(stderr, os.O_RDWR|os.O_CREAT) + stderrclose = lambda : os.close(stderr) + elif isinstance(stderr, int): + pass + elif stderr is None or not isinstance(stderr, file): + stderr = sys.stderr.fileno() + + program_log.info("Running... %s" % (" ".join([command] + argv),)) + + env = os.environ.copy() + env.update({"LC_ALL": "C"}) + + try: + proc = subprocess.Popen([command] + argv, stdin=stdin, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + preexec_fn=chroot, cwd=root, + env=env) + + while True: + (outStr, errStr) = proc.communicate() + if outStr: + map(program_log.info, outStr.splitlines()) + rc += outStr + if errStr: + map(program_log.error, errStr.splitlines()) + os.write(stderr, errStr) + + if proc.returncode is not None: + break + except OSError as e: + log.error ("Error running " + command + ": " + e.strerror) + closefds() + raise RuntimeError, "Error running " + command + ": " + e.strerror + + closefds() + return rc + +def execWithCallback(command, argv, stdin = None, stdout = None, + stderr = None, echo = True, callback = None, + callback_data = None, root = '/'): + def chroot(): + os.chroot(root) + + def closefds (): + stdinclose() + stdoutclose() + stderrclose() + + stdinclose = stdoutclose = stderrclose = lambda : None + + argv = list(argv) + if isinstance(stdin, str): + if os.access(stdin, os.R_OK): + stdin = os.open(stdin, os.O_RDONLY) + stdinclose = lambda : os.close(stdin) + else: + stdin = sys.stdin.fileno() + elif isinstance(stdin, int): + pass + elif stdin is None or not isinstance(stdin, file): + stdin = sys.stdin.fileno() + + if isinstance(stdout, str): + stdout = os.open(stdout, os.O_RDWR|os.O_CREAT) + stdoutclose = lambda : os.close(stdout) + elif isinstance(stdout, int): + pass + elif stdout is None or not isinstance(stdout, file): + stdout = sys.stdout.fileno() + + if isinstance(stderr, str): + stderr = os.open(stderr, os.O_RDWR|os.O_CREAT) + stderrclose = lambda : os.close(stderr) + elif isinstance(stderr, int): + pass + elif stderr is None or not isinstance(stderr, file): + stderr = sys.stderr.fileno() + + program_log.info("Running... %s" % (" ".join([command] + argv),)) + + p = os.pipe() + p_stderr = os.pipe() + childpid = os.fork() + if not childpid: + os.close(p[0]) + os.close(p_stderr[0]) + os.dup2(p[1], 1) + os.dup2(p_stderr[1], 2) + os.dup2(stdin, 0) + os.close(stdin) + os.close(p[1]) + os.close(p_stderr[1]) + + os.execvp(command, [command] + argv) + os._exit(1) + + os.close(p[1]) + os.close(p_stderr[1]) + + log_output = '' + while 1: + try: + s = os.read(p[0], 1) + except OSError as e: + if e.errno != 4: + map(program_log.info, log_output.splitlines()) + raise IOError, e.args + + if echo: + os.write(stdout, s) + log_output += s + + if callback: + callback(s, callback_data=callback_data) + + # break out early if the sub-process changes status. + # no need to flush the stream if the process has exited + try: + (pid, status) = os.waitpid(childpid,os.WNOHANG) + if pid != 0: + break + except OSError as e: + log.critical("exception from waitpid: %s %s" %(e.errno, e.strerror)) + + if len(s) < 1: + break + + map(program_log.info, log_output.splitlines()) + + log_errors = '' + while 1: + try: + err = os.read(p_stderr[0], 128) + except OSError as e: + if e.errno != 4: + map(program_log.error, log_errors.splitlines()) + raise IOError, e.args + break + log_errors += err + if len(err) < 1: + break + + os.write(stderr, log_errors) + map(program_log.error, log_errors.splitlines()) + os.close(p[0]) + os.close(p_stderr[0]) + + try: + #if we didn't already get our child's exit status above, do so now. + if not pid: + (pid, status) = os.waitpid(childpid, 0) + except OSError as e: + log.critical("exception from waitpid: %s %s" %(e.errno, e.strerror)) + + closefds() + + rc = 1 + if os.WIFEXITED(status): + rc = os.WEXITSTATUS(status) + return ExecProduct(rc, log_output , log_errors) + +def _pulseProgressCallback(data, callback_data=None): + if callback_data: + callback_data.pulse() + +def execWithPulseProgress(command, argv, stdin = None, stdout = None, + stderr = None, echo = True, progress = None, + root = '/'): + return execWithCallback(command, argv, stdin=stdin, stdout=stdout, + stderr=stderr, echo=echo, callback=_pulseProgressCallback, + callback_data=progress, root=root) + +## Run a shell. +def execConsole(): + try: + proc = subprocess.Popen(["/bin/sh"]) + proc.wait() + except OSError as e: + raise RuntimeError, "Error running /bin/sh: " + e.strerror + +
From: "Brian C. Lane" bcl@redhat.com
This is so that imgutils.mkext4img can dynamically calculate the size of the image. --- src/pylorax/treebuilder.py | 5 ++++- 1 files changed, 4 insertions(+), 1 deletions(-)
diff --git a/src/pylorax/treebuilder.py b/src/pylorax/treebuilder.py index b1b5b0c..2dda313 100644 --- a/src/pylorax/treebuilder.py +++ b/src/pylorax/treebuilder.py @@ -130,7 +130,10 @@ class RuntimeBuilder(object): def create_runtime(self, outfile="/tmp/squashfs.img", compression="xz", compressargs=[], size=2): # make live rootfs image - must be named "LiveOS/rootfs.img" for dracut workdir = joinpaths(os.path.dirname(outfile), "runtime-workdir") - fssize = size * (1024*1024*1024) # 2GB sparse file compresses down to nothin' + if size: + fssize = size * (1024*1024*1024) # 2GB sparse file compresses down to nothin' + else: + fssize = None # Let mkext4img figure out the needed size os.makedirs(joinpaths(workdir, "LiveOS")) imgutils.mkext4img(self.vars.root, joinpaths(workdir, "LiveOS/rootfs.img"), label="Anaconda", size=fssize)
From: "Brian C. Lane" bcl@redhat.com
livemedia-creator uses an anaconda install media iso to install to a file image. virt-install is used to execute the kickstart. lorax is used to post-process the image file and create a bootable .iso from it.
Future additions will allow creation of EC2 images and output xml details about the install. --- src/sbin/livemedia-creator | 623 ++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 623 insertions(+), 0 deletions(-) create mode 100755 src/sbin/livemedia-creator
diff --git a/src/sbin/livemedia-creator b/src/sbin/livemedia-creator new file mode 100755 index 0000000..bd4e143 --- /dev/null +++ b/src/sbin/livemedia-creator @@ -0,0 +1,623 @@ +#!/usr/bin/env python +# +# Live Media Creator +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This program 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. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# +# Author(s): Brian C. Lane bcl@redhat.com +# +import logging +log = logging.getLogger("livemedia-creator") +program_log = logging.getLogger("program") + +import os +import sys +import uuid +import tempfile +import subprocess +import socket +import threading +import SocketServer +import libvirt +from time import sleep +import shutil +import traceback +import argparse + +# Use pykickstart to calculate disk image size +from pykickstart.parser import KickstartParser +from pykickstart.version import makeVersion + +# Use the Lorax treebuilder branch for iso creation +from pylorax.base import DataHolder +from pylorax.treebuilder import TreeBuilder, RuntimeBuilder +from pylorax.sysutils import joinpaths, remove, linktree +from pylorax.imgutils import PartitionMount +from pylorax.executils import execWithRedirect, execWithCapture + + +# Default parameters for rebuilding initramfs, overrice with --dracut-args +DRACUT_DEFAULT = ["--xz", "--omit", "plymouth"] + + +class LogRequestHandler(SocketServer.BaseRequestHandler): + """ + Handle monitoring and saving the logfiles from the virtual install + """ + def setup(self): + if self.server.log_path: + self.fp = open(self.server.log_path, "w") + else: + print "no log_path specified" + self.request.settimeout(10) + + def handle(self): + """ + Handle writing incoming data to a logfile and + checking the logs for any Tracebacks or other errors that indicate + that the install failed. + """ + line = "" + while True: + if self.server.kill: + break + + try: + data = self.request.recv(4096) + self.fp.write(data) + self.fp.flush() + + # check the data for errors and set error flag + # need to assemble it into lines so we can test for the error + # string. + while data: + more = data.split("\n", 1) + line += more[0] + if len(more) > 1: + self.iserror(line) + line = "" + data = more[1] + else: + data = None + + except socket.timeout: + pass + except: + break + + def finish(self): + self.fp.close() + + def iserror(self, line): + """ + Check a line to see if it contains an error indicating install failure + """ + if line.find("Traceback (") > -1 \ + or line.find("Out of memory:") > -1 \ + or line.find("Call Trace:") > -1 \ + or line.find("insufficient disk space:") > -1: + self.server.log_error = True + + +class LogServer(SocketServer.TCPServer): + """ + Add path to logfile + Add log error flag + Add a kill switch + """ + def __init__(self, log_path, *args, **kwargs): + self.kill = False + self.log_error = False + self.log_path = log_path + SocketServer.TCPServer.__init__(self, *args, **kwargs) + + def log_check(self): + return self.log_error + + +class LogMonitor(object): + """ + Contains all the stuff needed to setup a thread to listen to the logs + from the virtual install + """ + def __init__(self, log_path, host="localhost", port=0): + """ + Fire up the thread listening for logs + """ + self.server = LogServer(log_path, (host, port), LogRequestHandler) + self.host, self.port = self.server.server_address + self.log_path = log_path + self.server_thread = threading.Thread(target=self.server.handle_request) + self.server_thread.daemon = True + self.server_thread.start() + + def shutdown(self): + self.server.kill = True + self.server_thread.join() + + +class IsoMountpoint(object): + """ + Mount the iso on a temporary directory and check to make sure the + vmlinuz and initrd.img files exist + """ + def __init__( self, iso_path ): + self.iso_path = iso_path + self.mount_dir = tempfile.mkdtemp() + if not self.mount_dir: + raise Exception("Error creating temporary directory") + + cmd = ["mount","-o","loop",self.iso_path,self.mount_dir] + log.debug( cmd ) + execWithRedirect( cmd[0], cmd[1:] ) + + self.kernel = self.mount_dir+"/isolinux/vmlinuz" + self.initrd = self.mount_dir+"/isolinux/initrd.img" + + if os.path.isdir( self.mount_dir+"/repodata" ): + self.repo = self.mount_dir + else: + self.repo = None + + try: + for f in [self.kernel, self.initrd]: + if not os.path.isfile(f): + raise Exception("Missing file on iso: {0}".format(f)) + except: + self.umount() + raise + + def umount( self ): + cmd = ["umount", self.mount_dir] + log.debug( cmd ) + execWithRedirect( cmd[0], cmd[1:] ) + os.rmdir( self.mount_dir ) + + +class ImageMount(object): + """ + # kpartx -p p -v -a /tmp/diskV2DiCW.im + # add map loop2p1 (253:2): 0 3481600 linear /dev/loop2 2048 + # add map loop2p2 (253:3): 0 614400 linear /dev/loop2 3483648 + + Find the / partition and only mount that + """ + def __init__(self, disk_img): + """ + Use kpartx to mount an image onto temporary directories + return a list of the dirs + """ + self.disk_img = disk_img + + # call kpartx and parse the output + cmd = [ "kpartx", "-v", "-p", "p", "-a", disk_img ] + log.debug( cmd ) + kpartx_output = execWithCapture( cmd[0], cmd[1:] ) + log.debug( kpartx_output ) + self.loop_devices = [] + for line in kpartx_output.splitlines(): + # add map loop2p3 (253:4): 0 7139328 linear /dev/loop2 528384 + # 3rd element is size in 512 byte blocks + if line.startswith("add map "): + fields = line[8:].split() + self.loop_devices.append((fields[0], int(fields[3])*512)) + + log.debug( self.loop_devices ) + + # Mount the devices, if possible + self.mount_dir = None + mount_dir = tempfile.mkdtemp() + for dev, size in self.loop_devices: + cmd = ["mount", "/dev/mapper/"+dev, mount_dir] + log.debug( cmd ) + try: + execWithRedirect( cmd[0], cmd[1:] ) + if os.path.isfile(mount_dir+"/etc/fstab"): + self.mount_dir = mount_dir + self.mount_dev = dev + self.mount_size = size + break + cmd = ["umount", mount_dir] + execWithRedirect( cmd[0], cmd[1:] ) + except subprocess.CalledProcessError: + log.debug( traceback.format_exc() ) + + if self.mount_dir: + log.info( "Found root partition, mounted on {0}".format(self.mount_dir) ) + log.info( "size = {0}".format(self.mount_size) ) + + def umount(self): + """ + unmount the disk image + """ + if self.mount_dir: + cmd = ["umount", self.mount_dir] + log.debug( cmd ) + execWithRedirect( cmd[0], cmd[1:] ) + os.rmdir(self.mount_dir) + self.mount_dir = None + + cmd = ["kpartx", "-d", self.disk_img] + log.debug( cmd ) + execWithRedirect( cmd[0], cmd[1:] ) + + +class VirtualInstall( object ): + """ + Run virt-install using an iso and kickstart(s) + """ + def __init__( self, iso, ks_paths, disk_img, img_size=2, + kernel_args=None, memory=1024, vnc=None, + log_check=None, virtio_host="127.0.0.1", virtio_port=6080 ): + """ + + iso is an instance of IsoMountpoint + ks_paths is a list of paths to a kickstart files. All are injected, the + first one is the one executed. + disk_img is the path to a disk image (doesn't need to exist) + img_size is the size, in GiB, of the image if it doesn't exist + kernel_args are extra arguments to pass on the kernel cmdline + memory is the amount of ram to assign to the virt + vnc is passed to the --graphics command verbatim + log_check is a method that returns True of the log indicates an error + virtio_host and virtio_port are used to communicate with the log monitor + """ + self.virt_name = "LiveOS-"+str(uuid.uuid4()) + # add --graphics none later + # add whatever serial cmds are needed later + cmd = [ "virt-install", "-n", self.virt_name, + "-r", str(memory), + "--noreboot", + "--noautoconsole" ] + + cmd.append("--graphics") + if vnc: + cmd.append(vnc) + else: + cmd.append("none") + + for ks in ks_paths: + cmd.append("--initrd-inject") + cmd.append(ks) + disk_opts = "path={0}".format(disk_img) + disk_opts += ",format=raw" + if not os.path.isfile(disk_img): + disk_opts += ",size={0}".format(img_size) + extra_args = "ks=file:/{0}".format(os.path.basename(ks_paths[0])) + if kernel_args: + extra_args += " "+kernel_args + if not vnc: + extra_args += " console=/dev/ttyS0" + channel_args = "tcp,host={0}:{1},mode=connect,target_type=virtio" \ + ",name=org.fedoraproject.anaconda.log.0".format( + virtio_host, virtio_port) + [cmd.append(o) for o in ["--disk", disk_opts, + "--extra-args", extra_args, + "--location", iso.mount_dir, + "--channel", channel_args]] + log.debug( cmd ) + + rc = execWithRedirect( cmd[0], cmd[1:] ) + if rc: + raise Exception("Problem starting virtual install") + + conn = libvirt.openReadOnly(None) + dom = conn.lookupByName(self.virt_name) + + # TODO: If vnc has been passed, we should look up the port and print that + # for the user at this point + + while dom.isActive() and not log_check(): + sys.stdout.write(".") + sys.stdout.flush() + sleep(10) + print + + if log_check(): + log.info( "Installation error detected. See logfile." ) + else: + log.info( "Install finished. Or at least virt shut down." ) + + def destroy( self ): + """ + Make sure the virt has been shut down and destroyed + + Could use libvirt for this instead. + """ + log.info( "Shutting down {0}".format(self.virt_name) ) + subprocess.call(["virsh","destroy",self.virt_name]) + subprocess.call(["virsh","undefine",self.virt_name]) + + +def get_kernels( boot_dir ): + """ + Examine the vmlinuz-* versions and return a list of them + """ + files = os.listdir(boot_dir) + return [f[8:] for f in files if f.startswith("vmlinuz-")] + + +def make_livecd( disk_img, squashfs_args="", templatedir=None, + title="Linux", project="Linux", releasever=16 ): + """ + Take the content from the disk image and make a livecd out of it + + This uses wwood's squashfs live initramfs method: + * put the real / into LiveOS/rootfs.img + * make a squashfs of the LiveOS/rootfs.img tree + * make a simple initramfs with the squashfs.img and /etc/cmdline in it + * make a cpio of that tree + * append the squashfs.cpio to a dracut initramfs for each kernel installed + + Then on boot dracut reads /etc/cmdline which points to the squashfs.img + mounts that and then mounts LiveOS/rootfs.img as / + + """ + with PartitionMount( disk_img ) as img_mount: + kernel_list = get_kernels( joinpaths( img_mount.mount_dir, "boot" ) ) + log.debug( "kernel_list = {0}".format(kernel_list) ) + if kernel_list: + kernel_arch = kernel_list[0].split(".")[-1] + else: + kernel_arch = "i386" + log.debug( "kernel_arch = {0}".format(kernel_arch) ) + + work_dir = tempfile.mkdtemp() + log.info( "working dir is {0}".format(work_dir) ) + + runtime = "images/install.img" + # This is a mounted image partition, cannot hardlink to it, so just use it + installroot = img_mount.mount_dir + # symlink installroot/images to work_dir/images so we don't run out of space + os.makedirs( joinpaths( work_dir, "images" ) ) + + # TreeBuilder expects the config files to be in /tmp/config_files + # I think these should be release specific, not from lorax, but for now + configdir = joinpaths(templatedir,"config_files/live/") + configdir_path = "tmp/config_files" + fullpath = joinpaths(installroot, configdir_path) + if os.path.exists(fullpath): + remove(fullpath) + shutil.copytree(configdir, fullpath) + + # Fake yum object + fake_yum = DataHolder( conf=DataHolder( installroot=installroot ) ) + # Fake arch with only basearch set + arch = DataHolder( basearch=kernel_arch, libdir=None, buildarch=None ) + # TODO: Need to get release info from someplace... + product = DataHolder( name=project, version=releasever, release="", + variant="", bugurl="", is_beta=False ) + + rb = RuntimeBuilder( product, arch, fake_yum ) + log.info( "Creating runtime" ) + rb.create_runtime( joinpaths( work_dir, runtime ), size=None ) + + # Link /images to work_dir/images to make the templates happy + if os.path.islink( joinpaths( installroot, "images" ) ): + os.unlink( joinpaths( installroot, "images" ) ) + subprocess.check_call(["/bin/ln", "-s", joinpaths( work_dir, "images" ), + joinpaths( installroot, "images" )]) + + tb = TreeBuilder( product=product, arch=arch, + inroot=installroot, outroot=work_dir, + runtime=runtime, templatedir=templatedir) + log.info( "Rebuilding initrds" ) + if not opts.dracut_args: + dracut_args = DRACUT_DEFAULT + else: + dracut_args = [] + for arg in opts.dracut_args: + dracut_args += arg.split(" ", 1) + log.info( "dracut args = {0}".format( dracut_args ) ) + tb.rebuild_initrds( add_args=dracut_args ) + log.info( "Building boot.iso" ) + tb.build() + + return work_dir + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( description="Create Live Install Media", + fromfile_prefix_chars="@" ) + + # These two are mutually exclusive, one is required + action = parser.add_mutually_exclusive_group( required=True ) + action.add_argument( "--make-iso", action="store_true", + help="Build a live iso" ) + action.add_argument( "--make-disk", action="store_true", + help="Build a disk image" ) + action.add_argument( "--make-appliance", action="store_true", + help="Build an appliance image and XML description" ) + action.add_argument( "--make-ami", action="store_true", + help="Build an ami image" ) + + source = parser.add_mutually_exclusive_group( required=True ) + source.add_argument( "--iso", type=os.path.abspath, + help="Anaconda installation .iso path to use for virt-install" ) + source.add_argument( "--disk-image", type=os.path.abspath, + help="Path to disk image to use for creating final image" ) + + parser.add_argument( "--ks", action="append", type=os.path.abspath, + help="Kickstart file defining the install." ) + parser.add_argument( "--image-only", action="store_true", + help="Exit after creating disk image." ) + parser.add_argument( "--keep-image", action="store_true", + help="Keep raw disk image after .iso creation" ) + parser.add_argument( "--no-virt", action="store_true", + help="Use Anaconda's image install instead of virt-install" ) + + parser.add_argument( "--logfile", default="./livemedia.log", + type=os.path.abspath, + help="Path to logfile" ) + parser.add_argument( "--lorax-templates", default="/usr/share/lorax/", + type=os.path.abspath, + help="Path to mako templates for lorax" ) + parser.add_argument( "--tmp", default="/tmp", type=os.path.abspath, + help="Top level temporary directory" ) + parser.add_argument( "--resultdir", default=None, dest="result_dir", + type=os.path.abspath, + help="Directory to copy the resulting images and iso into. " + "Defaults to the temporary working directory") + + # Group of arguments to pass to virt-install + virt_group = parser.add_argument_group("virt-install arguments") + virt_group.add_argument("--ram", metavar="MEMORY", default=1024, + help="Memory to allocate for installer in megabytes." ) + virt_group.add_argument("--vcpus", default=1, + help="Passed to --vcpus command" ) + virt_group.add_argument("--vnc", + help="Passed to --graphics command" ) + virt_group.add_argument("--arch", + help="Passed to --arch command" ) + virt_group.add_argument( "--kernel-args", + help="Additional argument to pass to the installation kernel" ) + + # dracut arguments + dracut_group = parser.add_argument_group( "dracut arguments" ) + dracut_group.add_argument( "--dracut-arg", action="append", dest="dracut_args", + help="Argument to pass to dracut when " + "rebuilding the initramfs. Pass this " + "once for each argument. NOTE: this " + "overrides the default. (default: %s)" % (DRACUT_DEFAULT,) ) + + parser.add_argument( "--title", default="Linux Live Media", + help="Substituted for @TITLE@ in bootloader config files" ) + parser.add_argument( "--project", default="Linux", + help="substituted for @PROJECT@ in bootloader config files" ) + parser.add_argument( "--releasever", type=int, default=16, + help="substituted for @VERSION@ in bootloader config files" ) + parser.add_argument( "--squashfs_args", + help="additional squashfs args" ) + + opts = parser.parse_args() + + # Setup logging to console and to logfile + log.setLevel( logging.DEBUG ) + sh = logging.StreamHandler() + sh.setLevel( logging.INFO ) + fmt = logging.Formatter("%(asctime)s: %(message)s") + sh.setFormatter( fmt ) + log.addHandler( sh ) + fh = logging.FileHandler( filename=opts.logfile, mode="w" ) + fh.setLevel( logging.DEBUG ) + fmt = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s") + fh.setFormatter( fmt ) + log.addHandler( fh ) + # External program output log + program_log.setLevel(logging.DEBUG) + logfile = os.path.abspath(os.path.dirname(opts.logfile))+"/program.log" + fh = logging.FileHandler( filename=logfile, mode="w") + fh.setLevel( logging.DEBUG ) + program_log.addHandler( fh ) + + log.debug( opts ) + + if os.getuid() != 0: + log.error("You need to run this as root") + sys.exit( 1 ) + + if not os.path.exists( opts.lorax_templates ): + log.error( "The lorax templates directory ({0}) doesn't" + " exist.".format( opts.lorax_templates ) ) + sys.exit( 1 ) + + if opts.result_dir and os.path.exists(opts.result_dir): + log.error( "The results_dir ({0}) should not exist, please delete or " + "move its contents".format( opts.result_dir )) + sys.exit( 1 ) + + if opts.iso and not os.path.exists( opts.iso ): + log.error( "The iso {0} is missing.".format( opts.iso ) ) + sys.exit( 1 ) + + if opts.disk_image and not os.path.exists( opts.disk_image ): + log.error( "The disk image {0} is missing.".format( opts.disk_image ) ) + sys.exit( 1 ) + + if opts.no_virt: + log.error( "--no-virt is not yet implemented." ) + sys.exit( 1 ) + + if opts.make_appliance: + log.error( "--make-appliance is not yet implemented." ) + sys.exit( 1 ) + + if opts.make_ami: + log.error( "--make-ami is not yet implemented." ) + sys.exit( 1 ) + + # Use virt-install to make the disk image + if opts.iso: + disk_img = tempfile.mktemp( prefix="disk", suffix=".img", dir=opts.tmp ) + install_log = os.path.abspath(os.path.dirname(opts.logfile))+"/virt-install.log" + + log.info( "disk_img = {0}".format(disk_img) ) + log.info( "install_log = {0}".format(install_log) ) + + # Parse the kickstart to get the partition sizes + ks_version = makeVersion() + ks = KickstartParser( ks_version, errorsAreFatal=False, missingIncludeIsFatal=False ) + ks.readKickstart( opts.ks[0] ) + disk_size = 1 + (sum( [p.size for p in ks.handler.partition.partitions] ) / 1024) + log.info( "disk_size = {0}GB".format(disk_size) ) + + iso_mount = IsoMountpoint( opts.iso ) + log_monitor = LogMonitor( install_log ) + + virt = VirtualInstall( iso_mount, opts.ks, disk_img, disk_size, + opts.kernel_args, opts.ram, opts.vnc, + log_check = log_monitor.server.log_check, + virtio_host = log_monitor.host, + virtio_port = log_monitor.port ) + virt.destroy() + log_monitor.shutdown() + iso_mount.umount() + + if log_monitor.server.log_check(): + log.error( "Install failed" ) + if not opts.keep_image: + log.info( "Removing bad disk image" ) + os.unlink( disk_img ) + sys.exit( 1 ) + else: + log.info( "Disk Image install successful" ) + + + result_dir = None + if opts.make_iso and not opts.image_only: + result_dir = make_livecd( opts.disk_image or disk_img, opts.squashfs_args, + opts.lorax_templates, + opts.title, opts.project, opts.releasever ) + + if not opts.keep_image and not opts.disk_image: + os.unlink( disk_img ) + + if opts.result_dir: + shutil.copytree( result_dir, opts.result_dir ) + shutil.rmtree( result_dir ) + + log.info("SUMMARY") + log.info("-------") + log.info("Logs are in {0}".format(os.path.abspath(os.path.dirname(opts.logfile)))) + if opts.keep_image: + log.info("Disk image is at {0}".format(disk_img)) + else: + log.info("Disk image erased") + if result_dir: + log.info("Results are in {0}".format(opts.result_dir or result_dir)) + + sys.exit( 0 ) +
From: "Brian C. Lane" bcl@redhat.com
--- share/config_files/live/x86/boot.msg | 5 ++ share/config_files/live/x86/grub.conf | 8 +++ share/config_files/live/x86/isolinux.cfg | 98 ++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 0 deletions(-) create mode 100644 share/config_files/live/x86/boot.msg create mode 100644 share/config_files/live/x86/grub.conf create mode 100644 share/config_files/live/x86/isolinux.cfg
diff --git a/share/config_files/live/x86/boot.msg b/share/config_files/live/x86/boot.msg new file mode 100644 index 0000000..ff54899 --- /dev/null +++ b/share/config_files/live/x86/boot.msg @@ -0,0 +1,5 @@ + +splash.lss + + - Press the 01<ENTER>07 key to begin the installation process. + diff --git a/share/config_files/live/x86/grub.conf b/share/config_files/live/x86/grub.conf new file mode 100644 index 0000000..9bd4d53 --- /dev/null +++ b/share/config_files/live/x86/grub.conf @@ -0,0 +1,8 @@ +#debug --graphics +default=0 +splashimage=@SPLASHPATH@ +timeout 5 +hiddenmenu +title @PRODUCT@ @VERSION@ + kernel @KERNELPATH@ @ROOT@ + initrd @INITRDPATH@ diff --git a/share/config_files/live/x86/isolinux.cfg b/share/config_files/live/x86/isolinux.cfg new file mode 100644 index 0000000..5278a8b --- /dev/null +++ b/share/config_files/live/x86/isolinux.cfg @@ -0,0 +1,98 @@ +default vesamenu.c32 +timeout 600 + +menu autoboot Starting @PRODUCT@ in # second{,s}. Press any key to interrupt. + +# Clear the screen when exiting the menu, instead of leaving the menu displayed. +# For vesamenu, this means the graphical background is still displayed without +# the menu itself for as long as the screen remains in graphics mode. +menu clear +menu background splash.png +menu title @PRODUCT@ @VERSION@ +menu vshift 8 +menu rows 18 +menu margin 8 +#menu hidden +menu helpmsgrow 15 +menu tabmsgrow 13 + +# Border Area +menu color border * #00000000 #00000000 none + +# Selected item +menu color sel 0 #ffffffff #00000000 none + +# Title bar +menu color title 0 #ff7ba3d0 #00000000 none + +# Press [Tab] message +menu color tabmsg 0 #ff3a6496 #00000000 none + +# Unselected menu item +menu color unsel 0 #84b8ffff #00000000 none + +# Selected hotkey +menu color hotsel 0 #84b8ffff #00000000 none + +# Unselected hotkey +menu color hotkey 0 #ffffffff #00000000 none + +# Help text +menu color help 0 #ffffffff #00000000 none + +# A scrollbar of some type? Not sure. +menu color scrollbar 0 #ffffffff #ff355594 none + +# Timeout msg +menu color timeout 0 #ffffffff #00000000 none +menu color timeout_msg 0 #ffffffff #00000000 none + +# Command prompt text +menu color cmdmark 0 #84b8ffff #00000000 none +menu color cmdline 0 #ffffffff #00000000 none + +# Do not display the actual menu unless the user presses a key. All that is displayed is a timeout message. + +menu tabmsg Press Tab for full configuration options on menu items. +menu separator # insert an empty line +menu separator # insert an empty line +label linux + menu label ^Start @PRODUCT@ + menu default + kernel vmlinuz + append initrd=initrd.img @ROOT@ quiet liveimg rd.luks=0 rd.md=0 rd.dm=0 +menu separator # insert an empty line +# utilities submenu +menu begin ^Troubleshooting + menu title Troubleshooting +label vesa + menu indent count 5 + menu label Start @PRODUCT@ in ^basic graphics mode. + text help + Try this option out if you're having trouble installing + @PRODUCT@. + endtext + kernel vmlinuz + append initrd=initrd.img @ROOT@ xdriver=vesa nomodeset quiet liveimg rd.luks=0 rd.md=0 rd.dm=0 +label memtest + menu label Run a ^memory test. + text help + If your system is having issues, a problem with your + system's memory may be the cause. Use this utility to + see if the memory is working correctly. + endtext + kernel memtest +menu separator # insert an empty line +label local + menu label Boot from ^local drive. + localboot 0xffff +menu separator # insert an empty line +menu separator # insert an empty line +label returntomain + menu label Return to ^main menu. + menu exit +menu end +#label local +# menu label Exit this menu and boot from ^local disk. +# localboot 0xffff +
From: "Brian C. Lane" bcl@redhat.com
--- README.livemedia-creator | 170 ++++++++++++++++++++ docs/fedora-livemedia.ks | 388 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 558 insertions(+), 0 deletions(-) create mode 100644 README.livemedia-creator create mode 100644 docs/fedora-livemedia.ks
diff --git a/README.livemedia-creator b/README.livemedia-creator new file mode 100644 index 0000000..9999258 --- /dev/null +++ b/README.livemedia-creator @@ -0,0 +1,170 @@ + +INTRO +----- +livemedia-creator uses Anaconda, kickstart and Lorax to create bootable media +such as live iso's that use the same install path as a normal system install. + +The general idea is to use virt-install to install into a disk image and then +use the disk image to create the bootable media. + +livemedia-creator --help will describe all of the options available. At the +minimum you need: + +--make-iso to create a final bootable .iso +--iso to specify the Anaconda install media to use with virt-install +--ks is the kickstart to use to install the system + + +QUICKSTART +---------- +sudo livemedia-creator --make-iso \ +--iso=/extra/iso/Fedora-16-x86_64-netinst.iso --ks=./fedora-livemedia.ks + +If you are using the lorax git repo you can run it like so: + +sudo PATH=./src/sbin/:$PATH PYTHONPATH=./src/ ./src/sbin/livemedia-creator \ +--make-iso --iso=/extra/iso/Fedora-16-x86_64-netinst.iso \ +--ks=./docs/livemedia-example.ks --lorax-templates=./share/ + +If you want to watch the install you can pass '--vnc vnc' and use a vnc +client to connect to localhost:0 + +This is usually a good idea when testing changes to the kickstart. It tries +to monitor the logs for fatal errors, but may not catch everything. + + +HOW IT WORKS +------------ +The --make-* switches define the final output. Currently only --make-iso +is working. + +You then need to either pass --iso and --ks in order to create a disk image +using virt-install, or --disk-image to use a disk image from a previous run +to create the .iso + +virt-install boots using the passed Anaconda installer iso and installs the +system based on the kickstart. The %post section of the kickstart is used to +customize the installed system in the same way that current spin-kickstarts +do. + +livemedia-creator monitors the install process for problems by watching the +install logs. They are written to the current directory or to the base directory +specified by the --logfile command. You can also monitor the install by passing +--vnc vnc and using a vnc client. This is recommended when first modifying a +kickstart, since there are still places where Anaconda may get stuck without +the log monitor catching it. + +The output from this process is a partitioned disk image. kpartx can be used +to mount and examine it when there is a problem with the install. It can also +be booted using kvm. + +Once the disk image is created it copies the / partition into a formatted +disk image which is then used as the input to lorax for creation of the +final media. + +The final image is created by lorax, using the templates in /usr/share/lorax/ +or the directory specified by --lorax-templates + +Currently the standard lorax templates are used to make a bootable iso, but +it should be possible to modify them to output other results. They are +written using the Mako template system which is very flexible. + + +KICKSTARTS +---------- +Existing spin kickstarts can be used to create live media with a few changes. +Here are the steps I used to convert the XFCE spin. + +1. Flatten the xfce kickstart using ksflatten +2. Add zerombr so you don't get the disk init dialog +3. Add clearpart --all +4. Add swap and biosboot partitions +5. bootloader target +6. Add shutdown to the kickstart +7. Add network --bootproto=dhcp --activate to activate the network + This works for F16 builds but for F15 and before you need to pass + something on the cmdline that activate the network, like sshd. + +livemedia-creator --kernel-args="sshd" + +8. Add a root password + +rootpw rootme +network --bootproto=dhcp --activate +zerombr +clearpart --all +bootloader --location=mbr +part biosboot --size=1 +part swap --size=512 +shutdown + +9. In the livesys script section of the %post remove the root password. This + really depends on how the spin wants to work. You could add the live user + that you create to the %wheel group so that sudo works if you wanted to. + +passwd -d root > /dev/null + +10. Remove /etc/fstab in %post, dracut handles mounting the rootfs + +cat /dev/null > /dev/fstab + +11. Don't delete initramfs files from /boot in %post +12. Have grub-efi in the package list + +One drawback to using virt-install is that it pulls the packages from +the repo each time you run it. To speed things up you either need a local +mirror of the packages, or you can use a caching proxy. When using a proxy +you pass it to livemedia-creator like so: + +--kernel-args="proxy=http://proxy.yourdomain.com:3128" + +You also need to use a specific mirror instead of mirrormanager so that the +packages will get cached: + +url --url="http://download.fedora.redhat.com/pub/fedora/linux/development/16/x86_64/os/" + +You can also add an update repo, but don't name it updates. Add --proxy to +it as well. + + +DEBUGGING PROBLEMS +------------------ +Cleaning up an aborted (ctrl-c) virt-install run (as root): +virsh list to show the name of the virt +virsh destroy <name> +virsh undefine <name> +umount /tmp/tmpXXXX +rm -rf /tmp/tmpXXXX +rm /tmp/diskXXXXX + +The logs from the virt-install run are stored in virt-install.log, +logs from livemedia-creator are in livemedia.log and program.log + +You can add --image-only to skip the .iso creation and examine the resulting +disk image. Or you can pass --keep-image to keep it around after lorax is +run. + + +THE FUTURE +---------- +The current release only supports creating live iso's. In the future +I want it to be able to create ami images as well as appliance images. + +It is also limited to x86 architectures because of it's use of virt-install. +I hope to be able to support other arches by using Anaconda's image install +feature instead of virt-install. This will require that livemedia-creator +be running on the same release as is being created in order to avoid odd +problems. + +I would like to provide a set of alternate lorax template scripts to create +other media. + + +HACKING +------- +Development on this will take place as part of the lorax project, and on the +anaconda-devel-list mailing list. + +Feedback, enhancements and bugs are welcome. +You can use http://bugzilla.redhat.com to report bugs. + diff --git a/docs/fedora-livemedia.ks b/docs/fedora-livemedia.ks new file mode 100644 index 0000000..667b560 --- /dev/null +++ b/docs/fedora-livemedia.ks @@ -0,0 +1,388 @@ +#version=DEVEL +sshpw --username=root --plaintext randOmStrinGhERE +# Firewall configuration +firewall --enabled --service=mdns +# Use network installation +url --url="http://download.fedora.redhat.com/pub/fedora/linux/releases/16/Everything/x8..." +# X Window System configuration information +xconfig --startxonboot +# Root password +rootpw --plaintext removethispw +# Network information +network --bootproto=dhcp --device=eth0 --onboot=on --activate +# System authorization information +auth --useshadow --enablemd5 +# System keyboard +keyboard us +# System language +lang en_US.UTF-8 +# SELinux configuration +selinux --enforcing +# Installation logging level +logging --level=info +# Shutdown after installation +shutdown +# System services +services --disabled="network,sshd" --enabled="NetworkManager" +# System timezone +timezone US/Eastern +# System bootloader configuration +bootloader --location=mbr +# Clear the Master Boot Record +zerombr +# Partition clearing information +clearpart --all +# Disk partitioning information +part biosboot --size=1 +part / --fstype="ext4" --size=4000 +part swap --size=1000 + +%post +# FIXME: it'd be better to get this installed from a package +cat > /etc/rc.d/init.d/livesys << EOF +#!/bin/bash +# +# live: Init script for live image +# +# chkconfig: 345 00 99 +# description: Init script for live image. + +. /etc/init.d/functions + +if ! strstr "`cat /proc/cmdline`" liveimg || [ "$1" != "start" ]; then + exit 0 +fi + +if [ -e /.liveimg-configured ] ; then + configdone=1 +fi + +exists() { + which $1 >/dev/null 2>&1 || return + $* +} + +touch /.liveimg-configured + +# mount live image +if [ -b `readlink -f /dev/live` ]; then + mkdir -p /mnt/live + mount -o ro /dev/live /mnt/live 2>/dev/null || mount /dev/live /mnt/live +fi + +livedir="LiveOS" +for arg in `cat /proc/cmdline` ; do + if [ "${arg##live_dir=}" != "${arg}" ]; then + livedir=${arg##live_dir=} + return + fi +done + +# enable swaps unless requested otherwise +swaps=`blkid -t TYPE=swap -o device` +if ! strstr "`cat /proc/cmdline`" noswap && [ -n "$swaps" ] ; then + for s in $swaps ; do + action "Enabling swap partition $s" swapon $s + done +fi +if ! strstr "`cat /proc/cmdline`" noswap && [ -f /mnt/live/${livedir}/swap.img ] ; then + action "Enabling swap file" swapon /mnt/live/${livedir}/swap.img +fi + +mountPersistentHome() { + # support label/uuid + if [ "${homedev##LABEL=}" != "${homedev}" -o "${homedev##UUID=}" != "${homedev}" ]; then + homedev=`/sbin/blkid -o device -t "$homedev"` + fi + + # if we're given a file rather than a blockdev, loopback it + if [ "${homedev##mtd}" != "${homedev}" ]; then + # mtd devs don't have a block device but get magic-mounted with -t jffs2 + mountopts="-t jffs2" + elif [ ! -b "$homedev" ]; then + loopdev=`losetup -f` + if [ "${homedev##/mnt/live}" != "${homedev}" ]; then + action "Remounting live store r/w" mount -o remount,rw /mnt/live + fi + losetup $loopdev $homedev + homedev=$loopdev + fi + + # if it's encrypted, we need to unlock it + if [ "$(/sbin/blkid -s TYPE -o value $homedev 2>/dev/null)" = "crypto_LUKS" ]; then + echo + echo "Setting up encrypted /home device" + plymouth ask-for-password --command="cryptsetup luksOpen $homedev EncHome" + homedev=/dev/mapper/EncHome + fi + + # and finally do the mount + mount $mountopts $homedev /home + # if we have /home under what's passed for persistent home, then + # we should make that the real /home. useful for mtd device on olpc + if [ -d /home/home ]; then mount --bind /home/home /home ; fi + [ -x /sbin/restorecon ] && /sbin/restorecon /home + if [ -d /home/liveuser ]; then USERADDARGS="-M" ; fi +} + +findPersistentHome() { + for arg in `cat /proc/cmdline` ; do + if [ "${arg##persistenthome=}" != "${arg}" ]; then + homedev=${arg##persistenthome=} + return + fi + done +} + +if strstr "`cat /proc/cmdline`" persistenthome= ; then + findPersistentHome +elif [ -e /mnt/live/${livedir}/home.img ]; then + homedev=/mnt/live/${livedir}/home.img +fi + +# if we have a persistent /home, then we want to go ahead and mount it +if ! strstr "`cat /proc/cmdline`" nopersistenthome && [ -n "$homedev" ] ; then + action "Mounting persistent /home" mountPersistentHome +fi + +# make it so that we don't do writing to the overlay for things which +# are just tmpdirs/caches +mount -t tmpfs -o mode=0755 varcacheyum /var/cache/yum +mount -t tmpfs tmp /tmp +mount -t tmpfs vartmp /var/tmp +[ -x /sbin/restorecon ] && /sbin/restorecon /var/cache/yum /tmp /var/tmp >/dev/null 2>&1 + +if [ -n "$configdone" ]; then + exit 0 +fi + +# add fedora user with no passwd +action "Adding live user" useradd $USERADDARGS -c "Live System User" liveuser +passwd -d liveuser > /dev/null + +# turn off firstboot for livecd boots +chkconfig --level 345 firstboot off 2>/dev/null +# We made firstboot a native systemd service, so it can no longer be turned +# off with chkconfig. It should be possible to turn it off with systemctl, but +# that doesn't work right either. For now, this is good enough: the firstboot +# service will start up, but this tells it not to run firstboot. I suspect the +# other services 'disabled' below are not actually getting disabled properly, +# with systemd, but we can look into that later. - AdamW 2010/08 F14Alpha +echo "RUN_FIRSTBOOT=NO" > /etc/sysconfig/firstboot + +# don't start yum-updatesd for livecd boots +chkconfig --level 345 yum-updatesd off 2>/dev/null + +# turn off mdmonitor by default +chkconfig --level 345 mdmonitor off 2>/dev/null + +# turn off setroubleshoot on the live image to preserve resources +chkconfig --level 345 setroubleshoot off 2>/dev/null + +# don't do packagekit checking by default +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -s -t string /apps/gnome-packagekit/update-icon/frequency_get_updates never >/dev/null +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -s -t string /apps/gnome-packagekit/update-icon/frequency_get_upgrades never >/dev/null +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -s -t string /apps/gnome-packagekit/update-icon/frequency_refresh_cache never >/dev/null +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -s -t bool /apps/gnome-packagekit/update-icon/notify_available false >/dev/null +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -s -t bool /apps/gnome-packagekit/update-icon/notify_distro_upgrades false >/dev/null +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -s -t bool /apps/gnome-packagekit/enable_check_firmware false >/dev/null +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -s -t bool /apps/gnome-packagekit/enable_check_hardware false >/dev/null +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -s -t bool /apps/gnome-packagekit/enable_codec_helper false >/dev/null +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -s -t bool /apps/gnome-packagekit/enable_font_helper false >/dev/null +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -s -t bool /apps/gnome-packagekit/enable_mime_type_helper false >/dev/null + + +# don't start cron/at as they tend to spawn things which are +# disk intensive that are painful on a live image +chkconfig --level 345 crond off 2>/dev/null +chkconfig --level 345 atd off 2>/dev/null +chkconfig --level 345 anacron off 2>/dev/null +chkconfig --level 345 readahead_early off 2>/dev/null +chkconfig --level 345 readahead_later off 2>/dev/null + +# Stopgap fix for RH #217966; should be fixed in HAL instead +touch /media/.hal-mtab + +# workaround clock syncing on shutdown that we don't want (#297421) +sed -i -e 's/hwclock/no-such-hwclock/g' /etc/rc.d/init.d/halt + +# and hack so that we eject the cd on shutdown if we're using a CD... +if strstr "`cat /proc/cmdline`" CDLABEL= ; then + cat >> /sbin/halt.local << FOE +#!/bin/bash +# XXX: This often gets stuck during shutdown because /etc/init.d/halt +# (or something else still running) wants to read files from the block\ +# device that was ejected. Disable for now. Bug #531924 +# we want to eject the cd on halt, but let's also try to avoid +# io errors due to not being able to get files... +#cat /sbin/halt > /dev/null +#cat /sbin/reboot > /dev/null +#/usr/sbin/eject -p -m $(readlink -f /dev/live) >/dev/null 2>&1 +#echo "Please remove the CD from your drive and press Enter to finish restarting" +#read -t 30 < /dev/console +FOE +chmod +x /sbin/halt.local +fi + +EOF + +# bah, hal starts way too late +cat > /etc/rc.d/init.d/livesys-late << EOF +#!/bin/bash +# +# live: Late init script for live image +# +# chkconfig: 345 99 01 +# description: Late init script for live image. + +. /etc/init.d/functions + +if ! strstr "`cat /proc/cmdline`" liveimg || [ "$1" != "start" ] || [ -e /.liveimg-late-configured ] ; then + exit 0 +fi + +exists() { + which $1 >/dev/null 2>&1 || return + $* +} + +touch /.liveimg-late-configured + +# read some variables out of /proc/cmdline +for o in `cat /proc/cmdline` ; do + case $o in + ks=*) + ks="--kickstart=${o#ks=}" + ;; + xdriver=*) + xdriver="${o#xdriver=}" + ;; + esac +done + +# if liveinst or textinst is given, start anaconda +if strstr "`cat /proc/cmdline`" liveinst ; then + plymouth --quit + /usr/sbin/liveinst $ks +fi +if strstr "`cat /proc/cmdline`" textinst ; then + plymouth --quit + /usr/sbin/liveinst --text $ks +fi + +# configure X, allowing user to override xdriver +if [ -n "$xdriver" ]; then + cat > /etc/X11/xorg.conf.d/00-xdriver.conf <<FOE +Section "Device" + Identifier "Videocard0" + Driver "$xdriver" +EndSection +FOE +fi + +EOF + +chmod 755 /etc/rc.d/init.d/livesys +/sbin/restorecon /etc/rc.d/init.d/livesys +/sbin/chkconfig --add livesys + +chmod 755 /etc/rc.d/init.d/livesys-late +/sbin/restorecon /etc/rc.d/init.d/livesys-late +/sbin/chkconfig --add livesys-late + +# work around for poor key import UI in PackageKit +rm -f /var/lib/rpm/__db* +rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-fedora +echo "Packages within this LiveCD" +rpm -qa + +# go ahead and pre-make the man -k cache (#455968) +/usr/bin/mandb + +# make sure there aren't core files lying around +rm -f /core* + +# convince readahead not to collect +rm -f /.readahead_collect +touch /var/lib/readahead/early.sorted + +%end + +%post --nochroot +cp $INSTALL_ROOT/usr/share/doc/*-release-*/GPL $LIVE_ROOT/GPL + +# only works on x86, x86_64 +if [ "$(uname -i)" = "i386" -o "$(uname -i)" = "x86_64" ]; then + if [ ! -d $LIVE_ROOT/LiveOS ]; then mkdir -p $LIVE_ROOT/LiveOS ; fi + cp /usr/bin/livecd-iso-to-disk $LIVE_ROOT/LiveOS +fi +%end + +%post +cat >> /etc/rc.d/init.d/livesys << EOF +# disable screensaver locking +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -s -t bool /apps/gnome-screensaver/lock_enabled false >/dev/null +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -s -t bool /desktop/gnome/lockdown/disable_lock_screen true >/dev/null + +# set up timed auto-login for after 60 seconds +cat >> /etc/gdm/custom.conf << FOE +[daemon] +AutomaticLoginEnable=True +AutomaticLogin=liveuser +FOE + +# Show harddisk install on the desktop +sed -i -e 's/NoDisplay=true/NoDisplay=false/' /usr/share/applications/liveinst.desktop +mkdir /home/liveuser/Desktop +cp /usr/share/applications/liveinst.desktop /home/liveuser/Desktop +chown -R liveuser.liveuser /home/liveuser/Desktop +chmod a+x /home/liveuser/Desktop/liveinst.desktop + +# But not trash and home +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -s -t bool /apps/nautilus/desktop/trash_icon_visible false >/dev/null +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -s -t bool /apps/nautilus/desktop/home_icon_visible false >/dev/null + +# Turn off PackageKit-command-not-found while uninstalled +sed -i -e 's/^SoftwareSourceSearch=true/SoftwareSourceSearch=false/' /etc/PackageKit/CommandNotFound.conf + +# Use the animated laughlin background by default +gconftool-2 --direct --config-source=xml:readwrite:/etc/gconf/gconf.xml.defaults -t str -s /desktop/gnome/background/picture_filename /usr/share/backgrounds/laughlin/default-tod/laughlin.xml + +EOF + +# Remove root password +passwd -d root > /dev/null + +# fstab from the install won't match anything. remove it and let dracut +# handle mounting. +cat /dev/null > /etc/fstab + +%end + +%packages +@admin-tools +@base +@base-x +@core +@dial-up +@fonts +@gnome-desktop +@graphical-internet +@hardware-support +@input-methods +#@office +#@printing +#@sound-and-video +anaconda +ibus-pinyin-db-android +isomd5sum +kernel +laughlin-backgrounds-animated-gnome +memtest86+ +nss-mdns +-ibus-pinyin-db-open-phrase +-smartmontools +grub-efi +grub2 + +%end
From: "Brian C. Lane" bcl@redhat.com
This runs anaconda directly, using the --image install feature. The host system should be the same release as the target system in order to avoid unexpected problems. --- src/sbin/livemedia-creator | 128 ++++++++++++++++++++++++++++++++------------ 1 files changed, 94 insertions(+), 34 deletions(-)
diff --git a/src/sbin/livemedia-creator b/src/sbin/livemedia-creator index e880f36..b141197 100755 --- a/src/sbin/livemedia-creator +++ b/src/sbin/livemedia-creator @@ -2,7 +2,7 @@ # # Live Media Creator # -# Copyright (C) 2009 Red Hat, Inc. +# Copyright (C) 2011 Red Hat, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -46,7 +46,7 @@ from pykickstart.version import makeVersion from pylorax.base import DataHolder from pylorax.treebuilder import TreeBuilder, RuntimeBuilder from pylorax.sysutils import joinpaths, remove, linktree -from pylorax.imgutils import PartitionMount +from pylorax.imgutils import PartitionMount, mksparse from pylorax.executils import execWithRedirect, execWithCapture
@@ -106,11 +106,14 @@ class LogRequestHandler(SocketServer.BaseRequestHandler): """ Check a line to see if it contains an error indicating install failure """ - if line.find("Traceback (") > -1 \ - or line.find("Out of memory:") > -1 \ - or line.find("Call Trace:") > -1 \ - or line.find("insufficient disk space:") > -1: - self.server.log_error = True + simple_tests = [ "Traceback (", + "Out of memory:", + "Call Trace:", + "insufficient disk space:" ] + for t in simple_tests: + if line.find( t ) > -1: + self.server.log_error = True + return
class LogServer(SocketServer.TCPServer): @@ -343,6 +346,22 @@ class VirtualInstall( object ): subprocess.call(["virsh","undefine",self.virt_name])
+def anaconda_install( disk_img, disk_size, kickstart, repo, args ): + """ + + """ + # Create the sparse image + mksparse( disk_img, disk_size * 1024**3 ) + + cmd = [ "anaconda", "--image", disk_img, "--kickstart", kickstart, + "--script", "--repo", repo_url ] + cmd += args + + log.debug( cmd ) + + return execWithRedirect( cmd[0], cmd[1:] ) + + def get_kernels( boot_dir ): """ Examine the vmlinuz-* versions and return a list of them @@ -434,7 +453,7 @@ if __name__ == '__main__': parser = argparse.ArgumentParser( description="Create Live Install Media", fromfile_prefix_chars="@" )
- # These two are mutually exclusive, one is required + # These are mutually exclusive, one is required action = parser.add_mutually_exclusive_group( required=True ) action.add_argument( "--make-iso", action="store_true", help="Build a live iso" ) @@ -445,10 +464,9 @@ if __name__ == '__main__': action.add_argument( "--make-ami", action="store_true", help="Build an ami image" )
- source = parser.add_mutually_exclusive_group( required=True ) - source.add_argument( "--iso", type=os.path.abspath, + parser.add_argument( "--iso", type=os.path.abspath, help="Anaconda installation .iso path to use for virt-install" ) - source.add_argument( "--disk-image", type=os.path.abspath, + parser.add_argument( "--disk-image", type=os.path.abspath, help="Path to disk image to use for creating final image" )
parser.add_argument( "--ks", action="append", type=os.path.abspath, @@ -459,6 +477,11 @@ if __name__ == '__main__': help="Keep raw disk image after .iso creation" ) parser.add_argument( "--no-virt", action="store_true", help="Use Anaconda's image install instead of virt-install" ) + parser.add_argument( "--proxy", + help="proxy URL to use for the install" ) + parser.add_argument( "--anaconda-arg", action="append", dest="anaconda_args", + help="Additional argument to pass to anaconda (no-virt " + "mode). Pass once for each argument" )
parser.add_argument( "--logfile", default="./livemedia.log", type=os.path.abspath, @@ -551,10 +574,6 @@ if __name__ == '__main__': log.error( "The disk image {0} is missing.".format( opts.disk_image ) ) sys.exit( 1 )
- if opts.no_virt: - log.error( "--no-virt is not yet implemented." ) - sys.exit( 1 ) - if opts.make_appliance: log.error( "--make-appliance is not yet implemented." ) sys.exit( 1 ) @@ -563,14 +582,12 @@ if __name__ == '__main__': log.error( "--make-ami is not yet implemented." ) sys.exit( 1 )
- # Use virt-install to make the disk image - if opts.iso: - disk_img = tempfile.mktemp( prefix="disk", suffix=".img", dir=opts.tmp ) - install_log = os.path.abspath(os.path.dirname(opts.logfile))+"/virt-install.log" - - log.info( "disk_img = {0}".format(disk_img) ) - log.info( "install_log = {0}".format(install_log) ) + if not opts.no_virt and not opts.iso and not opts.disk_image: + log.error( "virt-install needs an install iso." ) + sys.exit( 1 )
+ # Make the disk image + if not opts.disk_image: # Parse the kickstart to get the partition sizes ks_version = makeVersion() ks = KickstartParser( ks_version, errorsAreFatal=False, missingIncludeIsFatal=False ) @@ -578,19 +595,62 @@ if __name__ == '__main__': disk_size = 1 + (sum( [p.size for p in ks.handler.partition.partitions] ) / 1024) log.info( "disk_size = {0}GB".format(disk_size) )
- iso_mount = IsoMountpoint( opts.iso ) - log_monitor = LogMonitor( install_log ) + if ks.handler.method.method != "url": + log.error( "Only url install method is currently supported. Please " + "fix your kickstart file." ) + sys.exit( 1 ) + repo_url = ks.handler.method.url
- virt = VirtualInstall( iso_mount, opts.ks, disk_img, disk_size, - opts.kernel_args, opts.ram, opts.vnc, - log_check = log_monitor.server.log_check, - virtio_host = log_monitor.host, - virtio_port = log_monitor.port ) - virt.destroy() - log_monitor.shutdown() - iso_mount.umount() + disk_img = tempfile.mktemp( prefix="disk", suffix=".img", dir=opts.tmp ) + install_log = os.path.abspath(os.path.dirname(opts.logfile))+"/virt-install.log"
- if log_monitor.server.log_check(): + log.info( "disk_img = {0}".format(disk_img) ) + log.info( "install_log = {0}".format(install_log) ) + + if opts.no_virt: + anaconda_args = [] + if opts.anaconda_args: + for arg in opts.anaconda_args: + anaconda_args += arg.split(" ", 1) + if opts.proxy: + anaconda_args += [ "--proxy", opts.proxy ] + + # Use anaconda's image install + install_error = anaconda_install( disk_img, disk_size, opts.ks[0], + repo_url, anaconda_args ) + + # Move the anaconda logs over to a log directory + log_dir = os.path.abspath(os.path.dirname(opts.logfile)) + log_anaconda = joinpaths( log_dir, "anaconda" ) + if not os.path.isdir( log_anaconda ): + os.mkdir( log_anaconda ) + for l in ["anaconda.log", "ifcfg.log", "program.log", "storage.log", + "yum.log"]: + if os.path.exists( "/tmp/"+l ): + shutil.copy2( "/tmp/"+l, log_anaconda ) + os.unlink( "/tmp/"+l ) + else: + iso_mount = IsoMountpoint( opts.iso ) + log_monitor = LogMonitor( install_log ) + + kernel_args = "" + if opts.kernel_args: + kernel_args += opts.kernel_args + if opts.proxy: + kernel_args += " proxy="+opts.proxy + + virt = VirtualInstall( iso_mount, opts.ks, disk_img, disk_size, + kernel_args, opts.ram, opts.vnc, + log_check = log_monitor.server.log_check, + virtio_host = log_monitor.host, + virtio_port = log_monitor.port ) + virt.destroy() + log_monitor.shutdown() + iso_mount.umount() + + install_error = log_monitor.server.log_check() + + if install_error: log.error( "Install failed" ) if not opts.keep_image: log.info( "Removing bad disk image" ) @@ -616,7 +676,7 @@ if __name__ == '__main__': log.info("SUMMARY") log.info("-------") log.info("Logs are in {0}".format(os.path.abspath(os.path.dirname(opts.logfile)))) - if opts.keep_image: + if opts.keep_image or opts.make_disk: log.info("Disk image is at {0}".format(disk_img)) else: log.info("Disk image erased")
From: "Brian C. Lane" bcl@redhat.com
--- lorax.spec | 14 ++++++++++++-- setup.py | 3 ++- 2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/lorax.spec b/lorax.spec index 0b2c036..46f1af1 100644 --- a/lorax.spec +++ b/lorax.spec @@ -1,7 +1,7 @@ %define debug_package %{nil}
Name: lorax -Version: 17.0 +Version: 17.1 Release: 1%{?dist} Summary: Tool for creating the anaconda install images
@@ -47,6 +47,10 @@ Requires: kernel-bootwrapper %description Lorax is a tool for creating the anaconda install images.
+It also includes livemedia-creator which is used to create bootable livemedia, +including live isos and disk images. It can use libvirtd for the install, or +Anaconda's image install feature. + %prep %setup -q
@@ -58,11 +62,12 @@ make DESTDIR=$RPM_BUILD_ROOT install
%files %defattr(-,root,root,-) -%doc COPYING AUTHORS +%doc COPYING AUTHORS README.livemedia-creator %{python_sitelib}/pylorax %{python_sitelib}/*.egg-info %{_sbindir}/lorax %{_sbindir}/mkefiboot +%{_sbindir}/livemedia-creator %dir %{_sysconfdir}/lorax %config(noreplace) %{_sysconfdir}/lorax/lorax.conf %dir %{_datadir}/lorax @@ -70,6 +75,11 @@ make DESTDIR=$RPM_BUILD_ROOT install
%changelog +* Fri Dec 16 2011 Brian C. Lane bcl@redhat.com 17.1-1 +- Add livemedia-creator and sub-packages with proper deps for virt and non-virt + operation. +- Adjust a couple lorax treebuilder classes to make them callable + * Mon Oct 21 2011 Will Woods wwoods@redhat.com 17.0-1 - Merges the 'treebuilder' branch of lorax - images are split into two parts again (initrd.img, LiveOS/squashfs.img) diff --git a/setup.py b/setup.py index 8eadf8b..c4141f1 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,8 @@ for root, dnames, fnames in os.walk("share"): [os.path.join(root, fname)]))
# executable -data_files.append(("/usr/sbin", ["src/sbin/lorax", "src/sbin/mkefiboot"])) +data_files.append(("/usr/sbin", ["src/sbin/lorax", "src/sbin/mkefiboot", + "src/sbin/livemedia-creator"]))
# get the version sys.path.insert(0, "src")
On Wed, Nov 16, 2011 at 05:56:01PM -0800, Brian C. Lane wrote:
From: "Brian C. Lane" bcl@redhat.com
The wait is over. I think this stuff is now in good enough shape for others to start using it and providing feedback. The README.livemedia-creator file attempts to document things, or failing that, read the source :)
The idea behind this is to integrate all live media creation (isos, disk images, ami images, appliances, etc.) into lorax and anaconda. In its current state it will create bootable iso's which can be installed if you set LIVE_BLOCK=/dev/loop1 before running liveinst. See the README for more info and for future plans.
This all looks totally reasonable. I like the direction we're heading here - unifying where we're building our images and such - but we still have a good deal of cleanup/polish work to do. Stuff like:
- moving config files and templates into their own directory structure - using less kickstart %pre/%post magic - making the standalone 'lorax' binary work again - cleaning up logging a bit
But as long as we're all in agreement that there's work to be done - push it to master and let's get to hackin'!
-w
On Wed, Nov 16, 2011 at 8:56 PM, Brian C. Lane bcl@redhat.com wrote:
The wait is over. I think this stuff is now in good enough shape for others to start using it and providing feedback. The README.livemedia-creator file attempts to document things, or failing that, read the source :)
Sorry for not seeing this until Will's message today, I just realized I'm replying to stale mail - oh well :)
We have desires to use this at $DAYJOB for doing many things, and I'd like to know if it's in good enough shape to even POC right now.
First, we want to do as the name suggests, and create a RHEL6 livemedia distro. This is quite obviously unsupported, but the need (for various use cases) is real - and in reality, if it blows up. it's a live distro, so it doesn't matter :). What I'm specifically wondering is if this requires specific hooks into anaconda, or if it will work with the anaconda in RHEL6 (we'd probably be looking at 6.2 at this point). If it won't work today, what are the chances of getting the plumbing into 6.3 (or 6.4 for that matter) or is it too invasive? From reading the README.livemedia-creator, it doesn't look like anything special is required of anaconda - if I'm reading this correctly, it boots whatever anaconda you give it in --iso and runs it in KVM.
We have a few other use cases relating to internal cloud, but I'd rather not discuss them on a public list :)
But we'll certainly be kicking the tires on the livemedia part pretty quick - it looks awesome!
On Tue, Dec 20, 2011 at 09:31:09PM -0500, Jon Stanley wrote:
On Wed, Nov 16, 2011 at 8:56 PM, Brian C. Lane bcl@redhat.com wrote:
The wait is over. I think this stuff is now in good enough shape for others to start using it and providing feedback. The README.livemedia-creator file attempts to document things, or failing that, read the source :)
Sorry for not seeing this until Will's message today, I just realized I'm replying to stale mail - oh well :)
We have desires to use this at $DAYJOB for doing many things, and I'd like to know if it's in good enough shape to even POC right now.
It should be. I just did the first lorax build to include it (17.1-1) take a look at the README.livemedia-creator file for instructions.
First, we want to do as the name suggests, and create a RHEL6 livemedia distro. This is quite obviously unsupported, but the need (for various use cases) is real - and in reality, if it blows up. it's a live distro, so it doesn't matter :). What I'm specifically wondering is if this requires specific hooks into anaconda, or if it will work with the anaconda in RHEL6 (we'd probably be looking at 6.2 at this point). If it won't work today, what are the chances of getting the plumbing into 6.3 (or 6.4 for that matter) or is it too invasive? From reading the README.livemedia-creator, it doesn't look like anything special is required of anaconda - if I'm reading this correctly, it boots whatever anaconda you give it in --iso and runs it in KVM.
We have a few other use cases relating to internal cloud, but I'd rather not discuss them on a public list :)
I haven't tried it with RHEL6 yet. It will probably work, it depends on a couple of features -- the ability to inject the kickstart file into the initrd and on the logging system code that works with a virtual serial port. The virtio logging was added after rhel6 so that will cause problems with detecting errors during install.
Other than that, it is a straight kickstart install. lorax then takes over and converts the disk image to something you can boot an iso from.
But we'll certainly be kicking the tires on the livemedia part pretty quick - it looks awesome!
Thanks! I look forward to getting feedback from you.
anaconda-devel@lists.fedoraproject.org