Skip to content

Instantly share code, notes, and snippets.

@aeris
Created January 10, 2017 17:07
Show Gist options
  • Save aeris/26add07aef31a887237e3ba8530f49cc to your computer and use it in GitHub Desktop.
Save aeris/26add07aef31a887237e3ba8530f49cc to your computer and use it in GitHub Desktop.
LXC management
#!/usr/bin/env python
# License : AGPLv3+
import subprocess
import logging
import re
import os.path
import tempfile
from datetime import date, datetime, timedelta
import shutil
from glob import glob
import yaml
import fcntl
import argparse
import sys
from contextlib import contextmanager
class ColorizedFormatter(logging.Formatter):
COLORS = {
logging.FATAL: "\x1b[0;37;41m",
logging.ERROR: "\x1b[0;31;49m",
logging.WARNING: "\x1b[0;33;49m",
logging.INFO: "\x1b[0;94;49m",
logging.DEBUG: "\x1b[0;90;49m"
}
def format(self, record):
level = record.levelno
color = ColorizedFormatter.COLORS[level]
level = record.levelname
message = record.getMessage()
return "[%(color)s%(level)8s\x1b[0m] %(message)s" % {"color": color,
"level": level,
"message": message}
console = logging.StreamHandler()
console.setFormatter(ColorizedFormatter())
LOGGER = logging.getLogger("lxc")
LOGGER.addHandler(console)
LOGGER.setLevel(os.environ.get("LOG") or logging.DEBUG)
def fatal(*args, **kwargs):
LOGGER.fatal(*args, **kwargs)
exit(-1)
def call(*cmd, **kwargs):
LOGGER.debug(cmd)
stdin = kwargs.get("stdin")
if stdin:
process = subprocess.Popen(cmd, stdin=subprocess.PIPE)
process.communicate(stdin)
process.wait()
retcode = process.returncode
if retcode:
raise subprocess.CalledProcessError(retcode, cmd)
else:
stdout = kwargs.get("stdout")
if stdout:
return subprocess.check_output(cmd)
else:
subprocess.check_call(cmd)
@contextmanager
def lock(lockfile):
LOGGER.debug("Locking %s", lockfile)
with open(lockfile, "w") as file:
try:
fcntl.flock(file, fcntl.LOCK_EX | fcntl.LOCK_NB)
try:
yield
finally:
fcntl.flock(file, fcntl.LOCK_UN)
finally:
os.unlink(lockfile)
LOGGER.debug("Unlocking %s", lockfile )
@contextmanager
def tmp_dir():
dir = tempfile.mkdtemp()
try:
yield dir
finally:
os.rmdir(dir)
@contextmanager
def activate_lvm(lvm):
LOGGER.info("Activating LVM %s", lvm)
call("lvchange", "-ay", "-Ky", lvm)
try:
yield
finally:
LOGGER.info("Deactivating LVM %s", lvm)
call("lvchange", "-an", lvm)
@contextmanager
def lvm_snapshot(dev, snapshot):
LOGGER.info("Creating LVM snapshot of %s to %s", dev, snapshot)
call("lvcreate", "-n", snapshot, "-s", dev)
try:
with activate_lvm(snapshot):
yield
finally:
LOGGER.info("Removing LVM snapshot %s", snapshot)
call("lvremove", snapshot)
@contextmanager
def mount(dev):
with tmp_dir() as dir:
LOGGER.info("Mounting %s to %s", dev, dir)
call("mount", dev, dir)
try:
yield dir
finally:
LOGGER.info("Unmounting %s", dir)
call("umount", dir)
class LXCGuest:
DATE_FORMAT = "%Y-%m-%d"
BACKUP_DATE = date.today().strftime(DATE_FORMAT)
BACKUP_DIR = "/var/backups/lxc"
BACKUP_TODAY_DIR = os.path.join(BACKUP_DIR, BACKUP_DATE)
LXC_DIR = "/var/lib/lxc"
# EXCLUDES = ["/dev/*", "/proc/*", "/sys/*", "/tmp/*"]
EXCLUDES = ["/tmp/*"]
RETENTION = timedelta(days=7)
BACKUP_CONFIG = "/etc/lxc/backup.yml"
def __init__(self, name, state=None):
self.__name = name
self.__state = state
def name(self):
return self.__name
def state(self):
return self.__state
def __call(self, *args):
cmd = ["lxc-attach", "-n", self.__name] + list(args)
call(*cmd)
def __call_bash(self, cmd):
cmd = ["--", "bash", "-c", cmd]
self.__call(*cmd)
def __rootfs(self):
rootfs = call("lxc-info", "-n", self.__name, "-c", "lxc.rootfs",
stdout=True)
rootfs = rootfs.strip()
rootfs = re.split("\\s+=\\s+", rootfs)
rootfs = rootfs[1]
return rootfs
def backup(self):
LOGGER.info("Backuping %s", self.__name)
LOGGER.info(" Running backup scripts inside the container")
self.__call_bash("[ -d /etc/backup ] && run-parts /etc/backup || true")
rootfs = self.__rootfs()
snapshot = rootfs + "-snap"
with lvm_snapshot(rootfs, snapshot):
with mount(snapshot) as mountpoint:
archive = os.path.join(LXCGuest.BACKUP_TODAY_DIR,
self.__name + ".tar.xz")
LOGGER.info(" Backuping %s to %s", mountpoint, archive)
tar = ["tar", "-I", "pxz -9e -k -T8", "--numeric-owner",
"-cf", archive, "-C", mountpoint, "."]
tar += ["--exclude=.%s" % exclude for exclude in
LXCGuest.EXCLUDES]
call(*tar)
lxc_config = os.path.join(LXCGuest.LXC_DIR, self.__name,
"config")
config = os.path.join(LXCGuest.BACKUP_TODAY_DIR,
self.__name + ".config")
LOGGER.info(" Backuping %s to %s", lxc_config, config)
shutil.copy(lxc_config, config)
@staticmethod
def list(running=True, stopped=False):
ls = call("lxc-ls", "-f", stdout=True)
ls = ls.split("\n")[2:] # Skip headers
containers = []
for l in ls:
l = re.split("\\s+", l)
if len(l) != 6:
continue
name, state = l[0], l[1]
if (running and state == "RUNNING") or \
(stopped and state == "STOPPED"):
container = LXCGuest(name, state)
containers.append(container)
return containers
@staticmethod
def delete_old():
LOGGER.info("Purging old backups")
backups = os.path.join(LXCGuest.BACKUP_DIR, "*")
backups = glob(backups)
for backup in sorted(backups):
d = os.path.basename(backup)
d = datetime.strptime(d, LXCGuest.DATE_FORMAT).date()
if date.today() - d >= LXCGuest.RETENTION:
LOGGER.info(" Purging %s backup", d)
shutil.rmtree(backup)
else:
LOGGER.debug(" Keeping %s backup", d)
@staticmethod
def is_backup_enabled(config, container):
if container.state() != "RUNNING":
return False
included = config.get("include", [])
excluded = config.get("exclude", [])
name = container.name()
if excluded and name in excluded:
return False
if included:
if name in included:
return True
return False
return True
@staticmethod
def backup_all(hosts=None):
if not os.path.isdir(LXCGuest.BACKUP_TODAY_DIR):
os.makedirs(LXCGuest.BACKUP_TODAY_DIR)
if os.path.isfile(LXCGuest.BACKUP_CONFIG):
config = open(LXCGuest.BACKUP_CONFIG, "r")
config = yaml.load(config) or {}
else:
config = {}
if hosts:
containers = [LXCGuest(host) for host in hosts]
else:
containers = LXCGuest.list()
containers = filter(
lambda c: LXCGuest.is_backup_enabled(config, c),
containers
)
for container in containers:
container.backup()
# Don't purge old backup if not all guests backuped !
if not hosts:
LXCGuest.delete_old()
@staticmethod
def restore(host, size=None, dir=None):
if not size:
size = "5G"
if not dir:
dir = os.getcwd()
LOGGER.info("Restoring %s from %s", host, dir, size)
LOGGER.info(" Creating LVM %s with %i size", host, size)
call("lvcreate", "--virtualsize", size, "--thin", "--name", host,
"lxc/lxc")
dev = os.path.join("/dev/lxc", host)
LOGGER.info(" Formatting %s to ext4", dev)
call("mkfs.ext4", dev)
lxc_dir = os.path.join("/var/lib/lxc", host)
if not os.path.isdir(lxc_dir):
os.makedirs(lxc_dir)
config_from = os.path.join(dir, host + ".config")
config_to = os.path.join(lxc_dir, "config")
LOGGER.info(" Restoring config %s to %s", config_from, config_to)
shutil.copy(config_from, config_to)
with mount(dev) as mountpoint:
tar = os.path.join(dir, host + ".tar.xz")
LOGGER.info(" Desarchiving archive %s to %s", tar, mountpoint)
call("tar", "-C", mountpoint, "-xf", tar)
class LXCHost:
BACKUP_DIR = "/var/backups/lxc"
BACKUP_CONFIG = "/etc/lxc/backup.yml"
RSYNC = ["rsync", "-axh", "--delete"]
CONFIG = None
MONITORING = {
"AGED": timedelta(days=2),
"OBSOLETED": timedelta(days=3)
}
@staticmethod
def config():
if LXCHost.CONFIG:
return LXCHost.CONFIG
try:
config = open(LXCHost.BACKUP_CONFIG, "r")
config = yaml.load(config)
LXCHost.CONFIG = config
except:
LXCHost.CONFIG = {}
return LXCHost.CONFIG
def __init__(self, name):
self.__name = name
def backup(self):
lockfile = os.path.join(LXCHost.BACKUP_DIR, self.__name + ".lock")
with lock(lockfile):
# LOGGER.info("Launch LXC backup on %s", self.__name)
# call("ssh", self.__name, "lxc-backup")
backup_dir = os.path.join(LXCHost.BACKUP_DIR, self.__name)
target = "%s:%s/" % (self.__name, LXCGuest.BACKUP_DIR)
dest = "%s/" % (backup_dir)
LOGGER.info("Backuping %s (%s -> %s)", self.__name, target, dest)
rsync = LXCHost.RSYNC + [target, dest]
call(*rsync)
@staticmethod
def backup_all(hosts=None):
if not os.path.isdir(LXCHost.BACKUP_DIR):
os.makedirs(LXCHost.BACKUP_DIR)
if not hosts:
hosts = LXCHost.config().get("hosts", {}).keys()
for host in hosts:
host = LXCHost(host)
host.backup()
@staticmethod
def age(host, guest):
path = os.path.join(LXCHost.BACKUP_DIR, host, "*", "%s.tar.xz" % guest)
backups = glob(path)
age_min = timedelta.max
for backup in backups:
parent = os.path.dirname(backup)
d = os.path.basename(parent)
d = datetime.strptime(d, LXCGuest.DATE_FORMAT).date()
age = date.today() - d
if age < age_min:
age_min = age
return age_min
@staticmethod
def check_all():
hosts = LXCHost.config().get("hosts", {})
obsoleted = []
aged = []
for host, guests in hosts.iteritems():
for guest in guests:
age = LXCHost.age(host, guest)
result = (host, guest, age)
if age > LXCHost.MONITORING["OBSOLETED"]:
obsoleted.append(result)
elif age > LXCHost.MONITORING["AGED"]:
aged.append(result)
if obsoleted:
print("Obsoleted : %i backups" % len(obsoleted))
for o in obsoleted:
print(" %s @ %s : %s" % o)
if aged:
print("Aged : %i backups" % len(aged))
for a in aged:
print(" %s @ %s : %s" % a)
if obsoleted:
exit(2)
if aged:
exit(1)
parser = argparse.ArgumentParser(description="Backup LXC host or guests")
parser.add_argument("-b", "--backend",
help="Set this flag if run on the backup backend",
action="store_true")
parser.add_argument("-c", "--check",
help="Check the backups state",
action="store_true")
parser.add_argument("-r", "--restore",
help="Restore container from backup",
action="store_true")
parser.add_argument("args", nargs="*")
args = parser.parse_args()
if args.check:
if args.backend:
LXCHost.check_all()
elif args.restore:
# Python list has no .get :'(
args = dict(enumerate(args.args))
host = args[0]
size = args.get(1)
dir = args.get(2)
LXCGuest.restore(host, size=size, dir=dir)
else:
if args.backend:
LXCHost.backup_all(args.args)
else:
LXCGuest.backup_all(args.args)
#!/usr/bin/env python
# License : AGPLv3+
import argparse
import base64
import glob
import hashlib
import logging
import os
import random
import re
import socket
import subprocess
import textwrap
from contextlib import contextmanager
from grp import getgrnam
from pwd import getpwnam
from time import sleep
from jinja2 import Template
class ColorizedFormatter(logging.Formatter):
COLORS = {
logging.FATAL: "\x1b[0;37;41m",
logging.ERROR: "\x1b[0;31;49m",
logging.WARNING: "\x1b[0;33;49m",
logging.INFO: "\x1b[0;94;49m",
logging.DEBUG: "\x1b[0;90;49m"
}
def format(self, record):
level = record.levelno
color = ColorizedFormatter.COLORS[level]
level = record.levelname
message = record.getMessage()
return "[%(color)s%(level)8s\x1b[0m] %(message)s" % {"color": color,
"level": level,
"message": message}
console = logging.StreamHandler()
console.setFormatter(ColorizedFormatter())
LOGGER = logging.getLogger("lxc")
LOGGER.addHandler(console)
LOGGER.setLevel(logging.DEBUG)
def fatal(*args, **kwargs):
LOGGER.fatal(*args, **kwargs)
exit(-1)
def call(*cmd, **kwargs):
LOGGER.debug(cmd)
stdin = kwargs.get("stdin")
if stdin:
process = subprocess.Popen(cmd, stdin=subprocess.PIPE)
process.communicate(stdin)
process.wait()
retcode = process.returncode
if retcode:
raise subprocess.CalledProcessError(retcode, cmd)
else:
stdout = kwargs.get("stdout")
if stdout:
return subprocess.check_output(cmd)
else:
subprocess.check_call(cmd)
@contextmanager
def do_in_mount(dev, dir):
LOGGER.info("Mounting %s to %s", dev, dir)
call("mount", dev, dir)
try:
yield
finally:
LOGGER.info("Unmounting %s", dir)
call("umount", dir)
def chroot_call(directory, *cmd, **kwargs):
cmd = ["chroot", directory] + list(cmd)
call(*cmd, **kwargs)
def file_content(content, dst, user="root", group="root", chmod="644"):
with open(dst, "w") as file:
file.write(content)
uid = getpwnam(user).pw_uid
gid = getgrnam(group).gr_gid
os.chown(dst, uid, gid)
chmod = int(chmod, 8)
os.chmod(dst, chmod)
def file_template(src, dst, user="root", group="root", chmod="644", **kwargs):
template = Template(src)
content = template.render(**kwargs)
file_content(content, dst, user=user, group=group, chmod=chmod)
class LXCGuest:
LXC_DIR = "/var/lib/lxc"
def __init__(self, name):
self.__name = name
self.__dir = self.__path()
self.__dev = os.path.join("/dev/lxc", self.__name)
self.__root = self.__path("rootfs")
def __path(self, *args):
path = [LXCGuest.LXC_DIR, self.__name]
if args:
path += args
return os.path.join(*path)
def __rootfs_path(self, *args):
return self.__path("rootfs", *args)
def __is_mounted(self):
return os.path.ismount(self.__root)
@contextmanager
def mount(self):
if self.__is_mounted():
fatal("Rootfs is already mounted [%s]", self.__root)
with do_in_mount(self.__dev, self.__root):
yield
def create_container_from_file_template(self, template, size):
LOGGER.info("Creating LXC container %s from template %s",
self.__name, template)
call("lxc-create", "--bdev", "lvm", "--fssize", size,
"--template", template, "--name", self.__name)
def clone_container(self, clone, size):
LOGGER.info("Cloning LXC container %s from %s", self.__name, clone)
call("lxc-copy", "-B", "lvm", "--fssize", size,
"--name", clone, "--newname", self.__name)
def create_container_from_archive(self, tar, size):
LOGGER.info("Creating LXC container %s from archive %s",
self.__name, tar)
if not os.path.exists(tar):
fatal("Unable to find archive %s", tar)
LOGGER.debug(" Creating LXC container directory %s", self.__dir)
os.mkdir(self.__dir, 0o770)
LOGGER.debug(" Creating LXC container rootfs directory %s",
self.__root)
os.mkdir(self.__root, 0o755)
LOGGER.debug(
" Creating thin logical volume %s on lxc/lxc pool with %s size",
self.__dir, size)
call("lvcreate", "--virtualsize", size, "--thin", "--name", self.__name,
"lxc/lxc")
LOGGER.debug(" Formatting %s to ext4", self.__dev)
call("mkfs.ext4", self.__dev)
with self.mount():
LOGGER.debug(" Desarchiving %s to %s", tar, self.__root)
call("tar", "-C", self.__root, "-xf", tar)
def create_container(self, args):
if os.path.exists(self.__dir):
if not args.force:
fatal("Guest already exists [%s]", self.__dir)
else:
size = args.size
clone = args.clone
tar = args.tar
if clone:
self.clone_container(clone, size)
elif tar:
self.create_container_from_archive(tar, size)
else:
self.create_container_from_file_template(args.template, size)
def create_fstab(self):
fstab = self.__path("fstab")
LOGGER.info("Creating empty fstab [%s]", fstab)
file_content("", fstab)
@staticmethod
def __random_mac(prefix="02:00:00"):
mac = prefix.replace(":", "")
while len(mac) < 12:
digit = random.randint(0x0, 0xf)
digit = "{0:x}".format(digit)
mac += digit
mac = textwrap.wrap(mac, 2)
mac = ":".join(mac)
mac = mac.lower()
return mac
def create_config(self, args):
mac = LXCGuest.__random_mac()
veth = self.__name
# Interface name is limited to 15 characters
if len(veth) > 15:
hash = hashlib.sha256()
hash.update(veth)
veth = hash.hexdigest()
veth = "lxc-{0}".format(veth)
veth = veth[:15]
config = self.__path("config")
LOGGER.info(
"Creating container config (bridge: %s, veth: %s, mac: %s) [%s]",
args.bridge, veth, mac, config)
file_template("""\
lxc.start.auto = 1
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = {{bridge}}
lxc.network.name = eth0
lxc.network.veth.pair = {{veth}}
lxc.network.hwaddr = {{mac}}
lxc.rootfs = {{dev}}
# Common configuration
lxc.include = /usr/share/lxc/config/debian.common.conf
# Container specific configuration
lxc.mount = {{dir}}/fstab
lxc.utsname = {{name}}
lxc.arch = amd64
{% if memory %}
lxc.cgroup.memory.limit_in_bytes = {{memory}}
{% endif %}
{% if swap %}
lxc.cgroup.memory.memsw.limit_in_bytes = {{swap}}
{% endif %}
""", config, name=self.__name, dev=self.__dev, dir=self.__dir, root=self.__root,
veth=veth, mac=mac, bridge=args.bridge,
memory=args.memory, swap=args.swap)
def configure_network(self, args):
interfaces = self.__rootfs_path("etc/network/interfaces")
LOGGER.info("Enabling interfaces.d [%s]", interfaces)
file_content("source-directory /etc/network/interfaces.d\n", interfaces)
lo = self.__rootfs_path("etc/network/interfaces.d/lo")
LOGGER.info("Configuring loopback interface [%s]", lo)
file_content("""\
auto lo
iface lo inet loopback
""", lo)
if args.gateway is True:
ips = call("ip", "addr", "show", "dev", args.bridge, stdout=True)
ips = re.search("\\s+inet ([\\d.]+)/\\d+ brd .* scope global .*",
ips)
gateway = ips.group(1)
args.gateway = gateway
eth0 = self.__rootfs_path("etc/network/interfaces.d/eth0")
LOGGER.info("Configuring eth0 interface (ip: %s, gw: %s) [%s]",
args.ip, args.gateway, eth0)
file_template("""\
auto eth0
iface eth0 inet static
address {{ip}}
gateway {{gw}}
""", eth0, ip=args.ip, gw=args.gateway)
resolvconf = self.__rootfs_path("etc/resolv.conf")
LOGGER.info("Configuring DNS resolver (domain: %s, gw: %s) [%s]",
args.domain, args.gateway, resolvconf)
file_template("""\
domain {{domain}}
search {{domain}}
nameserver {{gw}}
""", resolvconf, domain=args.domain, gw=args.gateway)
hostname = self.__rootfs_path("etc/hostname")
LOGGER.info("Configuring hostname (hostname: %s) [%s]",
args.hostname, hostname)
file_template("{{hostname}}\n", hostname, hostname=args.hostname)
fqdn = "{name}.{domain}".format(name=args.hostname, domain=args.domain)
hosts = self.__rootfs_path("etc/hosts")
LOGGER.info("Configuring host names (hostname: %s, FQDN: %s) [%s]",
args.hostname, fqdn, hosts)
file_template("""\
127.0.0.1 {{fqdn}} {{name}}
127.0.0.1 localhost.localdomain localhost
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
""", hosts, name=args.hostname, fqdn=fqdn)
def __chroot(self, *cmd, **kwargs):
chroot_call(self.__root, *cmd, **kwargs)
def __lxc(self, *cmd, **kwargs):
cmd = ["lxc-attach", "-n", self.__name] + list(cmd)
call(*cmd, **kwargs)
def __start(self):
LOGGER.info("Starting guest")
call("lxc-start", "-dn", self.__name)
sleep(5)
def __stop(self):
LOGGER.info("Stopping guest")
call("lxc-stop", "-n", self.__name)
def set_root_password(self, password):
if password is None:
return
if password:
LOGGER.info("Setting root password")
self.__chroot("chpasswd",
stdin="root:{password}\n".format(password=password))
else:
LOGGER.info("Disabling root password")
self.__chroot("passwd", "--delete", "--lock", "root")
def deploy_ssh_keys(self, keys):
authorized_keys = self.__rootfs_path("root/.ssh/authorized_keys")
dir = os.path.dirname(authorized_keys)
if not os.path.isdir(dir):
os.mkdir(dir, 0o700)
with open(authorized_keys, "w") as authorized_keys:
for key in keys:
if os.path.isfile(key):
with open(key, "r") as file:
key = file.readlines()
else:
key = [key]
for key in key:
key = key.strip()
LOGGER.info("Adding SSH keys %s", key)
authorized_keys.write(key)
@contextmanager
def disable_services(self):
policy = self.__rootfs_path("usr/sbin/policy-rc.d")
LOGGER.info("Disabling service start [%s]", policy)
file_content("#!/bin/sh\nexit 101\n", policy, chmod="755")
try:
yield
finally:
LOGGER.info("Enabling service start [%s]", policy)
os.unlink(policy)
def regenerate_ssh_keys(self):
LOGGER.info("Removing old SSH host keys")
keys = self.__rootfs_path("etc/ssh/ssh_host*")
keys = glob.glob(keys)
for file in keys:
LOGGER.debug(" Remove SSH host key %s", file)
os.unlink(file)
LOGGER.info("Creating new SSH host keys")
self.__chroot("ssh-keygen", "-A")
def configure_apt(self, args):
resolvconf = self.__rootfs_path("etc/resolv.conf")
LOGGER.info(
"Configuring DNS resolver to localhost (we will be in a chroot!) [%s]",
resolvconf)
# Needed to have connectivity inside chroot
file_content("nameserver 127.0.0.1\n", resolvconf)
recommends = self.__rootfs_path("etc/apt/apt.conf.d/60recommends")
LOGGER.info("Disabling APT recommends & suggests [%s]", recommends)
file_content("""\
APT::Install-Recommends "0";
APT::Install-Suggests "0";
""", recommends)
if args.apt_proxy:
if args.apt_proxy is True:
args.apt_proxy = "apt.{domain}".format(domain=args.domain)
proxy = self.__rootfs_path("etc/apt/apt.conf.d/60proxy")
LOGGER.info(
"Configuring APT proxy to %s [%s]", args.apt_proxy, proxy)
file_template(
"Acquire::http::Proxy \"http://{{host}}:3142\";\n",
proxy, host=args.apt_proxy)
self.__chroot("apt", "update")
self.__chroot("apt", "install", "-y", "apt-utils")
def clean(self):
LOGGER.info("Cleaning apt cache")
self.__chroot("apt-get", "clean")
LOGGER.info("Removing logs")
self.__chroot("find", "/var/log", "-type", "f", "-delete")
@staticmethod
def create(args):
name = args.name
guest = LXCGuest(name)
guest.create_container(args)
guest.create_fstab()
guest.create_config(args)
with guest.mount():
guest.set_root_password(args.password)
guest.deploy_ssh_keys(args.keys)
with guest.disable_services():
guest.regenerate_ssh_keys()
guest.configure_apt(args)
# Reset resolv.conf, must be call at the end
guest.configure_network(args)
guest.clean()
local_name = socket.gethostname()
local_name = re.escape(local_name + ".")
local_fqdn = socket.getfqdn()
local_domain = re.sub("^" + local_name, "", local_fqdn)
parser = argparse.ArgumentParser(description="Create LXC guest")
parser.add_argument("-f", "--force",
help="In case of error, force creation if possible",
action="store_true")
parser.add_argument("-n", "--name", help="Guest name", required=True)
parser.add_argument("-H", "--hostname", help="Hostname (default to $name)")
parser.add_argument("-d", "--domain", help="Domain (default to host domain)",
default=local_domain)
parser.add_argument("-T", "--template", help="LXC template", nargs="?",
const="debian")
parser.add_argument("-s", "--size", help="Filesystem size", default="5G")
parser.add_argument("-c", "--clone", help="Clone from existing LXC guest")
parser.add_argument("-t", "--tar", help="Clone from archive")
parser.add_argument("-m", "--memory", help="Cap memory")
parser.add_argument("-w", "--swap", help="Cap swap")
parser.add_argument("-b", "--bridge", help="Bridge to use (default br1)",
default="br1")
parser.add_argument("-i", "--ip", help="IP address (CIDR notation IP/netmask)",
required=True)
parser.add_argument("-g", "--gateway", help="Gateway IP address", default=True)
parser.add_argument("-p", "--password", nargs="?", const=False,
help="Root password (set to empty to disable root login)")
parser.add_argument("-k", "--keys", nargs="+", help="SSH keys to deploy")
parser.add_argument("-P", "--apt-proxy",
help="APT proxy to use (default apt.$domain)",
nargs="?", const=True)
args = parser.parse_args()
if not args.hostname:
args.hostname = args.name
LXCGuest.create(args)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment