Merge pull request #3 from Torxed/net-deploy

Network deployment features added.
This commit is contained in:
Anton Hvornum 2018-05-24 20:30:46 +02:00 committed by GitHub
commit f853015448
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 403 additions and 86 deletions

View File

@ -5,25 +5,30 @@ Just a bare bone automated [Arch](https://wiki.archlinux.org/index.php/Arch_Linu
# cd ~/archlive # cd ~/archlive
# echo -e "git\npython-psutil" >> packages.both # echo -e "git\npython-psutil" >> packages.both
# echo "cd /root; git clone https://github.com/Torxed/archinstall.git" >> ./airootfs/root/customize_airootfs.sh # echo "cd /root" >> ./airootfs/root/customize_airootfs.sh
# echo "git clone https://github.com/Torxed/archinstall.git" >> ./airootfs/root/customize_airootfs.sh
# echo "cd archinstall; git checkout net-deploy; cd /root" >> ./airootfs/root/customize_airootfs.sh
# echo "chmod +x ~/archinstall/archinstall.py" >> ./airootfs/root/customize_airootfs.sh # echo "chmod +x ~/archinstall/archinstall.py" >> ./airootfs/root/customize_airootfs.sh
# mkdir ./airootfs/etc/skel # mkdir ./airootfs/etc/skel
# echo '[[ -z $DISPLAY && $XDG_VTNR -eq 1 ]] && sh -c ~/archinstall/archinstall.py' >> ./airootfs/etc/skel/.zprofile # echo '[[ -z $DISPLAY && $XDG_VTNR -eq 1 ]] && sh -c ~/archinstall/archinstall.py' >> ./airootfs/etc/skel/.zprofile
# rm -v work/build.make_* && ./build.sh -v # rm -v work*; ./build.sh -v
> Note: `~/archlive` might be different on your system, see [ArchISO#Setup](https://wiki.archlinux.org/index.php/archiso#Setup) for more info. > Note: `~/archlive` might be different on your system, see [ArchISO#Setup](https://wiki.archlinux.org/index.php/archiso#Setup) for more info.
Whenever this live-cd boots, from here on now - it'll run `archinstall.py`. Whenever this live-cd boots, from here on now - it'll run `archinstall.py` with the `net-deploy` branch.
> CAUTION: If no parameters are given, it will devour the first disk in your system (/dev/sda, /dev/nvme0n1p2 etc). > CAUTION: If no parameters are given, it will devour the first disk in your system (/dev/sda, /dev/nvme0n1 etc).
# Manually run it on a booted Live CD # Manually run it on a booted Live CD
# pacman -Sy git # wget https://raw.githubusercontent.com/Torxed/archinstall/net-deploy/archinstall.py
# git clone https://github.com/Torxed/archinstall.git # python3 archinstall.py
# python3 ./archinstall/archinstall.py
> Note: *(You could also wget the `archinstall.py` script and skip installing git)* # Rerunning a installation
# umount -R /mnt; cryptsetup close /dev/mapper/luksdev
# python3 ./archinstall/archinstall.py
> Note: This assumes `--post=stay` is set to avoid instant reboot at the end.
# Some parameters you can give it # Some parameters you can give it
@ -37,8 +42,11 @@ Whenever this live-cd boots, from here on now - it'll run `archinstall.py`.
Sets the starting location of the root partition Sets the starting location of the root partition
(TODO: /boot will take up space from 1MiB - <start>, make sure boot is no larger than 513MiB) (TODO: /boot will take up space from 1MiB - <start>, make sure boot is no larger than 513MiB)
--password=0000 (Default)
Which disk password to use, --password="<STDIN>" for prompt of password.
--pwfile=/tmp/diskpw (Default) --pwfile=/tmp/diskpw (Default)
Which file to use as the disk encryption password Which file to store the disk encryption password while sending it to cryptsetup
--hostname=Arcinstall (Default) --hostname=Arcinstall (Default)
Sets the hostname of the box Sets the hostname of the box
@ -54,7 +62,9 @@ Whenever this live-cd boots, from here on now - it'll run `archinstall.py`.
Adds an additional username to the system (default group Wheel) Adds an additional username to the system (default group Wheel)
--post=reboot (Default) --post=reboot (Default)
After a successful install, reboots into the system. After a successful install, reboots into the system. Use --post=stay to not reboot.
net-deployment structs support all these and more. Custom arguments with string formatting. See [deployments/workstation.json](https://github.com/Torxed/archinstall/blob/net-deploy/deployments/workstation.json) for examples.
## End note ## End note

View File

@ -1,15 +1,21 @@
#!/usr/bin/python3 #!/usr/bin/python3
import traceback
import psutil, os, re, struct, sys, json import psutil, os, re, struct, sys, json
import urllib.request, urllib.parse import urllib.request, urllib.parse, ssl
from glob import glob from glob import glob
#from select import epoll, EPOLLIN, EPOLLHUP #from select import epoll, EPOLLIN, EPOLLHUP
from socket import socket, inet_ntoa, AF_INET, AF_INET6, AF_PACKET from socket import socket, inet_ntoa, AF_INET, AF_INET6, AF_PACKET
from collections import OrderedDict as oDict from collections import OrderedDict as oDict
from subprocess import Popen, STDOUT, PIPE from subprocess import Popen, STDOUT, PIPE
from time import sleep
## FIXME: dependency checks (fdisk, lsblk etc)
rootdir_pattern = re.compile('^.*?/devices') rootdir_pattern = re.compile('^.*?/devices')
harddrives = oDict() harddrives = oDict()
deploy_target = 'https://raw.githubusercontent.com/Torxed/archinstall/net-deploy/deployments'
args = {} args = {}
positionals = [] positionals = []
for arg in sys.argv[1:]: for arg in sys.argv[1:]:
@ -41,19 +47,21 @@ def get_local_MACs():
macs[addr.address] = nic macs[addr.address] = nic
return macs return macs
def run(cmd, echo=False, *args, **kwargs): def run(cmd, echo=False, opts=None, *args, **kwargs):
#print('[!] {}'.format(cmd)) if not opts: opts = {}
if echo or 'debug' in opts:
print('[!] {}'.format(cmd))
handle = Popen(cmd, shell='True', stdout=PIPE, stderr=STDOUT, **kwargs) handle = Popen(cmd, shell='True', stdout=PIPE, stderr=STDOUT, **kwargs)
output = b'' output = b''
while handle.poll() is None: while handle.poll() is None:
data = handle.stdout.read() data = handle.stdout.read()
if len(data): if len(data):
if echo and 'flush': if echo or 'debug' in opts:
print(data.decode('UTF-8'), end='') print(data.decode('UTF-8'), end='')
# print(data.decode('UTF-8'), end='') # print(data.decode('UTF-8'), end='')
output += data output += data
data = handle.stdout.read() data = handle.stdout.read()
if echo: if echo or 'debug' in opts:
print(data.decode('UTF-8'), end='') print(data.decode('UTF-8'), end='')
output += data output += data
handle.stdout.close() handle.stdout.close()
@ -101,10 +109,11 @@ def grab_partitions(dev):
parts = oDict() parts = oDict()
o = run('lsblk -o name -J -b {dev}'.format(dev=dev)) o = run('lsblk -o name -J -b {dev}'.format(dev=dev))
r = json.loads(o) r = json.loads(o)
for part in r['blockdevices'][0]['children']: if len(r['blockdevices']) and 'children' in r['blockdevices'][0]:
parts[part['name'][len(drive_name):]] = { for part in r['blockdevices'][0]['children']:
# TODO: Grab partition info and store here? parts[part['name'][len(drive_name):]] = {
} # TODO: Grab partition info and store here?
}
return parts return parts
@ -130,35 +139,109 @@ def multisplit(s, splitters):
def grab_url_data(path): def grab_url_data(path):
safe_path = path[:path.find(':')+1]+''.join([item if item in ('/', '?', '=', '&') else urllib.parse.quote(item) for item in multisplit(path[path.find(':')+1:], ('/', '?', '=', '&'))]) safe_path = path[:path.find(':')+1]+''.join([item if item in ('/', '?', '=', '&') else urllib.parse.quote(item) for item in multisplit(path[path.find(':')+1:], ('/', '?', '=', '&'))])
response = urllib.request.urlopen(safe_path) ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode=ssl.CERT_NONE
response = urllib.request.urlopen(safe_path, context=ssl_context)
return response.read() return response.read()
def get_instructions(target):
instructions = {}
try:
instructions = grab_url_data('{}/{}.json'.format(deploy_target, target))
except urllib.error.HTTPError:
print('[N] No instructions found called: {}'.format(target))
return instructions
print('[N] Found net-deploy instructions called: {}'.format(target))
try:
instructions = json.loads(instructions.decode('UTF-8'), object_pairs_hook=oDict)
except:
print('[E] JSON instructions failed to load for {}'.format(target))
traceback.print_exc()
sleep(5)
return instructions
def merge_dicts(d1, d2, before=True, overwrite=False):
""" Merges d2 into d1 """
if before:
d1, d2 = d2.copy(), d1.copy()
overwrite = True
for key, val in d2.items():
if key in d1:
if type(d1[key]) in [dict, oDict] and type(d2[key]) in [dict, oDict]:
d1[key] = merge_dicts(d1[key] if not before else d2[key], d2[key] if not before else d1[key], before=before, overwrite=overwrite)
elif overwrite:
d1[key] = val
else:
d1[key] = val
return d1
if __name__ == '__main__': if __name__ == '__main__':
update_git() # Breaks and restarts the script if an update was found. update_git() # Breaks and restarts the script if an update was found.
update_drive_list() update_drive_list()
if not os.path.isdir('/sys/firmware/efi'):
print('[E] This script only supports UEFI-booted machines.')
exit(1)
## Setup some defaults (in case no command-line parameters or netdeploy-params were given)
if not 'drive' in args: args['drive'] = list(harddrives.keys())[0] # First drive found if not 'drive' in args: args['drive'] = list(harddrives.keys())[0] # First drive found
if not 'size' in args: args['size'] = '100%' if not 'size' in args: args['size'] = '100%'
if not 'start' in args: args['start'] = '513MiB' if not 'start' in args: args['start'] = '513MiB'
if not 'pwfile' in args: args['pwfile'] = '/tmp/diskpw' if not 'pwfile' in args: args['pwfile'] = '/tmp/diskpw'
if not 'hostname' in args: args['hostname'] = 'Arcinstall' if not 'hostname' in args: args['hostname'] = 'Arcinstall'
if not 'country' in args: args['country'] = 'SE' #all if not 'country' in args: args['country'] = 'SE' # 'all' if we don't want country specific mirrors.
if not 'packages' in args: args['packages'] = '' if not 'packages' in args: args['packages'] = '' # extra packages other than default
if not 'post' in args: args['post'] = 'reboot' if not 'post' in args: args['post'] = 'reboot'
if not 'password' in args: args['password'] = '0000' # Default disk passord, can be <STDIN> or a fixed string
## == If we got networking,
# Try fetching instructions for this box and execute them.
instructions = {}
if get_default_gateway_linux():
locmac = get_local_MACs()
if not len(locmac):
print('[N] No network interfaces - No net deploy.')
else:
for mac in locmac:
instructions = get_instructions(mac)
if 'args' in instructions:
## == Recursively fetch instructions if "include" is found under {args: ...}
while 'include' in instructions['args']:
includes = instructions['args']['include']
print('[!] Importing net-deploy target: {}'.format(includes))
del(instructions['args']['include'])
if type(includes) in (dict, list):
for include in includes:
instructions = merge_dicts(instructions, get_instructions(include), before=True)
else:
instructions = merge_dicts(instructions, get_instructions(includes), before=True)
## Update arguments if we found any
for key, val in instructions['args'].items():
args[key] = val
else:
print('[N] No gateway - No net deploy')
if args['password'] == '<STDIN>': args['password'] = input('Enter a disk (and root) password: ')
print(args) print(args)
if not os.path.isfile(args['pwfile']): if not os.path.isfile(args['pwfile']):
PIN = '0000' #PIN = '0000'
with open(args['pwfile'], 'w') as pw: with open(args['pwfile'], 'w') as pw:
pw.write(PIN) pw.write(args['password'])
else: #else:
## TODO: Convert to `rb` instead. # ## TODO: Convert to `rb` instead.
# We shouldn't discriminate \xfu from being a passwd phrase. # # We shouldn't discriminate \xfu from being a passwd phrase.
with open(args['pwfile'], 'r') as pw: # with open(args['pwfile'], 'r') as pw:
PIN = pw.read().strip() # PIN = pw.read().strip()
print() print()
print('[!] Disk PASSWORD is: {}'.format(PIN)) print('[!] Disk PASSWORD is: {}'.format(args['password']))
print() print()
print('[N] Setting up {drive}.'.format(**args)) print('[N] Setting up {drive}.'.format(**args))
# dd if=/dev/random of=args['drive'] bs=4096 status=progress # dd if=/dev/random of=args['drive'] bs=4096 status=progress
@ -170,37 +253,87 @@ if __name__ == '__main__':
o = run('parted -s {drive} set 1 boot on'.format(**args)) o = run('parted -s {drive} set 1 boot on'.format(**args))
o = run('parted -s {drive} mkpart primary {start} {size}'.format(**args)) o = run('parted -s {drive} mkpart primary {start} {size}'.format(**args))
first, second = grab_partitions(args['drive']).keys() args['paritions'] = grab_partitions(args['drive'])
o = run('mkfs.vfat -F32 {drive}{part1}'.format(**args, part1=first)) if len(args['paritions']) <= 0:
print('[E] No paritions were created on {drive}'.format(**args), o)
exit(1)
for index, part_name in enumerate(args['paritions']):
args['partition_{}'.format(index+1)] = part_name
o = run('mkfs.vfat -F32 {drive}{partition_1}'.format(**args))
if (b'mkfs.fat' not in o and b'mkfs.vfat' not in o) or b'command not found' in o:
print('[E] Could not setup {drive}{partition_1}'.format(**args), o)
exit(1)
# "--cipher sha512" breaks the shit. # "--cipher sha512" breaks the shit.
# TODO: --use-random instead of --use-urandom # TODO: --use-random instead of --use-urandom
print('[N] Adding encryption to {drive}{part2}.'.format(**args, part2=second)) print('[N] Adding encryption to {drive}{partition_2}.'.format(**args))
o = run('cryptsetup -q -v --type luks2 --pbkdf argon2i --hash sha512 --key-size 512 --iter-time 10000 --key-file {pwfile} --use-urandom luksFormat {drive}{part2}'.format(**args, part2=second)) o = run('cryptsetup -q -v --type luks2 --pbkdf argon2i --hash sha512 --key-size 512 --iter-time 10000 --key-file {pwfile} --use-urandom luksFormat {drive}{partition_2}'.format(**args))
if not o.decode('UTF-8').strip() == 'Command successful.': if not o.decode('UTF-8').strip() == 'Command successful.':
print('[E] Failed to setup disk encryption.') print('[E] Failed to setup disk encryption.', o)
exit(1) exit(1)
o = run('cryptsetup open {drive}{part2} luksdev --key-file {pwfile} --type luks2'.format(**args, part2=second)) o = run('cryptsetup open {drive}{partition_2} luksdev --key-file {pwfile} --type luks2'.format(**args))
o = run('file /dev/mapper/luksdev') # /dev/dm-0 o = run('file /dev/mapper/luksdev') # /dev/dm-0
if b'cannot open' in o: if b'cannot open' in o:
print('[E] Could not mount encrypted device.') print('[E] Could not mount encrypted device.', o)
exit(1) exit(1)
print('[N] Creating btrfs filesystem inside {drive}{partition_2}'.format(**args))
o = run('mkfs.btrfs /dev/mapper/luksdev') o = run('mkfs.btrfs /dev/mapper/luksdev')
if not b'UUID' in o:
print('[E] Could not setup btrfs filesystem.', o)
exit(1)
o = run('mount /dev/mapper/luksdev /mnt') o = run('mount /dev/mapper/luksdev /mnt')
print('[N] Reordering mirrors.')
os.makedirs('/mnt/boot') os.makedirs('/mnt/boot')
o = run('mount {drive}{part1} /mnt/boot'.format(**args, part1=first)) o = run('mount {drive}{partition_1} /mnt/boot'.format(**args))
o = run("wget 'https://www.archlinux.org/mirrorlist/?country={country}&protocol=https&ip_version=4&ip_version=6&use_mirror_status=on' -O /root/mirrorlist".format(**args))
o = run("sed -i 's/#Server/Server/' /root/mirrorlist") print('[N] Reordering mirrors.')
o = run('rankmirrors -n 6 /root/mirrorlist > /etc/pacman.d/mirrorlist') if 'mirrors' in args and args['mirrors'] and get_default_gateway_linux():
o = run("wget 'https://www.archlinux.org/mirrorlist/?country={country}&protocol=https&ip_version=4&ip_version=6&use_mirror_status=on' -O /root/mirrorlist".format(**args))
o = run("sed -i 's/#Server/Server/' /root/mirrorlist")
o = run('rankmirrors -n 6 /root/mirrorlist > /etc/pacman.d/mirrorlist')
pre_conf = {}
if 'pre' in instructions:
pre_conf = instructions['pre']
elif 'prerequisits' in instructions:
pre_conf = instructions['prerequisits']
## Prerequisit steps needs to NOT be executed in arch-chroot.
## Mainly because there's no root structure to chroot into.
## But partly because some configurations need to be done against the live CD.
## (For instance, modifying mirrors are done on LiveCD and replicated intwards)
for title in pre_conf:
print('[N] Network prerequisit step: {}'.format(title))
for command in pre_conf[title]:
raw_command = command
opts = pre_conf[title][raw_command] if type(pre_conf[title][raw_command]) in (dict, oDict) else {}
if len(opts):
if 'pass-args' in opts or 'format' in opts:
command = command.format(**args)
if 'pass-args' in opts:
del(opts['pass-args'])
elif 'format' in opts:
del(opts['format'])
else:
print('[-] Options: {}'.format(opts))
#print('[N] Command: {} ({})'.format(raw_command, opts))
o = run('{c}'.format(c=command), opts)
if type(conf[title][raw_command]) == bytes and len(conf[title][raw_command]) and not conf[title][raw_command] in o:
print('[W] Prerequisit step failed: {}'.format(o.decode('UTF-8')))
#print(o)
print('[N] Straping in packages.') print('[N] Straping in packages.')
o = run('pacman -Syy') o = run('pacman -Syy')
o = run('pacstrap /mnt base base-devel btrfs-progs efibootmgr nano wpa_supplicant dialog {packages}'.format(**args)) o = run('pacstrap /mnt base base-devel btrfs-progs efibootmgr nano wpa_supplicant dialog {packages}'.format(**args))
if not os.path.isdir('/mnt/etc'):
print('[E] Failed to strap in packages', o)
exit(1)
o = run('genfstab -pU /mnt >> /mnt/etc/fstab') o = run('genfstab -pU /mnt >> /mnt/etc/fstab')
with open('/mnt/etc/fstab', 'a') as fstab: with open('/mnt/etc/fstab', 'a') as fstab:
fstab.write('\ntmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0\n') # Redundant \n at the start? who knoes? fstab.write('\ntmpfs /tmp tmpfs defaults,noatime,mode=1777 0 0\n') # Redundant \n at the start? who knoes?
@ -211,17 +344,18 @@ if __name__ == '__main__':
#o = run('arch-chroot /mnt echo "{hostname}" > /etc/hostname'.format(**args)) #o = run('arch-chroot /mnt echo "{hostname}" > /etc/hostname'.format(**args))
#o = run("arch-chroot /mnt sed -i 's/#\(en_US\.UTF-8\)/\1/' /etc/locale.gen") #o = run("arch-chroot /mnt sed -i 's/#\(en_US\.UTF-8\)/\1/' /etc/locale.gen")
o = run("arch-chroot /mnt sh -c \"echo '{hostname}' > /etc/hostname\"".format(**args)) o = run("arch-chroot /mnt sh -c \"echo '{hostname}' > /etc/hostname\"".format(**args))
o = run("arch-chroot /mnt sh -c \"echo -n 'en_US.UTF-8' > /etc/locale.gen\"") o = run("arch-chroot /mnt sh -c \"echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen\"")
o = run("arch-chroot /mnt sh -c \"echo 'LANG=en_US.UTF-8' > /etc/locale.conf\"")
o = run('arch-chroot /mnt locale-gen') o = run('arch-chroot /mnt locale-gen')
o = run('arch-chroot /mnt chmod 700 /root') o = run('arch-chroot /mnt chmod 700 /root')
## == Passwords ## == Passwords
# o = run('arch-chroot /mnt usermod --password {} root'.format(PIN)) # o = run('arch-chroot /mnt usermod --password {} root'.format(args['password']))
# o = run("arch-chroot /mnt sh -c 'echo {pin} | passwd --stdin root'".format(pin='"{pin}"'.format(**args, pin=PIN)), echo=True) # o = run("arch-chroot /mnt sh -c 'echo {pin} | passwd --stdin root'".format(pin='"{pin}"'.format(**args, pin=args['password'])), echo=True)
o = run("arch-chroot /mnt sh -c \"echo 'root:{pin}' | chpasswd\"".format(**args, pin=PIN)) o = run("arch-chroot /mnt sh -c \"echo 'root:{pin}' | chpasswd\"".format(**args, pin=args['password']))
if 'user' in args: if 'user' in args:
o = run('arch-chroot /mnt useradd -m -G wheel {user}'.format(**args)) o = run('arch-chroot /mnt useradd -m -G wheel {user}'.format(**args))
o = run("arch-chroot /mnt sh -c \"echo '{user}:{pin}' | chpasswd\"".format(**args, pin=PIN)) o = run("arch-chroot /mnt sh -c \"echo '{user}:{pin}' | chpasswd\"".format(**args, pin=args['password']))
with open('/mnt/etc/mkinitcpio.conf', 'w') as mkinit: with open('/mnt/etc/mkinitcpio.conf', 'w') as mkinit:
## TODO: Don't replace it, in case some update in the future actually adds something. ## TODO: Don't replace it, in case some update in the future actually adds something.
@ -238,41 +372,38 @@ if __name__ == '__main__':
## For some reason, blkid and /dev/disk/by-uuid are not getting along well. ## For some reason, blkid and /dev/disk/by-uuid are not getting along well.
## And blkid is wrong in terms of LUKS. ## And blkid is wrong in terms of LUKS.
#UUID = run('blkid -s PARTUUID -o value {drive}{part2}'.format(**args, part2=second)).decode('UTF-8').strip() #UUID = run('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip()
UUID = run("ls -l /dev/disk/by-uuid/ | grep {basename}{part2} | awk '{awk}'".format(basename=os.path.basename(args['drive']), part2=second, awk='{print $9}')).decode('UTF-8').strip() UUID = run("ls -l /dev/disk/by-uuid/ | grep {basename}{partition_2} | awk '{{print $9}}'".format(basename=os.path.basename(args['drive']), **args)).decode('UTF-8').strip()
with open('/mnt/boot/loader/entries/arch.conf', 'w') as entry: with open('/mnt/boot/loader/entries/arch.conf', 'w') as entry:
entry.write('title Arch Linux\n') entry.write('title Arch Linux\n')
entry.write('linux /vmlinuz-linux\n') entry.write('linux /vmlinuz-linux\n')
entry.write('initrd /initramfs-linux.img\n') entry.write('initrd /initramfs-linux.img\n')
entry.write('options cryptdevice=UUID={UUID}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n'.format(UUID=UUID)) entry.write('options cryptdevice=UUID={UUID}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n'.format(UUID=UUID))
## == If we got networking, conf = {}
# Try fetching instructions for this box and execute them. if 'post' in instructions:
if get_default_gateway_linux(): conf = instructions['post']
locmac = get_local_MACs() elif not 'args' in instructions and len(instructions):
for mac in locmac: conf = instructions
try:
instructions = grab_url_data('https://raw.githubusercontent.com/Torxed/archinstall/net-deploy/deployments/{}.json'.format(mac))
except (urllib.error.HTTPError, urllib.error.URLError) as e:
print('[N] No instructions for this box on this mac: {}'.format(mac))
continue
#print('Decoding:', instructions)
instructions = json.loads(instructions.decode('UTF-8'), object_pairs_hook=oDict)
for title in instructions:
print('[N] Network Deploy: {}'.format(title))
for command in instructions[title]:
opts = instructions[title][command] if type(instructions[title][command]) in (dict, oDict) else {}
#print('[N] Command: {} ({})'.format(command, opts)) for title in conf:
o = run('arch-chroot /mnt {c}'.format(c=command), **opts) print('[N] Network Deploy: {}'.format(title))
if type(instructions[title][command]) == bytes and len(instructions[title][command]) and not instructions[title][command] in o: for command in conf[title]:
print('[W] Post install command failed: {}'.format(o.decode('UTF-8'))) raw_command = command
#print(o) opts = conf[title][command] if type(conf[title][command]) in (dict, oDict) else {}
if len(opts):
print('[-] Options: {}'.format(opts))
if 'pass-args' in opts and opts['pass-args']:
command = command.format(**args)
#print('[N] Command: {} ({})'.format(command, opts))
o = run('arch-chroot /mnt {c}'.format(c=command), opts)
if type(conf[title][raw_command]) == bytes and len(conf[title][raw_command]) and not conf[title][raw_command] in o:
print('[W] Post install command failed: {}'.format(o.decode('UTF-8')))
#print(o)
o = run('umount -R /mnt')
if args['post'] == 'reboot': if args['post'] == 'reboot':
o = run('umount -R /mnt')
o = run('reboot now') o = run('reboot now')
else: else:
print('Done. "reboot" when you\'re done tinkering.') print('Done. "umount -R /mnt; reboot" when you\'re done tinkering.')

View File

@ -0,0 +1,20 @@
{
"args" : {
"include" : "workstation",
"user" : "anton",
"password" : "1111",
"drive" : "/dev/sdb",
"post" : "don't reboot"
},
"post" : {
"Setup a basic virtual environment": {
"mkdir -p /home/{user}/virts" : {"pass-args" : true},
"qemu-img create -f qcow2 /home/{user}/virts/test_deploy.qcow2 4G" : {"pass-args" : true},
"chown -R {user}.{user} /home/{user}/virts" : {"pass-args" : true}
},
"Setup user" : {
"useradd -m -G wheel -s /bin/bash anton" : null,
"sh -c \"echo {user}:{password} | chpasswd\"" : {"pass-args" : true}
}
}
}

View File

@ -0,0 +1,21 @@
{
"args" : {
"password" : "0000"
},
"pre" : {
"sh -c \"echo -n -e '[arch_offline]\nSigLevel = Optional TrustAll\nServer = http://localmirror.lan/archlinux/os/${arch}' >> /etc/pacman.conf\"" : null
},
"post" : {
"Setup openssh": {
"pacman -Syy --noconfirm openssh" : null
},
"Setup OpenVPN": {
"pacman -Syy --noconfirm openvpn" : null
},
"Setup autostarts": {
"systemctl enable dhcpcd" : null,
"systemctl enable openssh" : null,
"systemctl enable openvpn-client@testclient" : null
}
}
}

View File

@ -0,0 +1,29 @@
{
"args" : {
"password" : "0000",
"post" : "stay"
},
"post" : {
"Installing DNS + Database": {
"pacman -Syy --noconfirm powerdns postgresql" : null
},
"Setup Database": {
"sh -c \"echo 'postgres:{pin}' | chpasswd\"" : null,
"su - postgres -c \"initdb --locale $LANG -E UTF8 -D '/var/lib/postgres/data'\"" : null,
"systemctl start postgresql" : null,
"su - postgres -c \"psql -c \\\"CREATE USER pdns WITH PASSWORD 'SomePassword';\\\"\"" : {"debug" : true}
},
"Setup DNS": {
"sh -c \"echo -e 'launch=gpgsql\ngpgsql-host=127.0.0.1\ngpgsql-user=pdns\ngpgsql-dbname=pdns\ngpgsql-password={PIN}' >> /etc/powerdns/pdns.conf\"" : null,
"psql -U pdns -d pdns -a -f /usr/share/doc/powerdns/schema.pgsql.sql" : null
},
"Install DNS Entries": {
},
"Setup autostarts": {
"systemctl enable dhcpcd" : null,
"systemctl enable postgresql" : null,
"systemctl enable powerdns" : null
}
}
}

View File

@ -0,0 +1,31 @@
{
"args" : {
"password" : "0001"
},
"post" : {
"Setup temp build env": {
"pacman -Syy --noconfirm git" : null,
"useradd -m -G wheel builder" : null,
"sed -i 's/# %wheel ALL=(ALL) NO/%wheel ALL=(ALL) NO/' /etc/sudoers" : null
},
"install lighttpd2-git": {
"git clone https://aur.archlinux.org/lighttpd2-git.git /home/builder/lighttpd2" : null,
"chown -R builder.builder /home/builder/lighttpd2" : null,
"su - builder -c \"(cd /home/builder/lighttpd2/; /usr/bin/makepkg -s --noconfirm)\"" : null,
"sh -c 'pacman -U --noconfirm /home/builder/lighttpd2/*.xz'" : null
},
"Remove temp build env": {
"rm -rf /home/builder/lighttpd2" : null,
"sed -i 's/%wheel ALL=(ALL) NO/# %wheel ALL=(ALL) NO/' /etc/sudoers" : null
},
"Create mirror": {
"mkdir -p /srv/http/archlinux/arch_offline/os/x86_64" : null,
"pacman --noconfirm --dbpath /tmp/ -Syu -w --cachedir /srv/http/archlinux/arch_offline/os/x86_64 base base-devel git python python-systemd awesome xorg-xinit xorg-server xterm nano screen sudo iptables mesa-libgl dhclient dnsmasq darkhttpd openssh sshfs openssl openvpn gcc openvpn rtorrent powerdns postgresql" : null,
"sh -c 'repo-add /srv/http/archlinux/arch_offline/os/x86_64/arch_offline.db.tar.gz /srv/http/archlinux/arch_offline/os/x86_64/*.pkg.tar.xz'" : null
},
"Setup autostarts": {
"systemctl enable dhcpcd" : null,
"systemctl enable lighttpd2" : null
}
}
}

37
deployments/default.json Normal file
View File

@ -0,0 +1,37 @@
{
"args" : {
"password" : "<STDIN>",
"user" : "anton",
"_webbrowser" : "chromium",
"_window_manager" : "awesome",
"_keyboard_layout" : "sv-latin1"
},
"post" : {
"Install workstation packages": {
"pacman -Syy --noconfirm {_webbrowser} {_window_manager} openssh sshfs git dhclient ttf-freefont xorg-server xorg-xrandr xorg-xinit xterm nano wget pulseaudio pulseaudio-alsa pavucontrol smbclient cifs-utils xscreensaver" : null
},
"Enable autostarts": {
"systemctl enable dhcpcd" : null
},
"Setup desktop environment" : {
"sed -i 's/^twm &/#&/' /etc/X11/xinit/xinitrc" : null,
"sed -i 's/^xclock/#&/' /etc/X11/xinit/xinitrc" : null,
"sed -i 's/^xterm/#&/' /etc/X11/xinit/xinitrc" : null,
"sed -i 's/^exec xterm/#&/' /etc/X11/xinit/xinitrc" : null,
"sh -c \"echo 'setxkbmap se' >> /etc/X11/xinit/xinitrc\"" : null,
"sh -c \"echo 'xscreensaver -no-splash &' >> /etc/X11/xinit/xinitrc\"" : null,
"sh -c \"echo 'exec {_window_manager}' >> /etc/X11/xinit/xinitrc\"" : {"pass-args" : true},
"sh -c \"echo 'KEYMAP={_keyboard_layout}\nFONT=lat9w-16' >> /etc/vconsole.conf\"" : {"pass-args" : true},
"sh -c \"sed -i 's/{ \\\"open terminal\\\", terminal/{ \\\"Chromium\\\", \\\"chromium\\\" },\n &1/' /etc/xdg/awesome/rc.lua\"" : null,
"sh -c \"sed -i 's/{ \\\"open terminal\\\", terminal/{ \\\"File handler\\\", \\\"nemo\\\" },\n &1/' /etc/xdg/awesome/rc.lua\"" : null,
"sh -c \"sed -i 's/^globalkeys = gears.table.join(/&\n awful.key({ modkey, }, \\\"l\\\", function() awful.spawn(\\\"xscreensaver-command -lock &\\\") end),\n/' /etc/xdg/awesome/rc.lua\"" : null,
"sh -c \"awk -i inplace -v RS='' '{gsub(/awful.key\\({ modkey,.*?}, \\\"Tab\\\",.*?\\\"client\\\"}\\),/, \\\"awful.key({ modkey, }, \\\"Tab\\\",\\\n function ()\\\n awful.client.focus.byidx(-1)\\\n if client.focus then\\\n client.focus:raise()\\\n end\\\n end),\\\n awful.key({ modkey, \\\"Shift\\\" }, \\\"Tab\\\",\\\n function ()\\\n awful.client.focus.byidx(1)\\\n if client.focus then\\\n client.focus.raise()\\\n end\\\n end),\\\"); print}' /etc/xdg/awesome/rc.lua\"" : null,
"gsettings set org.nemo.desktop show-desktop-icons false" : null,
"xdg-mime default nemo.desktop inode/directory application/x-gnome-saved-search" : null
},
"Setup users" : {
"useradd -m -G wheel -s /bin/bash anton" : null,
"sh -c \"echo {user}:{password} | chpasswd\"" : {"pass-args" : true}
}
}
}

View File

@ -0,0 +1,35 @@
{
"args" : {
"password" : "<STDIN>",
"_mediaplayer" : "lollypop gstreamer gst-plugins-good gnome-keyring",
"_webbrowser" : "chromium",
"_window_manager" : "awesome",
"_keyboard_layout" : "sv-latin1",
"_virtulization" : "qemu ovmf",
"post" : "don't reboot"
},
"post" : {
"Install workstation packages": {
"pacman -Syy --noconfirm openssh sshfs git {_webbrowser} {_mediaplayer} {_window_manager} {_virtulization} dhclient ttf-freefont xorg-server xorg-xrandr xorg-xinit xterm nano wget pulseaudio pulseaudio-alsa pavucontrol smbclient cifs-utils xscreensaver" : {"pass-args" : true}
},
"Setup virtulization" : {
"sh -c \"Description=\\\"Bridge for virtual machines\\\"\nInterface=br0\nConnection=bridge\nBindsToInterfaces=(eno1)\nIP=no\nExecUpPost=\\\"ip link set dev br0 address $(cat /sys/class/net/eno1/address); IP=dhcp; ip_set\\\"\nExecDownPre=\\\"IP=dhcp\\\"\n\n## Ignore (R)STP and immediately activate the bridge\nSkipForwardingDelay=yes\"" : null
},
"Setup desktop environment" : {
"sed -i 's/^twm &/#&/' /etc/X11/xinit/xinitrc" : null,
"sed -i 's/^xclock/#&/' /etc/X11/xinit/xinitrc" : null,
"sed -i 's/^xterm/#&/' /etc/X11/xinit/xinitrc" : null,
"sed -i 's/^exec xterm/#&/' /etc/X11/xinit/xinitrc" : null,
"sh -c \"echo 'setxkbmap se' >> /etc/X11/xinit/xinitrc\"" : null,
"sh -c \"echo 'xscreensaver -no-splash &' >> /etc/X11/xinit/xinitrc\"" : null,
"sh -c \"echo 'exec {_window_manager}' >> /etc/X11/xinit/xinitrc\"" : {"pass-args" : true},
"sh -c \"echo 'KEYMAP={_keyboard_layout}\nFONT=lat9w-16' >> /etc/vconsole.conf\"" : {"pass-args" : true},
"sh -c \"sed -i 's/{ \\\"open terminal\\\", terminal/{ \\\"Chromium\\\", \\\"chromium\\\" },\n &1/' /etc/xdg/awesome/rc.lua\"" : null,
"sh -c \"sed -i 's/{ \\\"open terminal\\\", terminal/{ \\\"File handler\\\", \\\"nemo\\\" },\n &1/' /etc/xdg/awesome/rc.lua\"" : null,
"sh -c \"sed -i 's/^globalkeys = gears.table.join(/&\n awful.key({ modkey, }, \\\"l\\\", function() awful.spawn(\\\"xscreensaver-command -lock &\\\") end),\n/' /etc/xdg/awesome/rc.lua\"" : null,
"sh -c \"awk -i inplace -v RS='' '{gsub(/awful.key\\({ modkey,.*?}, \\\"Tab\\\",.*?\\\"client\\\"}\\),/, \\\"awful.key({ modkey, }, \\\"Tab\\\",\n function ()\n awful.client.focus.byidx(-1)\n if client.focus then\n client.focus:raise()\n end\n end),\n awful.key({ modkey, \\\"Shift\\\" }, \\\"Tab\\\",\n function ()\n awful.client.focus.byidx(1)\n if client.focus then\n client.focus.raise()\n end\n end),\\\"); print}' /etc/xdg/awesome/rc.lua\"" : null,
"gsettings set org.nemo.desktop show-desktop-icons false" : null,
"xdg-mime default nemo.desktop inode/directory application/x-gnome-saved-search" : null
}
}
}

View File

@ -1,17 +1,24 @@
#!/bin/bash #!/bin/bash
# offline_mirror_path - is used to temporarily store build AUR packages
# in order to "host" them to the build process.
# The path will be on the build machine, not inside the build itself.
work_dir=$1 work_dir=$1
arch=$2 arch=$2
offline_mirror_path="/tmp/aur_offline" offline_mirror_path="/tmp/aur_offline"
# A func to download, build ... # A func to download, build and host AUR packages to the ISO build process
build_aur () { build_aur () {
old_dir=`pwd` old_dir=`pwd`
package=$1 package=$1
# Prep with a build-user: # Prep with a build-user (removed at the end):
# TODO: Check if already exists, if so, randomize name/don't remove at the end.
# TODO: Don't give permission to wheel, give it only to this user (easy, but needs debugging first)
useradd -m -G wheel builder useradd -m -G wheel builder
sed -i 's/# %wheel ALL=(ALL) NO/%wheel ALL=(ALL) NO/' /etc/sudoers sed -i 's/# %wheel ALL=(ALL) NO/%wheel ALL=(ALL) NO/' /etc/sudoers
# Extract the AUR package.
cd /tmp cd /tmp
rm -rf ${package} ${package}.tar.gz rm -rf ${package} ${package}.tar.gz
wget "https://aur.archlinux.org/cgit/aur.git/snapshot/${package}.tar.gz" wget "https://aur.archlinux.org/cgit/aur.git/snapshot/${package}.tar.gz"
@ -24,18 +31,12 @@ build_aur () {
su - builder -c "(cd ${build_dir}; makepkg -s --noconfirm)" >/dev/null 2>&1 su - builder -c "(cd ${build_dir}; makepkg -s --noconfirm)" >/dev/null 2>&1
echo " => Adding ${package} to local AUR mirror" echo " => Adding ${package} to local AUR hosting directory ${offline_mirror_path}"
mkdir -p ${offline_mirror_path} mkdir -p ${offline_mirror_path}
sh -c "cp *.xz ${offline_mirror_path}/" sh -c "cp *.xz ${offline_mirror_path}/"
sh -c "repo-add ${offline_mirror_path}/aur_offline.db.tar.gz ${offline_mirror_path}/*.xz" sh -c "repo-add ${offline_mirror_path}/aur_offline.db.tar.gz ${offline_mirror_path}/*.xz"
if [[ -z $(cat ${old_dir}/packages.both | grep ${package}) ]]; then
# TODO: save a copy of ${old_dir}/packages.both ONCE, if it doesn't excist already.
# This in order to revert our AUR changes which will affect a re-build.
echo " => Adding ${package} to packages.both (from AUR)"
echo "${package}" >> ${old_dir}/packages.both
fi
## Long term storage inside the ISO? (if we want to install to disk, we need to pass this along) ## Long term storage inside the ISO? (if we want to install from CD to disk or host it to others)
# sh -c "mv *.xz ${old_dir}/$2/$1.pkg.tar.xz" # sh -c "mv *.xz ${old_dir}/$2/$1.pkg.tar.xz"
cd ${old_dir} cd ${old_dir}
@ -48,8 +49,10 @@ build_aur () {
echo "Starting to sync upstream changes to offline mirror." echo "Starting to sync upstream changes to offline mirror."
rm -rf /tmp/sync /tmp/local rm -rf /tmp/sync /tmp/local
echo " => Building AUR (Adding packages to packages.both as we go along)" echo " => Building AUR packages (found in packages.aur)"
build_aur "lighttpd2-git" for package in $(cat ${work_dir}/packages.aur); do
build_aur package
done
if [[ -z $(cat ${work_dir}/pacman.conf | grep '\[aur_offline\]') ]]; then if [[ -z $(cat ${work_dir}/pacman.conf | grep '\[aur_offline\]') ]]; then
echo " => Adding offline mirror to the chroot environment" echo " => Adding offline mirror to the chroot environment"