Skip to content

Instantly share code, notes, and snippets.

@smoser
Last active January 10, 2019 16:07
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save smoser/49444542158f2e5f88f1 to your computer and use it in GitHub Desktop.
Save smoser/49444542158f2e5f88f1 to your computer and use it in GitHub Desktop.
lxc tools. Random tools around lxc.
#!/bin/bash
VERBOSITY=0
TEMP_D=""
UC_PREP="/usr/share/lxc/hooks/ubuntu-cloud-prep"
error() { echo "$@" 1>&2; }
fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
Usage() {
cat <<EOF
Usage: ${0##*/} [ options ] input-tar output-tarball
convert cloud-image-root.tar.gz into lxd compatible format,
and stuff nocloud seed in on the way.
options:
-h|--help this help
-v|--verbose
--metadata file to include as metadata.yaml
-u|--userdata U user-data for seed.
-S|--auth-key P pubkey to insert
-C|--cloud C do not seed instance
EOF
}
bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; }
cleanup() {
[ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
}
debug() {
local level=${1}; shift;
[ "${level}" -gt "${VERBOSITY}" ] && return
error "${@}"
}
default_mdyaml() {
cat <<EOF
architecture: "$(uname -m)"
creation_date: $(date "+%s")
name: "$1"
properties:
description: Ubuntu 14.04 LTS Intel 64bit
os: Ubuntu
release: [trusty, '14.04']
EOF
}
main() {
local short_opts="Chu:Sv"
local long_opts="auth-key:,cloud,help,metadata:,userdata:,verbose"
local getopt_out=""
local pt=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage; return; }
local cur="" next="" input="" output="" authkey="" vflags=""
pt=( )
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
-h|--help) Usage ; exit 0;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1))
vflags="${vflags}v";;
--metadata) mdyaml="$next";;
-u|--userdata) pt[${#pt[@]}]="--userdata=$next"; shift;;
-S|--auth-key) pt[${#pt[@]}]="--auth-key=$next"; shift;;
-C|--cloud) pt[${#pt[@]}]="--cloud=$next"; shift;;
--) shift; break;;
esac
shift;
done
[ -z "$vflags" ] || vflags="-$vflags"
[ $# -ne 0 ] || { bad_Usage "must provide arguments"; return; }
[ $# -eq 2 ] ||
{ bad_Usage "confused by args. got $# expected 2 [$*]"; return; }
input=$1
output=$2
TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
fail "failed to make tempdir"
trap cleanup EXIT
[ "$(id -u)" = "0" ] || { error "you're not root"; return 1; }
command -v "$UC_PREP" >/dev/null 2>&1 ||
{ error "$UC_PREP not available"; return 1; }
[ "$input" = "-" -o -f "$input" ] ||
{ error "$input: not a file or -"; return 1; }
[ -n "$output" ] || { error "$output: not a file or -"; return 1; }
[ -z "$mdyaml" -o -f "$mdyaml" ] ||
{ error "$mdyaml: not a file"; return 1; }
local extract create ucprep cprog="" t
mkdir -p "${TEMP_D}/rootfs" || { error "failed to make rootfs"; return 1; }
extract=(
tar -C "${TEMP_D}/rootfs"
--xattrs "--xattrs-include=*"
--anchored "--exclude=dev/*"
--numeric-owner -Sxpf "$input" )
for t in ${COMPRESS_PROGRAM:-pixz pxz xz gzip}; do
command -v $cprog >/dev/null 2>&1 && cprog=$t && break
done
[ -n "$cprog" ] || { error "did not find a compress program"; return 1; }
create=(
tar -C "${TEMP_D}/"
"--xattrs" "--xattrs-include=*" "--use-compress-program=$cprog"
"-cpf" "$output" metadata.yaml rootfs )
ucprep=(
"${UC_PREP}" $vflags "${pt[@]}" "${TEMP_D}/rootfs" )
if [ -n "$metadata" ]; then
cp "$metadata" "${TEMP_D}/metadata.yaml" ||
{ error "failed cp '$metadata' metadata.yaml"; return 1; }
debug 1 "copied metadata.yaml from '$metadata'"
else
local tname=""
tname=${input%.gz};
tname=${tname%.tar};
[ "$input" = "-" ] && tname="unknown name"
default_mdyaml "$tname" > "${TEMP_D}/metadata.yaml" ||
{ error "failed write metadata.yaml"; return 1; }
debug 1 "wrote questionable metadata.yaml file"
fi
debug 1 "extracting tar to tempdir"
debug 2 "cmd: ${extract[*]}"
"${extract[@]}" || { error "failed extraction"; return 1; }
debug 1 "running ucprep: ${ucprep[*]}"
"${ucprep[@]}" ||
{ error "failed to run ${ucprep[*]}"; return 1; }
debug 1 "writing tar to $out: ${create[*]}"
debug 2 "cmd: ${create[*]}"
"${create[@]}" || fail "failed writing tar to $out"
debug 1 "finished [${SECONDS}]"
}
main "$@"
# vi: ts=4 noexpandtab
#!/bin/sh
verbosity=0
if [ "$1" = "-v" ]; then
verbosity=1
shift
fi
container="$1"
shift || exit
[ $# -eq 0 ] && set -- /bin/bash
noinit="/sbin/no-init"
cr="
"
go() {
[ $verbosity -gt 0 ] && echo "$@" 1>&2
"$@";
}
status=$(lxc info "$container" | awk '$1 == "Status:" { print $2; exit(0); }')
oldconfig=""
reset=false
if [ "$status" = "Stopped" ]; then
reset=true
oldconfig=$(lxc config get "$container" raw.lxc)
go lxc config set "$container" raw.lxc "lxc.init_cmd=$noinit${cr}lxc.environment=KERNEL_CMDLINE=foo bar${cr}"
#lxc config set "$container" raw.lxc "lxc.init_cmd=$noinit${cr}"
go lxc file push --uid=0 --gid=0 --mode=0700 - "$container$noinit" <<"END"
#!/bin/sh
set -ef
FIFO="/tmp/.init-fifo"
UMOUNTS=""
cleanup() {
local f p=""
for f in "$FIFO" "$0"; do
[ -e "$f" ] || continue
p="${p:+${p} } $f"
done
[ -z "$p" ] || rm -f $p
local um
for um in $UMOUNTS; do
umount $um
done
}
cleanexit() { exit 0; }
trap "cleanexit" QUIT PWR;
trap cleanup EXIT
mount -t tmpfs /tmp /tmp && UMOUNTS="/tmp $UMOUNTS"
#mount -t proc /proc /proc && UMOUNTS="/proc $UMOUNTS"
#mount -t sysfs /sys /sys && UMOUNTS="/sys $UMOUNTS"
#mount -t devtmpfs /dev /dev && UMOUNTS="/dev $UMOUNTS"
set > /tmp/pid1-env
mkfifo "$FIFO"
exec 3<>"$FIFO"
rm -f "$FIFO" "$0"
read vname <&3
END
go lxc start "$container"
fi
lxc exec "$container" "$@"
ret=$?
if $reset; then
if [ -n "$oldconfig" ]; then
go lxc config set "$container" raw.lxc "$oldconfig"
else
go lxc config unset "$container" raw.lxc
fi
go lxc stop "$container"
fi
exit $ret
#!/bin/bash
Usage() {
cat <<EOF
Usage: ${0##*/} container [program [arguments]]
run 'program' in container with provided arguments.
container is started and stopped if not running.
The key value here is that stdin and stdout are left attached,
as they would be in 'chroot'.
Example:
$ sudo ./run-in my-container ps -ax
PID TTY STAT TIME COMMAND
1 ? S 0:00 sh -c trap "exit 0" QUIT; read s
3 ? R+ 0:00 ps -ax
$ sudo ${0##*/} my-container env LANG=C \\
'sh -c cat > my.deb; dpkg -i my.deb; r=\$?; rm my.deb; exit $r;'
EOF
}
STOP=""
error() { echo "$@" 1>&2; }
fail() { [ $# -eq 0 ] || error "$@"; exit 3; }
cleanup() {
if [ -n "$STOP" ]; then
lxc-stop "--name=$STOP"
fi
}
trap cleanup EXIT
[ "$1" = "-h" -o "$1" = "--help" ] && { Usage; exit 0; }
if [ $# -eq 1 ]; then
set -- "$1" "${SHELL:-/bin/sh}"
fi
[ $# -ge 2 ] || { Usage 1>&2; fail "expected 2 or more, got $#."; }
name=$1
shift
out=$(lxc-ls -1 --fancy --fancy-format='name,state' "^$name$" 2>/dev/null) ||
fail "failed to find container $out"
state=$(printf "%s" "$out" | awk '$1 == n { print $2 }' "n=$name")
[ -n "$state" ] || fail "no container found named '$name'"
if [ "$state" == "STOPPED" ]; then
error "starting a blocker"
lxc-start "--name=$name" --daemon -- \
sh -c 'trap "exit 0" QUIT PWR; read s' </dev/null
STOP="$name"
started=true
elif [ "$state" == "RUNNING" ]; then
error "already running"
STOP=""
started=false
else
fail "state '$state' not known. expecting STOPPED or RUNNING"
fi
lxc-wait "--name=$name" --state=RUNNING --timeout=30 </dev/null ||
fail "$name did not enter running state";
lxc-attach "--name=$name" -- "$@"
ret=$?
if $started; then
lxc-stop "--name=$name" && STOP="" ||
error "WARNING: failed to stop '$name'"
fi
exit $ret
#!/usr/bin/python3
import re
import json
import subprocess
import sys
args = sys.argv[1:]
force = False
dry_run = True
if '-f' in args or '--force' in args:
args = [f for f in args if f not in ('-f', '--force')]
force = True
if '-n' in args or '--dry-run' in args:
args = [f for f in args if f not in ('-n', '--dry-run')]
dry_run = True
try:
out = subprocess.check_output(["lxc", "list", "--format=json"])
except subprocess.CalledProcessError as e:
sys.exit(e.returncode)
data = json.loads(out.decode('utf-8'))
names = [f["name"] for f in data]
if not names:
sys.stderr.write("Nothing to delete\n")
sys.exit(0)
if force:
sys.stderr.write(' '.join(names) + '\n')
r = input("Delete these [y/N]?")
if r not in ("y", "Y"):
sys.stderr.write("Aborted.\n")
sys.exit(0)
to_del = names
if len(args):
to_del = []
for n in names:
for m in args:
if re.match(m, n):
to_del.append(n)
break
cmd = ["lxc", "delete", "--force"] + to_del
sys.stderr.write(' '.join(cmd) + "\n")
if dry_run:
sys.exit(0)
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError as e:
sys.stderr.write("Failed [%s]" % e.returncode)
sys.exit(e.returncode)
#!/usr/bin/python3
import argparse
import copy
import atexit
import json
import logging
import os
import shlex
import subprocess
import sys
import yaml
RC_ANY = "*"
FMT_YAML = "yaml"
LOG = logging.getLogger("pstart")
PSUEDO_INIT_PATH = "/sbin/psuedo-init"
CLEANUPS = {}
SHELL_DUMP = False
class SubpError(IOError):
def __init__(self, cmd=None, rc=None, stdout=None, stderr=None, desc=None,
data=None):
if cmd is None:
cmd = ["NO_COMMAND"]
if desc is None:
desc = "Execution of command failed."
self.dsc = desc
self.cmd = cmd if cmd else ["NO_COMMAND"]
self.rc = rc
self.stderr = stderr
self.stdout = stdout
msg = (
desc + "\n" +
" cmd: %s\n" % ' '.join([shlex.quote(f) for f in cmd]) +
" rc: %s\n" % rc +
self._fmt("stdout", stdout) +
self._fmt("stderr", stderr) +
self._fmt("data", data))
IOError.__init__(self, msg)
def _fmt(self, name, data, pre=b' '):
cr = b'\n'
if not data:
return name + ": None\n"
if data.endswith(cr):
data = data[:-1]
return (
name + ":\n" +
(pre + data.replace(cr, cr + pre) + cr).decode(errors='ignore'))
def lxc(args, ofmt=None, rcs=None, fmsg=None, data=None):
try:
out, err, rc = subp(['lxc'] + args, rcs=rcs, data=data)
except SubpError as e:
if fmsg is None:
raise e
fail(str(e) + fmsg + "\n")
if ofmt == FMT_YAML:
out = yaml.safe_load(out.decode())
return out, err, rc
def dump_data(data):
return json.dumps(data, indent=1, sort_keys=True,
separators=(',', ': '))
def add_cleanup(name, func, *args, **kwargs):
global CLEANUPS
CLEANUPS[name] = (func, args, kwargs)
def rm_cleanup(name):
global CLEANUPS
del CLEANUPS[name]
def cleanups():
global CLEANUPS
cleanups = CLEANUPS
for name, (func, args, kwargs) in cleanups.items():
LOG.debug("Calling cleanup %s" % name)
func(*args, **kwargs)
def shell_quote(cmd):
if isinstance(cmd, (tuple, list)):
return ' '.join([shlex.quote(x) for x in cmd])
return shlex.quote(cmd)
def print_cmd(cmd, data=None, fp=sys.stderr):
global SHELL_DUMP
if not SHELL_DUMP:
return
msg = 'pstart% ' + shell_quote(cmd)
if data:
msg += ' <<"PSTART_EOF"\n' + data.decode("utf-8") + "\nPSTART_EOF"
fp.write(msg + '\n')
def print_cmd_output(out, err, fp=sys.stderr):
global SHELL_DUMP
if not SHELL_DUMP:
return
for ref, data in (("<stdout>", out), ("<stderr>", err)):
if not data:
continue
fp.write("%s\n%s\n" % (ref, data.decode("utf-8", errors="replace")))
def subp(args, rcs=None, capture=True, data=None):
if rcs is None:
rcs = [0]
devnull_fp = None
try:
stdin = None
stdout = None
stderr = None
if capture:
stdout = subprocess.PIPE
stderr = subprocess.PIPE
if data is None:
# using devnull assures any reads get null, rather
# than possibly waiting on input.
devnull_fp = open(os.devnull)
stdin = devnull_fp
else:
stdin = subprocess.PIPE
print_cmd(args, data)
sp = subprocess.Popen(args, stdout=stdout,
stderr=stderr, stdin=stdin)
(out, err) = sp.communicate(data)
if not out:
out = b''
if not err:
err = b''
finally:
if devnull_fp:
devnull_fp.close()
print_cmd_output(out, err)
rc = sp.returncode
if rcs != RC_ANY and rc not in rcs:
raise SubpError(cmd=args, rc=rc, stdout=out, stderr=err, data=data)
return (out, err, rc)
def fail(msg):
LOG.error(msg)
sys.exit(1)
def create_profile(remote, pname):
rname = ''.join((remote, pname))
gw_cidr = "10.3.23.1/24"
netcfg, err, rc = lxc(['network', 'show', rname],
ofmt=FMT_YAML, rcs=RC_ANY)
if rc == 0:
LOG.info("re-using existing network %s", rname)
LOG.debug("%s had config: %s", rname, netcfg)
gw_cidr = netcfg['config'].get('ipv4.address')
if not gw_cidr:
fail("No 'ipv4.address' in network config %s" % rname)
else:
out, err, rc = lxc(
['network', 'create', rname, "ipv4.address=%s" % gw_cidr,
"ipv4.nat=true"],
fmsg="Failed to create network '%s'" % rname)
netcfg, err, rc = lxc(
['network', 'show', rname],
fmsg="Failed show network after create: %s" % rname)
LOG.info("Created network '%s' with addr '%s'", rname, gw_cidr)
profcfg, err, rc = lxc(['profile', 'show', rname],
ofmt=FMT_YAML, rcs=RC_ANY)
if rc == 0:
LOG.info("re-using existing profile %s", rname)
LOG.debug("%s had config: %s", rname, profcfg)
else:
init_cmd = ' '.join([PSUEDO_INIT_PATH, "--network=%s" % gw_cidr])
profcfg = {
"config": {"raw.lxc": "lxc.init.cmd=%s" % init_cmd},
"description": "Profile for psuedo-start.",
"devices": {"eth0": {"nictype": "bridged", "parent": pname,
"type": "nic"}}}
lxc(['profile', 'create', rname],
fmsg="Failed to create profile %s" % rname)
lxc(['profile', 'edit', rname], data=yaml.dump(profcfg).encode(),
fmsg="Failed to set config for profile '%s'" % rname)
LOG.info("Created profile '%s'", rname)
return profcfg, netcfg
def get_psuedo_init_blob():
fpath = os.path.join(
os.path.dirname(os.path.realpath(__file__)), 'psuedo-init')
if not os.path.isfile(fpath):
fail("Expected to find psuedo-init at %s but it does not exist" %
fpath)
with open(fpath, "rb") as fp:
return fp.read()
def do_start(remote, name, prof_name, ctn_cfg, cmd=None):
init_blob = get_psuedo_init_blob()
rcontainer = remote + ":" + name if remote else name
prof_cfg, net_cfg = create_profile(remote, prof_name)
new_cfg = copy.deepcopy(ctn_cfg)
new_cfg['profiles'] = (
[p for p in new_cfg['profiles'] if p != prof_name] + [prof_name])
lxc(['config', 'edit', rcontainer], data=yaml.dump(new_cfg).encode())
lxc(['file', 'push', '--mode=0755', '-',
rcontainer + PSUEDO_INIT_PATH],
data=init_blob,
fmsg=("Failed to push psuedo-init to %s%s" %
(rcontainer, PSUEDO_INIT_PATH)))
lxc(['start', rcontainer],
fmsg="Failed to start container '%s'" % rcontainer)
LOG.debug("waiting for container via lxc exec %s -- %s wait",
rcontainer, PSUEDO_INIT_PATH)
lxc(['exec', rcontainer, '--', PSUEDO_INIT_PATH, 'wait'])
if not cmd:
return
lcmd = ['lxc', 'exec', rcontainer, '--'] + cmd
print_cmd(lcmd)
ret = subprocess.call(lcmd)
do_stop(remote, name, prof_name, new_cfg)
sys.exit(ret)
def do_clean(remote, name, prof_name, ctn_cfg):
rcontainer = remote + ":" + name if remote else name
new_profiles = [p for p in ctn_cfg['profiles'] if p != prof_name]
if ctn_cfg['profiles'] == new_profiles:
LOG.debug("No change needed to %s (%s not in profiles)",
rcontainer, prof_name)
return
ctn_cfg['profiles'] = new_profiles
LOG.debug("Removing '%s' from profiles for container '%s'",
prof_name, rcontainer)
lxc(['config', 'edit', rcontainer], data=yaml.dump(ctn_cfg).encode(),
fmsg="Failed to restore config on '%s'" % rcontainer)
def do_stop(remote, name, prof_name, ctn_cfg):
rcontainer = remote + ":" + name if remote else name
LOG.debug("Stopping container %s.", rcontainer)
lxc(['stop', rcontainer],
fmsg="Failed to stop container %s" % rcontainer)
do_clean(remote, name, prof_name, ctn_cfg)
def main():
parser = argparse.ArgumentParser(prog="lxc-pstart")
mgroup = parser.add_mutually_exclusive_group(required=False)
for m in ('stop', 'start', 'clean'):
mgroup.add_argument('--' + m, action='store_const', const=m,
dest='mode')
parser.add_argument('-b', '--bname', action='store', default='pstart0',
help='The name to use for items created.')
parser.add_argument('-D', '--dump-commands', action='store_true',
default=False,
help='Dump all lxc commands to stderr')
parser.add_argument('-v', '--verbose', action='count', default=0)
parser.add_argument('container', metavar='<remote>:container',
action='store', default=None)
parser.add_argument('cmd', metavar='command', nargs='*')
cmdargs = parser.parse_args()
if cmdargs.mode is None:
cmdargs.mode = 'start'
if cmdargs.cmd and cmdargs.mode != 'start':
sys.stderr.write("command can only be given to 'start'\n")
sys.exit(1)
level = min(cmdargs.verbose, 2)
logging.basicConfig(
stream=sys.stderr,
level=(logging.ERROR, logging.INFO, logging.DEBUG)[level])
if cmdargs.dump_commands:
global SHELL_DUMP
SHELL_DUMP = True
atexit.register(cleanups)
prof_name = cmdargs.bname
if ':' in cmdargs.container:
remote, _, container = cmdargs.container.partition(":")
else:
remote = ""
container = cmdargs.container
rcontainer = remote + container
ctn_cfg, _, _ = lxc(
['config', 'show', rcontainer], ofmt=FMT_YAML,
fmsg="Failed to get config for '%s'. Does it exist?" % rcontainer)
if cmdargs.mode == "start":
do_start(remote, container, prof_name, ctn_cfg, cmdargs.cmd)
elif cmdargs.mode == "clean":
do_clean(remote, container, prof_name, ctn_cfg)
elif cmdargs.mode == "stop":
do_stop(remote, container, prof_name, ctn_cfg)
else:
sys.stderr.write("Unknown mode: %s" % cmdargs.mode)
sys.exit(1)
sys.exit(0)
if __name__ == '__main__':
main()
# vi: ts=4 expandtab syntax=python

lxc-pstart

Psuedo start a container.

This utility takes a container that is stopped and starts it in a way that will not have side affects of a "normal" /sbin/init.

It sets up simple networking so that when you run commands in this psuedo init environment you can use the network.

How

  • create a profile and a network. The profile and network by default are pstart0. The profile puts a device on the network, and changes the init command

  • modify the container's config to add the new profile

  • push a new init command into the container (psuedo-init)

  • start the container

  • psuedo-init brings up a network connection with 'ip' picking a somewhat random address on its /24 network.

  • put the config back to old profile

One command Example

Just calling 'pstart' willi do all the steps in 'how' above, and then execute your command and then tear everything down.

# create a container
$ lxc init ubuntu-daily:xenial myx1
creating myx1

$ lxc-pstart myx1 -- sh -xc 'ps axw; ip a'
+ ps axw
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 /bin/sh /sbin/psuedo-init --network=10.3.23.1/24
   33 ?        Ss+    0:00 sh -xc ps axw; ip a
   34 ?        R+     0:00 ps axw
+ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> 
   ....
113: eth0@if114: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 
    link/ether 00:16:3e:91:05:38 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.3.23.56/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::216:3eff:fe91:538/64 scope link tentative 
       valid_lft forever preferred_lft forever

Start / Stop Example

# create a container
$ lxc init ubuntu-daily:xenial myx1
creating myx1

# now start it with lxc-pstart
# The first time it is run, it will create its network and profile.
$ lxc-pstart -v myx1
INFO:pstart:Created network 'pstart0' with addr '10.3.23.1/24'
INFO:pstart:Created profile 'pstart0'

# now the container is started.
$ lxc list myx1
+------+---------+-------------------+----------------------------------------------+------------+-----------+
| NAME |  STATE  |       IPV4        |                     IPV6                     |    TYPE    | SNAPSHOTS |
+------+---------+-------------------+----------------------------------------------+------------+-----------+
| myx1 | RUNNING | 10.3.23.85 (eth0) | fd42:1138:c9c2:44f:216:3eff:fe55:8555 (eth0) | PERSISTENT | 0         |
+------+---------+-------------------+----------------------------------------------+------------+-----------+

$ lxc exec myx1 host ubuntu.com
The configuration file contains legacy configuration keys.
Please update your configuration file!
ubuntu.com has address 91.189.94.40
ubuntu.com mail is handled by 10 mx.canonical.com.

# Now you need to cleanup
$ lxc-pstart --stop myx1
#!/bin/bash
VERBOSITY=0
error() { echo "$@" 1>&2; }
fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
Usage() {
cat <<EOF
Usage: ${0##*/} [user [group]]
set up lxc with user namespaces for user
options:
-v increase verbosity
EOF
}
bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; }
cleanup() {
[ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
}
debug() {
local level=${1}; shift;
[ "${level}" -gt "${VERBOSITY}" ] && return
error "${@}"
}
asuser() {
local myname="$1" target="$2"
shift 2
if [ "$myname" != "$target" ]; then
sudo -Hu "$target" -- "$@"
else
"$@"
fi
}
asroot() {
local myname="$1"
shift
if [ "$myname" != "root" ]; then
sudo -- "$@"
else
"$@"
fi
}
write_conf() {
local bridge=$1 hwaddr=$2 uid_map=$3 gid_map=$4
cat <<END_CONF
lxc.network.type = veth
lxc.network.link = $bridge
lxc.network.flags = up
lxc.network.hwaddr = $hwaddr
lxc.id_map = u 0 $uid_map
lxc.id_map = g 0 $gid_map
END_CONF
}
main() {
local short_opts="hv"
local long_opts="help,verbose"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage; return; }
local uname="" gname=""
local cur="" next="" bridge="lxcbr0" hwaddr="00:16:3e:xx:xx:xx"
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
-h|--help) Usage ; exit 0;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
--) shift; break;;
esac
shift;
done
[ $# -lt 3 ] ||
{ bad_Usage "confused by $# args. expected < 2"; return 1; }
local uname="$1" gname="$2"
local myname=""
if [ $# -eq 0 ]; then
myname=$(id --user --name) ||
{ error "failed to run 'id --user --name'"; return 1; }
if [ "$myname" = "root" ]; then
bad_Usage "root user must provide a user (no default)"
return
fi
uname=${myname}
else
# verify input
id --user --name "$uname" >/dev/null ||
{ error "'$uname': not a valid user"; return 1; }
myname=$(id --user --name) ||
{ error "failed: 'id --user --name'"; return 1; }
fi
if [ -z "$gname" ]; then
# get primary group
gname=$(id --group --name "$uname") ||
{ error "failed: id --group --name '$uname'"; return 1; }
fi
local uid_map="" gid_map=""
uid_map=$(awk -F: '$1 == n { print $2, $3 }' "n=$uname" /etc/subuid) ||
{ error "failed to read /etc/subuid"; return 1; }
[ -n "$uid_map" ] ||
{ error "user '$uname' not in /etc/subuid"; return 1; }
gid_map=$(awk -F: '$1 == n { print $2, $3 }' "n=$gname" /etc/subgid) ||
{ error "failed to read /etc/subgid"; return 1; }
[ -n "$gid_map" ] ||
{ error "group '$gname' not in /etc/subgid"; return 1; }
home=$(eval echo "~$uname")
if [ "$myname" = "$uname" -a -n "$HOME" ]; then
# respect HOME if user is operating on themselves.
home="$HOME"
fi
local cfg="$home/.config/lxc/default.conf"
debug 1 "configuring user=$uname group=$gname cfg=$cfg"
asuser "$myname" "$uname" mkdir -p "${cfg%/*}" ||
{ error "failed to create ${cfg%/*}"; return 1; }
write_conf "$bridge" "$hwaddr" "$uid_map" "$gid_map" |
asuser "$myname" "$uname" sh -c 'cat > "$1"' -- "$cfg" ||
{ error "failed to write to $cfg"; return 1; }
local unet="/etc/lxc/lxc-usernet" found=""
found=$(awk '$1 == n { print $1 }' "n=$uname" "$unet")
if [ -z "$found" ]; then
echo "$uname veth $bridge 10" |
asroot "$myname" tee -a "$unet" >/dev/null ||
{ error "failed write to $unet"; return 1; }
debug 1 "added entry to $unet"
else
debug 1 "existing entry for '$uname' in $unet"
fi
return 0
}
main "$@"
# vi: ts=4 expandtab
#!/bin/bash
VERBOSITY=0
TEMP_D=""
DEFAULT_MAX="20"
error() { echo "$@" 1>&2; }
fail() { local r=$?; [ $r -eq 0 ] && r=1; failrc "$r" "$@"; }
failrc() { local r=$1; shift; [ $# -eq 0 ] || error "$@"; exit $r; }
debug() {
local level=${1}; shift;
[ "${level}" -gt "${VERBOSITY}" ] && return
error "${@}"
}
Usage() {
cat <<EOF
Usage: ${0##*/} [ options ] container [container [container ... ]]
Wait for container(s) to finish booting.
options:
-m | --max SECONDS wait at most N seconds [default $DEFAULT_MAX]
EOF
}
bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; }
main() {
local short_opts="ho:v"
local long_opts="help,output:,verbose"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage; return; }
local cur="" next=""
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
-h|--help) Usage ; exit 0;;
-m|--max) max=$next; shift;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
--) shift; break;;
esac
shift;
done
[ $# -gt 0 ] || { bad_Usage "must provide container"; return; }
local fails="" name=""
for name in "$@"; do
lxc exec "$name" -- sh -s wait-inside \
"$name" "$max" "$VERBOSITY" < "$0" ||
fails="${fails:+${fails} }$name"
done
if [ -n "$fails" ]; then
debug 2 "had failures: $fails"
return 1
else
debug 2 "ready."
return 0
fi
}
wait_inside() {
local name="$1" max="${2:-${DEF_MAX}}" debug=${3:-0}
local i=0;
if [ -e /usr/bin/cloud-init ]; then
local rfile="/run/cloud-init/result.json"
while [ ! -e "$rfile" ] && i=$(($i+1)); do
[ $i -ge $max ] && exit 1
[ "$debug" = "0" ] || echo -n .
sleep 1
done
[ "$debug" = "0" ] || echo "[$name done after $i]"
exit 0
else
debug 1 "$name no cloud-init, just sleeping."
sleep 10
exit 0
fi
}
if [ "$1" = "wait-inside" ]; then
shift
wait_inside "$@"
else
main "$@"
fi
# vi: ts=4 noexpandtab
#!/bin/sh
# This is 'psuedo-init' that is used by lxc-pstart.
set -f
VERBOSITY=1
LOG="/tmp/${0##*/}.log"
BK_SUF=".psi-bk"
error() { echo "$@" 1>&2; }
errorrc() { local r=$?; echo "$@" 1>&2; return $r; }
fail() { local r=$?; [ $r -eq 0 ] && r=1; failrc "$r" "$@"; }
failrc() { local r=$1; shift; [ $# -eq 0 ] || error "$@"; exit $r; }
Usage() {
cat <<EOF
Usage: psuedo-init [options] name
psuedo-init a false init that provides a pid 1 that will
allow 'lxc exec' without side affects of a normal /sbin/init
options:
-h | --help this message
EOF
}
bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; }
debug() {
local level=${1}; shift;
[ "${level}" -gt "${VERBOSITY}" ] && return
echo "$@" >> "$LOG"
error "$@"
}
cleanup() {
local f p="" um=""
ip link set $IFACE down
for f in $RESTORES; do
[ -e "$f${BK_SUF}" -o -L "$f${BK_SUF}" ] || continue
mv "$f${BK_SUF}" "$f"
done
[ ! -f "$FIFO" ] || rm -f "$FIFO"
[ ! -f "$0" ] || rm -f "$0"
for um in $UMOUNTS; do
umount $um
done
}
link_up_and_dad() {
local dev=$1 delay=${2:-0.1} attempts=${3:-60}
ip link set up dev "$dev" ||
{ error "$dev: failed to set link up"; return 1; }
local n=0
while :; do
n=$((n+1))
# note: busybox ip does not understand 'tentative' as input
# so we cannot just use the tentative flag and check for empty
out=$(ip -6 -o address show dev "$dev" scope link) || {
error "$dev: checking for link-local addresses failed";
return 1
}
case " $out " in
*\ dadfailed\ *)
error "$dev: ipv6 dad failed."
return 1;;
*\ tentative\ *) :;;
*) return 0;;
esac
[ $n -lt $attempts ] || {
error "$dev: time out waiting for permanent link-local address"
return 1;
}
sleep $delay
done
return 0
}
configure_net() {
local iface="$IFACE" netinfo="$1"
[ -n "$netinfo" ] || return 0
local gateway="" addr="" nameserver="" size=24
gateway="${netinfo%/*}"
nameserver=${gateway}
local network=${gateway%.*} # first 3 tokens
if [ -e /sys/class/net/$iface/address ]; then
# pick address based on mac address.. why not.
local mac="" last=""
read mac </sys/class/net/$iface/address
last=${mac##*:}
addr=$((0x$last))
if [ "$network.$addr" = "$gateway" -o "$addr" = "0" ]; then
addr=$(((addr+1)%256))
fi
addr="$network.$addr"
debug 1 "using $addr from $last"
else
addr="$network.2"
fi
if [ "$netinfo" != "$gateway" ]; then
size="${netinfo#*/}"
fi
backup /etc/resolv.conf
debug 1 ip addr add "$addr/$size" dev "$iface"
ip addr add "${addr}/$size" dev "$iface" ||
fail "Failed: ip link set $iface"
debug 1 ip link set $iface up
ip link set $iface up
#link_up_and_dad "$iface" || fail "Failed: ip link set $iface up"
debug 1 "ip route add default via \"$gateway\""
ip route add default via "$gateway"
debug 1 "add nameserver $nameserver to /etc/resolv.conf"
{
echo "nameserver $nameserver"
echo "search lxd"
} > /etc/resolv.conf
}
block_forever() {
# do nothing in a manner that allows signals to get through.
local fifo="$1" vname=""
mkfifo "$FIFO"
exec 3<>"$FIFO"
rm -f "$FIFO"
read vname <&3
}
backup() {
local f=""
for f in "$@"; do
[ -e "$f" -o -L "$f" ] || continue
mv "$f" "$f${BK_SUF}"
RESTORES="${RESTORES} $f"
done
}
wait_for_ready() {
local i=0
while [ $i -lt 1000 ]; do
[ -e /tmp/ready ] && exit 0
sleep .1
done
exit 1
}
main() {
local short_opts="h:v"
local long_opts="help,network:,verbose"
local getopt_out="" args="$*"
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage; return; }
local cur="" next="" network=""
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
-h|--help) Usage ; exit 0;;
-p|--network) network="$next";;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
--) shift; break;;
esac
shift;
done
FIFO="/tmp/.init-fifo" IFACE="eth0" RESTORES="" UMOUNTS=""
trap "exit 0" QUIT PWR;
trap cleanup EXIT
mount -t tmpfs /tmp /tmp || fail "Failed to mount tmpfs onto /tmp"
set -x
debug 1 "$0 up. args: $args"
UMOUNTS="/tmp $UMOUNTS"
debug 1 "mounted /tmp"
if [ $VERBOSITY -gt 1 ]; then
set 1>&2
fi
configure_net "$network"
touch /tmp/ready
block_forever "$FIFO"
}
if [ "$1" = "wait" ]; then
wait_for_ready
else
main "$@"
fi
# vi: ts=4 expandtab
#!/bin/bash
VERBOSITY=0
DEFAULT_WAIT_MAX="20"
OIFS="$IFS"
CR="
"
set -f
set -o pipefail
error() { echo "$@" 1>&2; }
rerror() { local r=$?; echo "$@" 1>&2; return $r; }
fail() { local r=$?; [ $r -eq 0 ] && r=1; failrc "$r" "$@"; }
failrc() { local r=$1; shift; [ $# -eq 0 ] || error "$@"; exit $r; }
debug() {
local level=${1}; shift;
[ "${level}" -gt "${VERBOSITY}" ] && return
error "${@}"
}
if command -v ubuntu-distro-info >/dev/null 2>&1; then
RELEASES=( $(ubuntu-distro-info --supported) precise )
else
RELEASES=( )
fi
declare -A ALIASES
ALIASES=(
[cent]="images:centos/7"
[cent6]="images:centos/6"
[cent7]="images:centos/7"
[suse]="images:opensuse/42.3"
[core]="images:ubuntu-core/16"
[sid]="images:debian/sid"
)
for i in "${RELEASES[@]}"; do
# bionic -> ubuntu-daily:bionic
ALIASES[$i]="ubuntu-daily:$i"
# 'b' -> ubuntu-daily:bionic
ALIASES[${i:0:1}]="ubuntu-daily:$i"
# 'minb' -> minimal-ubuntu-daily:bionic
ALIASES[min${i:0:1}]="minimal-ubuntu-daily:$i"
done
Usage() {
cat <<EOF
Usage: ${0##*/} [ options ] ...
wrapper around lxc client for some nicer things.
updates commands:
* launch / init
- '--user-data/-U' and '--user-data-file/-u'
- add '--ideb' to install a deb (use multiple times).
* launch:
- add '--wait'
adds new commands:
* wait: wait for the container to boot
* chroot: chroot into container (do not start)
* delete-all: delete matching containers (default '*')
EOF
}
bad_Usage() { Usage 1>&2; error "$@"; return 1; }
quote_cmd() {
local quote='"' x="" vline=""
for x in "$@"; do
if [ "${x#* }" != "${x}" ]; then
if [ "${x#*$quote}" = "${x}" ]; then
x="\"$x\""
else
x="'$x'"
fi
fi
vline="${vline} $x"
done
echo "${vline# }"
}
main_delete_all() {
local out="" python="python3" all="" x=""
out=$(lxc list --format=json) ||
fail "fail: lxc list --format=json"
all=$(echo "$out" |
$python -c '
import json, sys;
sys.stdout.write("\n".join([f["name"] for f in json.load(sys.stdin)]) + "\n")'
)
if [ -z "$all" ]; then
error "nothing to delete"
exit 0
fi
if [ $# -eq 0 ]; then
set -- "*"
fi
local matches="" input=""
matches=$(
for name in $all; do
for m in "$@"; do
case "$name" in
$m) echo "$name"; break;;
esac
done
done
)
if [ -z "$matches" ]; then
error "no matches to $*"
exit 0
fi
if [ "$1" != "--force" ]; then
echo "delete all of (Y/n):"
for i in $matches; do
echo " $i"
done
echo -n "? "
read x
case "$x" in
y|Y|"") :;;
*) error aborted.; exit 0;;
esac
fi
debug 1 lxc delete --force $matches
lxc delete --force $matches
exit
}
main_wait() {
local short_opts="hm:o:v"
local long_opts="help,max:,verbose"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage; return; }
local cur="" next="" max="$DEFAULT_WAIT_MAX"
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
-h|--help) Usage ; exit 0;;
-m|--max) max=$next; shift;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
--) shift; break;;
esac
shift;
done
[ $# -gt 0 ] || { bad_Usage "must provide container"; return; }
local fails="" name=""
for name in "$@"; do
lxc exec "$name" -- bash -s wait-inside \
"$name" "$max" "$VERBOSITY" < "$0" ||
fails="${fails:+${fails} }$name"
done
if [ -n "$fails" ]; then
debug 2 "had failures: $fails"
return 1
else
debug 2 "ready."
return 0
fi
}
is_done_cloudinit() {
[ -e "/run/cloud-init/result.json" ]
_RET=""
}
inargs() {
# inargs(needle, [haystack])
# return 0 if needle is in haystack
local n="$1" i=""
shift
for i in "$@"; do
[ "$i" = "$n" ] && return 0
done
return 1
}
is_done_systemd() {
local s="" num="$1"
s=$(systemctl is-system-running 2>&1);
_RET="$? $s"
case "$s" in
initializing|starting) return 1;;
*[Ff]ailed*connect*bus*)
# warn if not the first run.
[ "$num" = "$0" ] ||
error "Failed to connect to systemd bus [${_RET%% *}]";
return 1;;
esac
return 0
}
is_done_other() {
return 1
}
wait_inside() {
local name="$1" max="${2:-${DEFAULT_MAX}}" debug=${3:-0}
local i=0 check="is_done_other";
if [ -e /run/systemd ]; then
check=is_done_systemd
elif [ -x /usr/bin/cloud-init ]; then
check=is_done_cloudinit
fi
[ "$debug" != "0" -a "$check" = "is_done_other" ] &&
echo "no cloud-init or systemd, just waiting."
while ! $check $i && i=$(($i+1)); do
[ $i -ge $max ] && exit 0
[ "$debug" = "0" ] || echo -n .
sleep 1
done
if [ "$debug" != "0" ]; then
read up idle </proc/uptime
echo "[$name ${i:+done after $i }up=$up${_RET:+ ${_RET}}]"
fi
}
main_init() {
local cmd="" cur="" next="" udata="_unset" ufile="" dry="false" pcmd=""
local dowait=false
local subcmd="$1" img="" name="" debs="" addargs="" enter=false
cmd=( )
shift
debs=( )
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
--user-data|-U) udata=$next; shift;;
--user-data=*) udata=${cur#*=};;
--user-data-file|-u) ufile=$next; shift;;
--user-data-file=*) ufile=${cur#*=};;
--dry-run) dry=true;;
--insert-deb=*|--ideb=*) debs[${#debs[@]}]="${cur#*=}";;
--insert-deb|--ideb) debs[${#debs[@]}]="$next"; shift;;
--wait) dowait=${DEFAULT_WAIT_MAX};;
--wait=*) dowait=${cur#*=}; shift;;
--) break;;
-*) cmd[${#cmd[@]}]="$cur";;
*)
if [ -z "$img" ]; then
img="$cur"
img_argn=${#cmd[@]}
elif [ -z "$name" ]; then
name="$cur"
name_argn=${#cmd[@]}
fi
cmd[${#cmd[@]}]="$cur"
;;
esac
shift;
done
addargs=( "$@" )
case "$subcmd" in
l) subcmd=launch;;
le) subcmd=launch; enter=true;;
lew|lwe) subcmd=launch; dowait=${DEFAULT_WAIT_MAX}; enter=true;;
lw) subcmd=launch; dowait=${DEFAULT_WAIT_MAX};;
i) subcmd=init;;
ls) subcmd=list;;
esac
local need_start=false osubcmd=$subcmd
if [ -n "${ALIASES[$img]}" ]; then
debug 1 "translated image $img to ${ALIASES[$img]}"
cmd[$img_argn]="${ALIASES[$img]}"
fi
if [ "$osubcmd" = "init" -o "$osubcmd" = "launch" ] && [ -z "$name" ]; then
local names="" i=0 bn="${img##*:}"
names=( $(lxc list --columns=n --format=csv "${bn}.*") )
while i=$(($i+1)); do
if ! inargs "$bn$i" "${names[@]}"; then
name="${bn}$i"
cmd[${#cmd[@]}]="$name"
break
fi
done
debug 1 "set name for image $img to $name"
fi
if [ "$dowait" != "false" ]; then
if [ "$subcmd" = "init" ]; then
error "'$subcmd' is incompatible with '--wait'"
return 1
fi
if [ -z "$name" ]; then
error "--wait only supported if name provided to launch"
return 1
fi
fi
if [ -n "$ufile" ]; then
if [ ! -f "$ufile" ]; then
error "--user-data-file '$ufile' is not a file"
return 1
fi
udata=$(cat "$ufile")
fi
if [ "$udata" != "_unset" ]; then
cmd[${#cmd[@]}]="--config=user.user-data=$udata"
fi
local meta_data=""
local ssh_keys=""
if [ -f ~/.ssh/id_rsa.pub ]; then
# prefer the single key rather than a bunch.
ssh_keys=$(cat ~/.ssh/id_rsa.pub) ||
{ rerror "failed reading ~/.ssh/id_rsa.pub"; return; }
elif out=$(ssh-add -L) 2>/dev/null && [ -n "$out" ]; then
ssh_keys="$out"
fi
if [ -n "$ssh_keys" ]; then
local ssh_key_yaml=""
ssh_key_yaml=$(
echo 'public-keys:'
IFS=$'\n'; set -- $ssh_keys; unset IFS
for k in "$@"; do
echo " - $k"
done
)
meta_data="${meta_data}${ssh_key_yaml}"
fi
if [ -n "$meta_data" ]; then
cmd[${#cmd[@]}]="--config=user.meta-data=${meta_data}"
fi
if [ "${#debs[@]}" != "0" -a "$subcmd" = "launch" ]; then
subcmd=init
need_start=true
if [ -z "$name" ]; then
error "--insert-deb requires name"
fi
fi
local debs_tar="" mdebs="" i="" ldir="" dir="" debnames="" out=""
debs_tar=( )
for i in "${debs[@]}"; do
cur=$(readlink -f "$i") && [ -f "$cur" ] || {
mdebs="${mdebs:+${mdebs} } $i"
continue
}
dir="${cur%/*}"
if [ "${dir}" != "$ldir" ]; then
debs_tar[${#debs_tar[@]}]="--directory=$dir"
fi
debs_tar[${#debs_tar[@]}]="${cur##*/}"
debnames="${debnames} ./${cur##*/}"
ldir="$dir"
done
if [ -n "$mdebs" ]; then
error "some debs were not files: $mdebs"
return 1
fi
cmd=( lxc $subcmd "${cmd[@]}" "${addargs[@]}" )
pcmd=$(quote_cmd "${cmd[@]}")
debug 1 "$pcmd"
if $dry; then
[ $VERBOSITY -lt 1 ] && debug 0 "$pcmd"
return 0
fi
"${cmd[@]}" || { rerror "failed: ${pcmd}"; return; }
if [ "${#debs[@]}" != "0" ]; then
debug 1 "installing ${debnames} into $name"
# using 'apt install' here lets dependencies get pulled.
local out=""
out=$(
tar -cf - "${debs_tar[@]}" | lxc-pstart "$name" -- sh -ec '
d=$(mktemp -d); trap "rm -Rf $d" EXIT;
cd "$d"; tar -xf -;
export DEBIAN_FRONTEND=noninteractive
apt-get install --assume-yes "$@" </dev/null' \
xlc-install-debs $debnames ) || {
error "failed to install in $name [$?]: ${debnames}"
error "$out"
return
}
fi
local info=""
if info=$(lxc file pull "$name/etc/cloud/build.info" - 2>/dev/null); then
error "$(echo "$info" | grep serial)"
fi
if $need_start; then
debug 1 "starting $name"
lxc start "$name"
fi
if [ "$dowait" != "false" ]; then
main_wait -v "--max=$dowait" "$name" || fail "failed wait on $name."
fi
if $enter; then
lxc exec "$name" "/bin/bash"
fi
}
main_chroot() {
local short_opts="hv"
local long_opts="help,verbose"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage; return; }
local cur="" next="" container="" status="" out=""
local oldconfig="" reset=false ret="0"
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
-h|--help) Usage ; exit 0;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
--) shift; break;;
esac
shift;
done
[ $# -ne 0 ] || { bad_Usage "must provide container."; return; }
container="$1"
shift
[ $# -eq 0 ] && set -- /bin/bash
out=$(lxc info "$container") ||
fail "failed: lxc info '$container'"
status=$(echo "$out" | awk '$1 == "Status:" { print $2; exit(0); }')
local vflag=$([ "$VERBOSITY" -ge 1 ] && echo "-v" || :)
if [ "$status" = "Stopped" ]; then
debug 1 "Using lxc-pstart."
lxc-pstart $vflag --start "$container" -- "$@"
else
debug 1 "Using lxc exec (container status=$status)"
lxc exec "$container" -- "$@"
fi
ret=$?
exit $ret
}
main() {
if [ -z "$1" -o "$1" = "-h" -o "$1" = "--help" ]; then
Usage;
exit 0
fi
if [ "${1#-v}" != "$1" ]; then
VERBOSITY=$((${#1}-1))
shift
fi
case "$1" in
launch|init|l|ls|i|lwe|lew|lw|le) main_init "$@";;
chroot) shift; main_chroot "$@";;
wait) shift; main_wait "$@";;
wait-inside) shift; wait_inside "$@";;
delete-all) shift; main_delete_all "$@";;
*) exec lxc "$@";;
esac
}
main "$@"
# vi: ts=4 expandtab
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment