233 lines
5.6 KiB
Python
Executable File
233 lines
5.6 KiB
Python
Executable File
#!/usr/bin/env python
|
|
import argparse
|
|
import binascii
|
|
import http.server
|
|
import os
|
|
import random
|
|
import socket
|
|
import string
|
|
import subprocess
|
|
import time
|
|
|
|
|
|
HTTP_PROXY = 'caithe.pyrocufflink.jazz:3128'
|
|
PORT_RANGE = (10000, 65535)
|
|
REPO_URL = 'http://mirror.centos.org/centos/7/os/x86_64'
|
|
|
|
|
|
KICKSTART = string.Template('''\
|
|
text
|
|
install
|
|
url --url=http://mirror.centos.org/centos/7/os/x86_64
|
|
repo --name=updates --baseurl=http://mirror.centos.org/centos/7/updates/x86_64
|
|
repo --name=extras --baseurl=http://mirror.centos.org/centos/7/extras/x86_64
|
|
lang en_US.UTF-8
|
|
keyboard us
|
|
timezone --utc UTC
|
|
rootpw --iscrypted x
|
|
shutdown
|
|
|
|
bootloader --location=mbr
|
|
clearpart --all --initlabel
|
|
autopart --type=lvm
|
|
|
|
network --hostname=${hostname}
|
|
|
|
%packages --nocore
|
|
@core --nodefaults
|
|
-biosdevname
|
|
-btrfs-progs
|
|
-firewalld
|
|
-iprutils
|
|
-irqbalance
|
|
-kexec-tools
|
|
-man-db
|
|
-parted
|
|
-plymouth
|
|
-teamd
|
|
-tuned
|
|
avahi
|
|
qemu-guest-agent
|
|
%end
|
|
|
|
%addon com_redhat_kdump --disable
|
|
%end
|
|
|
|
%post
|
|
install -d /root/.ssh
|
|
cat > /root/.ssh/authorized_keys <<EOF
|
|
${ssh_pubkey}
|
|
EOF
|
|
sed 's/use-ipv6=.*/use-ipv6=yes/' /etc/avahi/avahi-daemon.conf
|
|
%end
|
|
''')
|
|
|
|
|
|
class KickstartHTTPServer(http.server.HTTPServer):
|
|
|
|
address_family = socket.AF_INET6
|
|
|
|
def __init__(self, kickstart):
|
|
port = random.randint(*PORT_RANGE)
|
|
super(KickstartHTTPServer, self).__init__(
|
|
server_address=('::', port),
|
|
RequestHandlerClass=KickstartHTTPRequestHandler,
|
|
)
|
|
self.kickstart = kickstart
|
|
|
|
|
|
class KickstartHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
|
|
|
def do_GET(self):
|
|
self.do_HEAD()
|
|
self.wfile.write(self.server.kickstart)
|
|
|
|
def do_HEAD(self):
|
|
self.send_response(200)
|
|
self.send_header('Content-Type', 'text/plain; charset=utf-8')
|
|
self.send_header('Content-Length', str(len(self.server.kickstart)))
|
|
self.end_headers()
|
|
|
|
|
|
def get_server_name():
|
|
tries = [
|
|
(socket.AF_INET6, '2001:db8::1'),
|
|
(socket.AF_INET, '203.0.113.1'),
|
|
]
|
|
addr = None
|
|
for af, remote in tries:
|
|
s = socket.socket(af, socket.SOCK_DGRAM)
|
|
try:
|
|
s.connect((remote, 0))
|
|
addr = s.getsockname()[0]
|
|
except:
|
|
pass
|
|
finally:
|
|
s.close()
|
|
if addr is None:
|
|
try:
|
|
addr = socket.gethostbyname(socket.gethostname())
|
|
except:
|
|
return socket.gethostname()
|
|
try:
|
|
return socket.gethostbyaddr(addr)[0]
|
|
except:
|
|
return addr
|
|
|
|
|
|
def get_ssh_pubkey():
|
|
dirname = os.path.expanduser('~/.ssh')
|
|
for t in ('ed25519', 'rsa', 'ecdsa'):
|
|
try:
|
|
with open(os.path.join(dirname, 'id_{}.pub'.format(t))) as f:
|
|
return f.read()
|
|
except OSError:
|
|
continue
|
|
return ''
|
|
|
|
|
|
def virt_install(args):
|
|
yield 'virt-install'
|
|
for key, value in args.items():
|
|
yield '--{}'.format(key)
|
|
if value is not None:
|
|
yield str(value)
|
|
|
|
|
|
def wait_for_host(host, port, timeout=300):
|
|
deadline = time.time() + timeout
|
|
while time.time() < deadline:
|
|
try:
|
|
sock = socket.create_connection((host, port))
|
|
except OSError:
|
|
pass
|
|
else:
|
|
sock.close()
|
|
return True
|
|
return False
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--name')
|
|
parser.add_argument('--ram', type=int, default=1536)
|
|
parser.add_argument('--vcpus', type=int, default=2)
|
|
parser.add_argument('--cpu', default='host')
|
|
parser.add_argument('--location', default=REPO_URL)
|
|
parser.add_argument('--disk-pool', default='default')
|
|
parser.add_argument('--disk-size', type=int, default=6)
|
|
parser.add_argument('--network', default='bridge=br0')
|
|
parser.add_argument('--sound', default='none')
|
|
parser.add_argument('--server-name')
|
|
parser.add_argument('--ip', default='dhcp')
|
|
parser.add_argument('--ssh-key')
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
|
|
if args.name:
|
|
name = args.name
|
|
else:
|
|
name = 'C7-{}'.format(binascii.hexlify(os.urandom(3)).decode())
|
|
hostname = '{}.local'.format(name.lower())
|
|
|
|
if args.ssh_key is None:
|
|
ssh_pubkey = get_ssh_pubkey()
|
|
else:
|
|
ssh_pubkey = args.ssh_key
|
|
|
|
kickstart = KICKSTART.substitute(
|
|
hostname=hostname,
|
|
ssh_pubkey=ssh_pubkey,
|
|
)
|
|
|
|
httpd = KickstartHTTPServer(kickstart.encode('utf-8'))
|
|
|
|
kcmdline = 'ip={ip} proxy={proxy} ks=http://{server}:{port}/'.format(
|
|
ip=args.ip,
|
|
proxy=HTTP_PROXY,
|
|
server=args.server_name or get_server_name(),
|
|
port=httpd.server_port,
|
|
)
|
|
|
|
cmd = list(virt_install({
|
|
'name': name,
|
|
'ram': args.ram,
|
|
'vcpus': args.vcpus,
|
|
'cpu': args.cpu,
|
|
'location': args.location,
|
|
'extra-args': kcmdline,
|
|
'os-variant': 'rhel7',
|
|
'disk': 'pool={pool},size={size}'.format(
|
|
pool=args.disk_pool,
|
|
size=args.disk_size,
|
|
),
|
|
'network': args.network,
|
|
'sound': args.sound,
|
|
'redirdev': 'none',
|
|
'noautoconsole': None,
|
|
'wait': -1,
|
|
}))
|
|
env = os.environ.copy()
|
|
env['http_proxy'] = HTTP_PROXY
|
|
p = subprocess.Popen(cmd, env=env)
|
|
httpd.handle_request()
|
|
httpd.server_close()
|
|
try:
|
|
p.wait()
|
|
except KeyboardInterrupt:
|
|
raise SystemExit(1)
|
|
print('Waiting for host to come up...')
|
|
if wait_for_host(hostname, 22):
|
|
os.execlp('ssh', 'ssh', '-oStrictHostKeyChecking=no',
|
|
'root@{}'.format(hostname))
|
|
else:
|
|
sys.stderr.write('Timed out waiting for {}\n'.format(hostname))
|
|
raise SystemExit(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|