Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Hvornum <anton.feeds+github@gmail.com>2019-12-25 18:46:08 +0000
committerAnton Hvornum <anton.feeds+github@gmail.com>2019-12-25 18:46:08 +0000
commit906324026b17bd2ed507aefbaf5a634eab648bf9 (patch)
tree86f75b44c9c37dbd83e455d0918bc5dd1b20f6a4
parent2f90c03afd0bbf7c57ef2563149d281c9b3183d5 (diff)
parent00d0a29c491d5641d589bdacccdd0726c44f4951 (diff)
Cleanup
-rw-r--r--README.md9
-rw-r--r--archinstall.py883
-rw-r--r--deployments/00:11:22:33:44:55.json8
-rw-r--r--deployments/default.json19
-rw-r--r--deployments/gitea.json13
-rw-r--r--deployments/vmhost.json26
6 files changed, 638 insertions, 320 deletions
diff --git a/README.md b/README.md
index 2682979e..39a40bd8 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,9 @@ More options for the built ISO:
--drive=</dev/sdX>
Which drive to install arch on, if absent, the first disk under /dev/ is used
+
+ --minimal
+ Starts a minimal installation, and skips looking for profiles.
--size=100% (Default)
Sets the size of the root filesystem (btrfs)
@@ -75,8 +78,10 @@ More options for the built ISO:
--hostname=Arcinstall (Default)
Sets the hostname of the box
- --country=SE (Default)
+ --country=all (Default)
Default mirror allocation for fetching packages.
+ If network is found, archinstall will try to attempt and guess which country the
+ install originates from, basing it off GeoIP off your public IP (uses https://hvornu.se/ip/ for lookups)
--packages='' (Default)
Which additional packages to install, defaults to none.
@@ -88,7 +93,7 @@ More options for the built ISO:
--post=reboot (Default)
After a successful install, reboots into the system. Use --post=stay to not reboot.
- --default
+ --unattended
This parameter causes the installation script to install arch unattended on the first disk
--profile=<name>
diff --git a/archinstall.py b/archinstall.py
index 6b26059c..64c1a164 100644
--- a/archinstall.py
+++ b/archinstall.py
@@ -7,7 +7,7 @@ from glob import glob
from select import epoll, EPOLLIN, EPOLLHUP
from socket import socket, inet_ntoa, AF_INET, AF_INET6, AF_PACKET
from collections import OrderedDict as oDict
-from subprocess import Popen, STDOUT, PIPE
+from subprocess import Popen, STDOUT, PIPE, check_output
from random import choice
from string import ascii_uppercase, ascii_lowercase, digits
from hashlib import sha512
@@ -29,17 +29,6 @@ commandlog = []
worker_history = oDict()
instructions = oDict()
args = {}
-positionals = []
-for arg in sys.argv[1:]:
- if '--' == arg[:2]:
- if '=' in arg:
- key, val = [x.strip() for x in arg[2:].split('=')]
- else:
- key, val = arg[2:], True
- args[key] = val
- else:
- positionals.append(arg)
-
import logging
from systemd.journal import JournalHandler
@@ -156,7 +145,7 @@ signal.signal(signal.SIGINT, sig_handler)
def gen_uid(entropy_length=256):
return sha512(os.urandom(entropy_length)).hexdigest()
-def get_default_gateway_linux(*args, **kwargs):
+def get_default_gateway_linux(*positionals, **kwargs):
"""Read the default gateway directly from /proc."""
with open("/proc/net/route") as fh:
for line in fh:
@@ -193,7 +182,7 @@ def simple_command(cmd, opts=None, *positionals, **kwargs):
if not opts: opts = {}
if 'debug' in opts:
print('[!] {}'.format(cmd))
- handle = Popen(cmd, shell='True', stdout=PIPE, stderr=STDOUT, stdin=PIPE, **kwargs)
+ handle = Popen(cmd, shell='True', stdout=PIPE, stderr=STDOUT, stdin=PIPE)
output = b''
while handle.poll() is None:
data = handle.stdout.read()
@@ -235,9 +224,9 @@ class sys_command():#Thread):
if not self.cmd[0][0] == '/':
log('Worker command is not executed with absolute path, trying to find: {}'.format(self.cmd[0]), origin='spawn', level=5)
- o = sys_command('/usr/bin/which {}'.format(self.cmd[0]), emulate=False, hide_from_log=True)
+ o = check_output(['/usr/bin/which', self.cmd[0]])
log('This is the binary {} for {}'.format(o.decode('UTF-8'), self.cmd[0]), origin='spawn', level=5)
- self.cmd[0] = o.decode('UTF-8')
+ self.cmd[0] = o.decode('UTF-8').strip()
if not os.path.isdir(self.exec_dir):
os.makedirs(self.exec_dir)
@@ -257,7 +246,7 @@ class sys_command():#Thread):
yield line
def __repr__(self, *positionals, **kwargs):
- return self.trace_log.decode('UTF-8')
+ return f"{self.cmd, self.trace_log}"
def decode(self, fmt='UTF-8'):
return self.trace_log.decode(fmt)
@@ -292,12 +281,23 @@ class sys_command():#Thread):
if not self.pid: # Child process
# Replace child process with our main process
if not self.kwargs['emulate']:
- os.execv(self.cmd[0], self.cmd)
+ try:
+ os.execv(self.cmd[0], self.cmd)
+ except FileNotFoundError:
+ self.status = 'done'
+ log(f"{self.cmd[0]} does not exist.", origin='spawn', level=2)
+ self.exit_code = 1
+ return False
+
os.chdir(old_dir)
poller = epoll()
poller.register(child_fd, EPOLLIN | EPOLLHUP)
+ if 'events' in self.kwargs and 'debug' in self.kwargs:
+ print(f'[D] Using triggers for command: {self.cmd}')
+ print(json.dumps(self.kwargs['events']))
+
alive = True
last_trigger_pos = 0
while alive and not self.kwargs['emulate']:
@@ -310,16 +310,26 @@ class sys_command():#Thread):
break
if 'debug' in self.kwargs and self.kwargs['debug'] and len(output):
+ print(self.cmd[0], 'gave:', output.decode('UTF-8'))
log(self.cmd[0],'gave:', output.decode('UTF-8'), origin='spawn', level=4)
lower = output.lower()
broke = False
if 'events' in self.kwargs:
for trigger in list(self.kwargs['events']):
+ if type(trigger) != bytes:
+ original = trigger
+ trigger = bytes(original, 'UTF-8')
+ self.kwargs['events'][trigger] = self.kwargs['events'][original]
+ del(self.kwargs['events'][original])
+ if type(self.kwargs['events'][trigger]) != bytes:
+ self.kwargs['events'][trigger] = bytes(self.kwargs['events'][trigger], 'UTF-8')
+
if trigger.lower() in self.trace_log[last_trigger_pos:].lower():
trigger_pos = self.trace_log[last_trigger_pos:].lower().find(trigger.lower())
if 'debug' in self.kwargs and self.kwargs['debug']:
+ print(f"Writing to subprocess {self.cmd[0]}: {self.kwargs['events'][trigger].decode('UTF-8')}")
log(f"Writing to subprocess {self.cmd[0]}: {self.kwargs['events'][trigger].decode('UTF-8')}", origin='spawn', level=5)
last_trigger_pos = trigger_pos
@@ -358,6 +368,9 @@ class sys_command():#Thread):
else:
self.exit_code = 0
+ if 'ignore_errors' in self.kwargs:
+ self.exit_code = 0
+
if self.exit_code != 0:
log(f"{self.cmd[0]} did not exit gracefully, exit code {self.exit_code}.", origin='spawn', level=3)
log(self.trace_log.decode('UTF-8'), origin='spawn', level=3)
@@ -370,19 +383,40 @@ class sys_command():#Thread):
worker_history[self.worker_id] = self.dump()
- if 'dependency' in self.kwargs and self.exit_code == 0:
- ## If this had a dependency waiting,
- ## Start it since there's no hook for this yet, the worker has to spawn it's waiting workers.
- module = self.kwargs['dependency']['module']
- print(self.cmd[0],'fullfills a dependency:', module)
- dependency_id = self.kwargs['dependency']['id']
- dependencies[module][dependency_id]['fullfilled'] = True
- dependencies[module][dependency_id]['spawn'](*dependencies[module][dependency_id]['args'], **dependencies[module][dependency_id]['kwargs'], start_callback=_worker_started_notification)
+ if 'dependency' in self.kwargs:
+ pass # TODO: Not yet supported (steal it from archinstall_gui)
+ """
+ dependency = self.kwargs['dependency']
+ if type(dependency) == str:
+ # Dependency is a progress-string. Wait for it to show up.
+ while main and main.isAlive() and dependency not in progress or progress[dependency] is None:
+ time.sleep(0.25)
+ dependency = progress[dependency]
+
+ if type(dependency) == str:
+ log(f"{self.func} waited for progress {dependency} which never showed up. Aborting.", level=2, origin='worker', function='run')
+ self.ended = time.time()
+ self.status = 'aborted'
+ return None
+
+ while main and main.isAlive() and dependency.ended is None:
+ time.sleep(0.25)
+
+ print(' *** Dependency released for:', self.func)
+
+ if dependency.data is None or not main or not main.isAlive():
+ log('Dependency:', dependency.func, 'did not exit clearly. There for,', self.func, 'can not execute.', level=2, origin='worker', function='run')
+ self.ended = time.time()
+ self.status = 'aborted'
+ return None
+ """
if self.callback:
- self.callback(self, *self.args, **self.kwargs)
+ pass # TODO: Not yet supported (steal it from archinstall_gui)
+
+ #self.callback(self, *self.args, **self.kwargs)
-def get_drive_from_uuid(uuid):
+def get_drive_from_uuid(drive):
if len(harddrives) <= 0: raise ValueError("No hard drives to iterate in order to find: {}".format(uuid))
for drive in harddrives:
@@ -394,12 +428,12 @@ def get_drive_from_uuid(uuid):
return None
-def get_drive_from_part_uuid(partuuid):
+def get_drive_from_part_uuid(partuuid, *positionals, **kwargs):
if len(harddrives) <= 0: raise ValueError("No hard drives to iterate in order to find: {}".format(uuid))
for drive in harddrives:
- for partition in get_partitions(f'/dev/{drive}'):
- o = simple_command(f'blkid -s PARTUUID -o value /dev/{drive}')
+ for partition in get_partitions(f'/dev/{drive}', *positionals, **kwargs):
+ o = simple_command(f'blkid -s PARTUUID -o value /dev/{drive}{partition}')
if len(o) and o == partuuid:
return drive
@@ -463,7 +497,7 @@ def device_state(name, *positionals, **kwargs):
return
return True
-def get_partitions(dev):
+def get_partitions(dev, *positionals, **kwargs):
drive_name = os.path.basename(dev)
parts = oDict()
#o = b''.join(sys_command('/usr/bin/lsblk -o name -J -b {dev}'.format(dev=dev)))
@@ -502,7 +536,7 @@ def disk_info(drive, *positionals, **kwargs):
lkwargs = {**kwargs}
lkwargs['emulate'] = False # This is a emulate-safe function. Does not alter filesystem.
- info = json.loads(b''.join(sys_command(f'lsblk -J -o "NAME,SIZE,FSTYPE,LABEL" {drive}', *positionals, **lkwargs)).decode('UTF_8'))['blockdevices'][0]
+ info = json.loads(b''.join(sys_command(f'lsblk -J -o "NAME,SIZE,FSTYPE,LABEL" {drive}', *positionals, **lkwargs, hide_from_log=True)).decode('UTF_8'))['blockdevices'][0]
fileformats = []
labels = []
if 'children' in info: ## Might not be partitioned yet
@@ -522,7 +556,11 @@ def disk_info(drive, *positionals, **kwargs):
def cleanup_args():
for key in args:
- if args[key] == '<STDIN>': args[key] = input(f'Enter a value for {key}: ')
+ if args[key] == '<STDIN>':
+ if not args['unattended']:
+ args[key] = input(f'Enter a value for {key}: ')
+ else:
+ args[key] = random_string(32)
elif args[key] == '<RND_STR>': args[key] = random_string(32)
elif args[key] == '<YUBIKEY>':
args[key] = gen_yubikey_password()
@@ -552,11 +590,14 @@ def merge_in_includes(instructions, *positionals, **kwargs):
## Update arguments if we found any
for key, val in instructions['args'].items():
args[key] = val
+ if 'user_args' in kwargs:
+ for key, val in kwargs['user_args'].items():
+ args[key] = val
return instructions
-def update_drive_list(*args, **kwargs):
+def update_drive_list(*positionals, **kwargs):
# https://github.com/karelzak/util-linux/blob/f920f73d83f8fd52e7a14ec0385f61fab448b491/disk-utils/fdisk-list.c#L52
for path in glob('/sys/block/*/device'):
name = re.sub('.*/(.*?)/device', '\g<1>', path)
@@ -584,25 +625,39 @@ def human_disk_info(drive):
}
def close_disks():
+ o = simple_command('/usr/bin/umount -R /mnt/boot')
o = simple_command('/usr/bin/umount -R /mnt')
o = simple_command('/usr/bin/cryptsetup close /dev/mapper/luksdev')
-def format_disk(drive=None, start='512MiB', end='100%', emulate=False, *args, **kwargs):
+def format_disk(drive='drive', start='start', end='size', emulate=False, *positionals, **kwargs):
+ drive = args[drive]
+ start = args[start]
+ end = args[end]
if not drive:
raise ValueError('Need to supply a drive path, for instance: /dev/sdx')
- print(f'[N] Setting up {drive}.')
# dd if=/dev/random of=args['drive'] bs=4096 status=progress
# https://github.com/dcantrell/pyparted would be nice, but isn't officially in the repo's #SadPanda
- o = b''.join(sys_command(f'/usr/bin/parted -s {drive} mklabel gpt', emulate=emulate))
- o = b''.join(sys_command(f'/usr/bin/parted -s {drive} mkpart primary FAT32 1MiB {start}', emulate=emulate))
- o = b''.join(sys_command(f'/usr/bin/parted -s {drive} name 1 "EFI"', emulate=emulate))
- o = b''.join(sys_command(f'/usr/bin/parted -s {drive} set 1 esp on', emulate=emulate))
- o = b''.join(sys_command(f'/usr/bin/parted -s {drive} set 1 boot on', emulate=emulate))
- o = b''.join(sys_command(f'/usr/bin/parted -s {drive} mkpart primary {start} {end}', emulate=emulate))
- # TODO: grab paritions after each parted/partition step instead of guessing which partiton is which later on.
+ if sys_command(f'/usr/bin/parted -s {drive} mklabel gpt', emulate=emulate, *positionals, **kwargs).exit_code != 0:
+ return None
+ if sys_command(f'/usr/bin/parted -s {drive} mklabel gpt', emulate=emulate, *positionals, **kwargs).exit_code != 0:
+ return None
+ if sys_command(f'/usr/bin/parted -s {drive} mkpart primary FAT32 1MiB {start}', emulate=emulate, *positionals, **kwargs).exit_code != 0:
+ return None
+ if sys_command(f'/usr/bin/parted -s {drive} name 1 "EFI"', emulate=emulate, *positionals, **kwargs).exit_code != 0:
+ return None
+ if sys_command(f'/usr/bin/parted -s {drive} set 1 esp on', emulate=emulate, *positionals, **kwargs).exit_code != 0:
+ return None
+ if sys_command(f'/usr/bin/parted -s {drive} set 1 boot on', emulate=emulate, *positionals, **kwargs).exit_code != 0:
+ return None
+ if sys_command(f'/usr/bin/parted -s {drive} mkpart primary {start} {end}', emulate=emulate, *positionals, **kwargs).exit_code != 0:
+ return None
+
+ # TODO: grab partitions after each parted/partition step instead of guessing which partiton is which later on.
# Create one, grab partitions - dub that to "boot" or something. do the next partition, grab that and dub it "system".. or something..
# This "assumption" has bit me in the ass so many times now I've stoped counting.. Jerker is right.. Don't do it like this :P
+ return True
+
def multisplit(s, splitters):
s = [s,]
for key in splitters:
@@ -651,29 +706,37 @@ def get_application_instructions(target):
return instructions
-def get_instructions(target, *positionals, **kwargs):
+def get_local_instructions(target):
instructions = oDict()
- try:
- instructions = grab_url_data('{}/{}.json'.format(args['profiles-path'], target)).decode('UTF-8')
- print('[N] Found net-deploy instructions called: {}'.format(target))
- except urllib.error.HTTPError:
- print('[N] Could not find remote instructions. Trying local instructions under ./deployments')
- local_path = './deployments' if os.path.isfile('./archinstall.py') else './archinstall/deployments' # Dangerous assumption
- if os.path.isfile(f'{local_path}/{target}.json'):
- with open(f'{local_path}/{target}.json', 'r') as fh:
- instructions = fh.read()
+ local_path = './deployments' if os.path.isfile('./archinstall.py') else './archinstall/deployments' # Dangerous assumption
+ if os.path.isfile(f'{local_path}/{target}.json'):
+ with open(f'{local_path}/{target}.json', 'r') as fh:
+ instructions = fh.read()
- print('[N] Found local instructions called: {}'.format(target))
- else:
- print('[N] No instructions found called: {}'.format(target))
- return instructions
-
- try:
- instructions = json.loads(instructions, object_pairs_hook=oDict)
- except:
- print('[E] JSON syntax error in {}'.format('{}/{}.json'.format(args['profiles-path'], target)))
- traceback.print_exc()
- exit(1)
+ print('[N] Found local instructions called: {}'.format(target))
+ else:
+ print('[N] No instructions found called: {}'.format(target))
+ return instructions
+
+def get_instructions(target, *positionals, **kwargs):
+ instructions = oDict()
+ if get_default_gateway_linux():
+ try:
+ instructions = grab_url_data('{}/{}.json'.format(args['profiles-path'], target)).decode('UTF-8')
+ print('[N] Found net-deploy instructions called: {}'.format(target))
+ except urllib.error.HTTPError:
+ print('[N] Could not find remote instructions. Trying local instructions under ./deployments')
+ isntructions = get_local_instructions(target, *positionals)
+ else:
+ isntructions = get_local_instructions(target, *positionals)
+
+ if type(instructions) not in (dict, oDict,):
+ try:
+ instructions = json.loads(instructions, object_pairs_hook=oDict)
+ except:
+ print('[E] JSON syntax error in {}'.format('{}/{}.json'.format(args['profiles-path'], target)))
+ traceback.print_exc()
+ exit(1)
return instructions
@@ -697,54 +760,71 @@ def merge_dicts(d1, d2, before=True, overwrite=False):
def random_string(l):
return ''.join(choice(ascii_uppercase + ascii_lowercase + digits) for i in range(l))
-def setup_args_defaults(args, interactive=True):
+def phone_home(url):
+ payload = json.dumps({"hostname": args['hostname'],
+ "done" : time.time(),
+ "profile": args['profile'],
+ "drive": args['drive'],
+ "base_status": base_return_code}).encode('utf8')
+ request = urllib.request.Request(url, data=payload, headers={'content-type': 'application/json'})
+ response = urllib.request.urlopen(request)
+
+def get_external_ip(*positionals, **kwargs):
+ result = urllib.request.urlopen("https://hvornum.se/ip/?f=json").read().decode('UTF-8')
+ return json.loads(result)['ip']
+
+def guess_country(ip, *positionals, **kwargs):
+ # python-pygeoip
+ # geoip-database
+ result = None
+ GEOIP_DB = '/usr/share/GeoIP/GeoIP.dat'
+ if os.path.isfile(GEOIP_DB):
+ try:
+ import pygeoip
+ except:
+ ## TODO: Do a best-effort-guess based off the hostname given off the IP instead, if GoeIP doesn't exist.
+ return result
+
+ gi = pygeoip.GeoIP(GEOIP_DB)
+ result = gi.country_code_by_addr(ip)
+ else:
+ log(f'Missing GeoIP database: {GEOIP_DB}', origin='guess_country', level=LOG_LEVELS.ERROR)
+ return result
+
+def setup_args_defaults(args, *positionals, **kwargs):
if not 'size' in args: args['size'] = '100%'
+ if not 'mirrors' in args: args['mirrors'] = True
if not 'start' in args: args['start'] = '513MiB'
if not 'pwfile' in args: args['pwfile'] = '/tmp/diskpw'
if not 'hostname' in args: args['hostname'] = 'Archinstall'
- if not 'country' in args: args['country'] = 'SE' # 'all' if we don't want country specific mirrors.
if not 'packages' in args: args['packages'] = '' # extra packages other than default
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 not 'default' in args: args['default'] = False
+ if not 'minimal' in args: args['minimal'] = False
+ if not 'unattended' in args: args['unattended'] = False
if not 'profile' in args: args['profile'] = None
+ if not 'skip-encrypt' in args: args['skip-encrypt'] = False
if not 'profiles-path' in args: args['profiles-path'] = profiles_path
if not 'rerun' in args: args['rerun'] = None
+ if not 'aur-keep' in args: args['aur-keep'] = False
if not 'aur-support' in args: args['aur-support'] = True # Support adds yay (https://github.com/Jguer/yay) in installation steps.
if not 'ignore-rerun' in args: args['ignore-rerun'] = False
+ if not 'phone-home' in args: args['phone-home'] = False
+
+ # Setup locales if we didn't get one.
+ if not 'country' in args:
+ country = None
+ if get_default_gateway_linux():
+ ip = get_external_ip()
+ country = guess_country(ip)
+ args['country'] = 'all' if not country else country
if not 'localtime' in args: args['localtime'] = 'Europe/Stockholm' if args['country'] == 'SE' else 'GMT+0' # TODO: Arbitrary for now
- if not 'drive' in args:
- if interactive and len(harddrives):
- drives = sorted(list(harddrives.keys()))
- if len(drives) > 1 and 'force' not in args and ('default' in args and 'first-drive' not in args):
- for index, drive in enumerate(drives):
- print(f"{index}: {drive} ({harddrives[drive]['size'], harddrives[drive]['fstype'], harddrives[drive]['label']})")
- drive = input('Select one of the above disks (by number): ')
- if not drive.isdigit():
- raise KeyError("Multiple disks found, --drive=/dev/X not specified (or --force/--first-drive)")
- drives = [drives[int(drive)]] # Make sure only the selected drive is in the list of options
- args['drive'] = drives[0] # First drive found
- else:
- args['drive'] = None
- rerun = args['ignore-rerun']
-
- if args['drive'] and args['drive'][0] != '/':
- ## Remap the selected UUID to the device to be formatted.
- drive = get_drive_from_uuid(args['drive'])
- if not drive:
- print(f'[N] Could not map UUID "{args["drive"]}" to a device. Trying to match via PARTUUID instead!')
- drive = get_drive_from_part_uuid(args['drive'])
- if not drive:
- print(f'[E] Could not map UUID "{args["drive"]}" to a device. Aborting!')
- exit(1)
-
- args['drive'] = drive
return args
-def load_automatic_instructions(*args, **kwargs):
+def load_automatic_instructions(*positionals, **kwargs):
instructions = oDict()
- if get_default_gateway_linux(*args, **kwargs):
+ if get_default_gateway_linux(*positionals, **kwargs):
locmac = get_local_MACs()
if not len(locmac):
print('[N] No network interfaces - No net deploy.')
@@ -767,6 +847,9 @@ def load_automatic_instructions(*args, **kwargs):
## Update arguments if we found any
for key, val in instructions['args'].items():
args[key] = val
+ if 'user_args' in kwargs:
+ for key, val in kwargs['user_args'].items():
+ args[key] = val
else:
print('[N] No gateway - No net deploy')
@@ -778,232 +861,183 @@ def cache_diskpw_on_disk():
with open(args['pwfile'], 'w') as pw:
pw.write(args['password'])
-def refresh_partition_list(drive):
- args['paritions'] = get_partitions(drive)
-
-if __name__ == '__main__':
- update_git() # Breaks and restarts the script if an update was found.
- update_drive_list()
-
- ## Setup some defaults
- # (in case no command-line parameters or netdeploy-params were given)
- args = setup_args_defaults(args)
-
- ## == If we got networking,
- # Try fetching instructions for this box and execute them.
- instructions = load_automatic_instructions()
+def refresh_partition_list(drive, *positionals, **kwargs):
+ drive = args[drive]
+ if not 'partitions' in args:
+ args['partitions'] = oDict()
+ for index, part_name in enumerate(sorted(get_partitions(drive, *positionals, **kwargs).keys())):
+ args['partitions'][str(index+1)] = part_name
+ return True
- if args['profile'] and not args['default']:
- instructions = get_instructions(args['profile'])
- if len(instructions) <= 0:
- print('[E] No instructions by the name of {} was found.'.format(args['profile']))
- print(' Installation won\'t continue until a valid profile is given.')
- print(' (this is because --profile was given and a --default is not given)')
- exit(1)
- else:
- first = True
- while not args['default'] and not args['profile'] and len(instructions) <= 0:
- profile = input('What template do you want to install: ')
- instructions = get_instructions(profile)
- if first and len(instructions) <= 0:
- print('[E] No instructions by the name of {} was found.'.format(profile))
- print(' Installation won\'t continue until a valid profile is given.')
- print(' (this is because --default is not instructed and no --profile given)')
- first = False
+def mkfs_fat32(drive, partition, *positionals, **kwargs):
+ drive = args[drive]
+ partition = args['partitions'][partition]
+ o = b''.join(sys_command(f'/usr/bin/mkfs.vfat -F32 {drive}{partition}'))
+ if (b'mkfs.fat' not in o and b'mkfs.vfat' not in o) or b'command not found' in o:
+ return None
+ return True
- # TODO: Might not need to return anything here, passed by reference?
- instructions = merge_in_includes(instructions)
- cleanup_args()
+def is_luksdev_mounted(*positionals, **kwargs):
+ o = b''.join(sys_command('/usr/bin/file /dev/mapper/luksdev', hide_from_log=True)) # /dev/dm-0
+ if b'cannot open' in o:
+ return False
+ return True
- print(json.dumps(args, indent=4))
- if args['default'] and not 'force' in args:
- if(input('Are these settings OK? (No return beyond this point) N/y: ').lower() != 'y'):
- exit(1)
+def mount_luktsdev(drive, partition, keyfile, *positionals, **kwargs):
+ drive = args[drive]
+ partition = args['partitions'][partition]
+ keyfile = args[keyfile]
+ if not is_luksdev_mounted():
+ o = b''.join(sys_command(f'/usr/bin/cryptsetup open {drive}{partition} luksdev --key-file {keyfile} --type luks2'.format(**args)))
+ return is_luksdev_mounted()
+
+def encrypt_partition(drive, partition, keyfile='/tmp/diskpw', *positionals, **kwargs):
+ drive = args[drive]
+ partition = args['partitions'][partition]
+ keyfile = args[keyfile]
+ o = b''.join(sys_command(f'/usr/bin/cryptsetup -q -v --type luks2 --pbkdf argon2i --hash sha512 --key-size 512 --iter-time 10000 --key-file {keyfile} --use-urandom luksFormat {drive}{partition}'))
+ if not b'Command successful.' in o:
+ return False
+ return True
- cache_diskpw_on_disk()
- #else:
- # ## TODO: Convert to `rb` instead.
- # # We shouldn't discriminate \xfu from being a passwd phrase.
- # with open(args['pwfile'], 'r') as pw:
- # PIN = pw.read().strip()
+def mkfs_btrfs(drive='/dev/mapper/luksdev', *positionals, **kwargs):
+ o = b''.join(sys_command(f'/usr/bin/mkfs.btrfs -f {drive}'))
+ if not b'UUID' in o:
+ return False
+ return True
- print()
- print('[!] Disk PASSWORD is: {}'.format(args['password']))
- print()
+def mount_luksdev(where='/dev/mapper/luksdev', to='/mnt', *positionals, **kwargs):
+ check_mounted = simple_command('/usr/bin/mount | /usr/bin/grep /mnt', *positionals, **kwargs).decode('UTF-8').strip()# /dev/dm-0
+ if len(check_mounted):
+ return False
- if not args['rerun'] or args['ignore-rerun']:
- for i in range(5, 0, -1):
- print(f'Formatting {args["drive"]} in {i}...')
- time.sleep(1)
+ o = b''.join(sys_command('/usr/bin/mount /dev/mapper/luksdev /mnt', *positionals, **kwargs))
+ return True
- close_disks()
- format_disk(args['drive'], start=args['start'], end=args['size'])
-
- refresh_partition_list(args['drive'])
- print(f'Partitions: (Boot: {list(args["paritions"].keys())[0]})')
+def mount_part(drive, partition, mountpoint='/mnt', *positionals, **kwargs):
+ os.makedirs(mountpoint, exist_ok=True)
+ #o = b''.join(sys_command('/usr/bin/mount | /usr/bin/grep /mnt/boot', *positionals, **kwargs)) # /dev/dm-0
- if len(args['paritions']) <= 0:
- print('[E] No paritions were created on {drive}'.format(**args), o)
- exit(1)
- for index, part_name in enumerate(sorted(args['paritions'].keys())):
- args['partition_{}'.format(index+1)] = part_name
- print(f'Partition info: {part_name}')
- print(json.dumps(args['paritions'][part_name], indent=4))
+ check_mounted = simple_command(f'/usr/bin/mount | /usr/bin/grep {mountpoint}', *positionals, **kwargs).decode('UTF-8').strip()
+ if len(check_mounted):
+ return False
- if not args['rerun'] or args['ignore-rerun']:
- o = b''.join(sys_command('/usr/bin/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)
+ o = b''.join(sys_command(f'/usr/bin/mount {drive}{partition} {mountpoint}', *positionals, **kwargs))
+ return True
- # "--cipher sha512" breaks the shit.
- # TODO: --use-random instead of --use-urandom
- print('[N] Adding encryption to {drive}{partition_2}.'.format(**args))
- o = b''.join(sys_command('/usr/bin/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 b'Command successful.' in o:
- print('[E] Failed to setup disk encryption.', o)
- exit(1)
+def mount_boot(drive, partition, mountpoint='/mnt/boot', *positionals, **kwargs):
+ os.makedirs('/mnt/boot', exist_ok=True)
+ #o = b''.join(sys_command('/usr/bin/mount | /usr/bin/grep /mnt/boot', *positionals, **kwargs)) # /dev/dm-0
- o = b''.join(sys_command('/usr/bin/file /dev/mapper/luksdev')) # /dev/dm-0
- if b'cannot open' in o:
- o = b''.join(sys_command('/usr/bin/cryptsetup open {drive}{partition_2} luksdev --key-file {pwfile} --type luks2'.format(**args)))
- o = b''.join(sys_command('/usr/bin/file /dev/mapper/luksdev')) # /dev/dm-0
- if b'cannot open' in o:
- print('[E] Could not open encrypted device.', o)
- exit(1)
+ check_mounted = simple_command('/usr/bin/mount | /usr/bin/grep /mnt/boot', *positionals, **kwargs).decode('UTF-8').strip()
+ if len(check_mounted):
+ return False
- if not args['rerun'] or args['ignore-rerun']:
- print('[N] Creating btrfs filesystem inside {drive}{partition_2}'.format(**args))
- o = b''.join(sys_command('/usr/bin/mkfs.btrfs -f /dev/mapper/luksdev'))
- if not b'UUID' in o:
- print('[E] Could not setup btrfs filesystem.', o)
- exit(1)
+ o = b''.join(sys_command(f'/usr/bin/mount {drive}{partition} {mountpoint}', *positionals, **kwargs))
+ return True
- o = simple_command('/usr/bin/mount | /usr/bin/grep /mnt') # /dev/dm-0
- if len(o) <= 0:
- o = b''.join(sys_command('/usr/bin/mount /dev/mapper/luksdev /mnt'))
+def mount_mountpoints(drive, bootpartition, mountpoint='/mnt', *positionals, **kwargs):
+ drive = args[drive]
+ if args['skip-encrypt']:
+ mount_part(drive, args['partitions']["2"], mountpoint, *positionals, **kwargs)
+ else:
+ mount_luksdev(*positionals, **kwargs)
+ mount_boot(drive, args['partitions'][bootpartition], mountpoint=f'{mountpoint}/boot', *positionals, **kwargs)
+ return True
- os.makedirs('/mnt/boot', exist_ok=True)
- o = simple_command('/usr/bin/mount | /usr/bin/grep /mnt/boot') # /dev/dm-0
- if len(o) <= 0:
- o = b''.join(sys_command('/usr/bin/mount {drive}{partition_1} /mnt/boot'.format(**args)))
+def re_rank_mirrors(top=10, *positionals, **kwargs):
+ if sys_command(('/usr/bin/rankmirrors -n {top} /etc/pacman.d/mirrorlist > /etc/pacman.d/mirrorlist')).exit_code == 0:
+ return True
+ return False
+
+def filter_mirrors_by_country_list(countries, top=None, *positionals, **kwargs):
+ ## TODO: replace wget with urllib.request (no point in calling syscommand)
+ country_list = []
+ for country in countries.split(','):
+ country_list.append(f'country={country}')
+ o = b''.join(sys_command((f"/usr/bin/wget 'https://www.archlinux.org/mirrorlist/?{'&'.join(country_list)}&protocol=https&ip_version=4&ip_version=6&use_mirror_status=on' -O /root/mirrorlist")))
+ o = b''.join(sys_command(("/usr/bin/sed -i 's/#Server/Server/' /root/mirrorlist")))
+ o = b''.join(sys_command(("/usr/bin/mv /root/mirrorlist /etc/pacman.d/")))
+
+ if top:
+ re_rank_mirrors(top, *positionals, **kwargs) or not os.path.isfile('/etc/pacman.d/mirrorlist')
- if 'mirrors' in args and args['mirrors'] and 'country' in args and get_default_gateway_linux():
- print('[N] Reordering mirrors.')
- o = simple_command("/usr/bin/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 = simple_command("/usr/bin/sed -i 's/#Server/Server/' /root/mirrorlist")
- o = simple_command('/usr/bin/rankmirrors -n 6 /root/mirrorlist > /etc/pacman.d/mirrorlist')
+ return True
- pre_conf = {}
- if 'pre' in instructions:
- pre_conf = instructions['pre']
- elif 'prerequisits' in instructions:
- pre_conf = instructions['prerequisits']
+def strap_in_base(*positionals, **kwargs):
+ if args['aur-support']:
+ args['packages'] += ' git'
+ if sys_command('/usr/bin/pacman -Syy', *positionals, **kwargs).exit_code == 0:
+ x = sys_command('/usr/bin/pacstrap /mnt base base-devel linux linux-firmware btrfs-progs efibootmgr nano wpa_supplicant dialog {packages}'.format(**args), *positionals, **kwargs)
+ if x.exit_code == 0:
+ return True
+ return False
+
+def configure_base_system(*positionals, **kwargs):
+ ## TODO: Replace a lot of these syscalls with just python native operations.
+ o = b''.join(sys_command('/usr/bin/genfstab -pU /mnt >> /mnt/etc/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?
+
+ o = b''.join(sys_command('/usr/bin/arch-chroot /mnt rm -f /etc/localtime'))
+ o = b''.join(sys_command('/usr/bin/arch-chroot /mnt ln -s /usr/share/zoneinfo/{localtime} /etc/localtime'.format(**args)))
+ o = b''.join(sys_command('/usr/bin/arch-chroot /mnt hwclock --hctosys --localtime'))
+ #o = sys_command('arch-chroot /mnt echo "{hostname}" > /etc/hostname'.format(**args))
+ #o = sys_command("arch-chroot /mnt sed -i 's/#\(en_US\.UTF-8\)/\1/' /etc/locale.gen")
+ o = b''.join(sys_command("/usr/bin/arch-chroot /mnt sh -c \"echo '{hostname}' > /etc/hostname\"".format(**args)))
+ o = b''.join(sys_command("/usr/bin/arch-chroot /mnt sh -c \"echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen\""))
+ o = b''.join(sys_command("/usr/bin/arch-chroot /mnt sh -c \"echo 'LANG=en_US.UTF-8' > /etc/locale.conf\""))
+ o = b''.join(sys_command('/usr/bin/arch-chroot /mnt locale-gen'))
+ o = b''.join(sys_command('/usr/bin/arch-chroot /mnt chmod 700 /root'))
+
+ with open('/mnt/etc/mkinitcpio.conf', 'w') as mkinit:
+ ## TODO: Don't replace it, in case some update in the future actually adds something.
+ mkinit.write('MODULES=(btrfs)\n')
+ mkinit.write('BINARIES=(/usr/bin/btrfs)\n')
+ mkinit.write('FILES=()\n')
+ mkinit.write('HOOKS=(base udev autodetect modconf block encrypt filesystems keyboard fsck)\n')
+ o = b''.join(sys_command('/usr/bin/arch-chroot /mnt mkinitcpio -p linux'))
- if 'git-branch' in pre_conf:
- update_git(pre_conf['git-branch'])
- del(pre_conf['git-branch'])
+ return True
- ## 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))
- if args['rerun'] and args['rerun'] != title and not rerun:
- continue
+def setup_bootloader(*positionals, **kwargs):
+ o = b''.join(sys_command('/usr/bin/arch-chroot /mnt bootctl --no-variables --path=/boot install'))
+
+ with open('/mnt/boot/loader/loader.conf', 'w') as loader:
+ loader.write('default arch\n')
+ loader.write('timeout 5\n')
+
+ ## For some reason, blkid and /dev/disk/by-uuid are not getting along well.
+ ## And blkid is wrong in terms of LUKS.
+ #UUID = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip()
+ with open('/mnt/boot/loader/entries/arch.conf', 'w') as entry:
+ entry.write('title Arch Linux\n')
+ entry.write('linux /vmlinuz-linux\n')
+ entry.write('initrd /initramfs-linux.img\n')
+ if args['skip-encrypt']:
+ ## NOTE: We could use /dev/disk/by-partuuid but blkid does the same and a lot cleaner
+ UUID = simple_command(f"blkid -s PARTUUID -o value /dev/{os.path.basename(args['drive'])}{args['partitions']['2']}").decode('UTF-8').strip()
+ entry.write('options root=PARTUUID={UUID} rw intel_pstate=no_hwp\n'.format(UUID=UUID))
else:
- rerun = True
-
- 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)
- ## FIXME: Instead of deleting the two options
- ## in order to mute command output further down,
- ## check for a 'debug' flag per command and delete these two
- if 'pass-args' in opts:
- del(opts['pass-args'])
- elif 'format' in opts:
- del(opts['format'])
- elif 'debug' in opts and opts['debug']:
- print('[N] Complete command-string: '.format(command))
- else:
- print('[-] Options: {}'.format(opts))
-
- #print('[N] Command: {} ({})'.format(raw_command, opts))
- o = b''.join(sys_command('{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 b''.join(o):
- print('[W] Prerequisit step failed: {}'.format(b''.join(o).decode('UTF-8')))
- #print(o)
-
- if not args['rerun'] or rerun:
- print('[N] Straping in packages.')
- if args['aur-support']:
- args['packages'] += ' git'
- o = b''.join(sys_command('/usr/bin/pacman -Syy'))
- o = b''.join(sys_command('/usr/bin/pacstrap /mnt base base-devel linux linux-firmware btrfs-progs efibootmgr nano wpa_supplicant dialog {packages}'.format(**args)))
+ UUID = simple_command(f"ls -l /dev/disk/by-uuid/ | grep {os.path.basename(args['drive'])}{args['partitions']['2']} | awk '{{print $9}}'").decode('UTF-8').strip()
+ entry.write('options cryptdevice=UUID={UUID}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n'.format(UUID=UUID))
- if not os.path.isdir('/mnt/etc'): # TODO: This might not be the most long term stable thing to rely on...
- print('[E] Failed to strap in packages', o)
- exit(1)
+ return True
- if not args['rerun'] or rerun:
- o = b''.join(sys_command('/usr/bin/genfstab -pU /mnt >> /mnt/etc/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?
-
- o = b''.join(sys_command('/usr/bin/arch-chroot /mnt rm -f /etc/localtime'))
- o = b''.join(sys_command('/usr/bin/arch-chroot /mnt ln -s /usr/share/zoneinfo/{localtime} /etc/localtime'.format(**args)))
- o = b''.join(sys_command('/usr/bin/arch-chroot /mnt hwclock --hctosys --localtime'))
- #o = sys_command('arch-chroot /mnt echo "{hostname}" > /etc/hostname'.format(**args))
- #o = sys_command("arch-chroot /mnt sed -i 's/#\(en_US\.UTF-8\)/\1/' /etc/locale.gen")
- o = b''.join(sys_command("/usr/bin/arch-chroot /mnt sh -c \"echo '{hostname}' > /etc/hostname\"".format(**args)))
- o = b''.join(sys_command("/usr/bin/arch-chroot /mnt sh -c \"echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen\""))
- o = b''.join(sys_command("/usr/bin/arch-chroot /mnt sh -c \"echo 'LANG=en_US.UTF-8' > /etc/locale.conf\""))
- o = b''.join(sys_command('/usr/bin/arch-chroot /mnt locale-gen'))
- o = b''.join(sys_command('/usr/bin/arch-chroot /mnt chmod 700 /root'))
-
- with open('/mnt/etc/mkinitcpio.conf', 'w') as mkinit:
- ## TODO: Don't replace it, in case some update in the future actually adds something.
- mkinit.write('MODULES=(btrfs)\n')
- mkinit.write('BINARIES=(/usr/bin/btrfs)\n')
- mkinit.write('FILES=()\n')
- mkinit.write('HOOKS=(base udev autodetect modconf block encrypt filesystems keyboard fsck)\n')
- o = b''.join(sys_command('/usr/bin/arch-chroot /mnt mkinitcpio -p linux'))
- ## WORKAROUND: https://github.com/systemd/systemd/issues/13603#issuecomment-552246188
- o = b''.join(sys_command('/usr/bin/arch-chroot /mnt bootctl --no-variables --path=/boot install'))
-
- with open('/mnt/boot/loader/loader.conf', 'w') as loader:
- loader.write('default arch\n')
- loader.write('timeout 5\n')
-
- ## For some reason, blkid and /dev/disk/by-uuid are not getting along well.
- ## And blkid is wrong in terms of LUKS.
- #UUID = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip()
- UUID = simple_command("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:
- entry.write('title Arch Linux\n')
- entry.write('linux /vmlinuz-linux\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))
+def add_AUR_support(*positionals, **kwargs):
+ o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "useradd -m -G wheel aibuilder"'))
+ o = b''.join(sys_command("/usr/bin/sed -i 's/# %wheel ALL=(ALL) NO/%wheel ALL=(ALL) NO/' /mnt/etc/sudoers"))
+
+ o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "su - aibuilder -c \\"(cd /home/aibuilder; git clone https://aur.archlinux.org/yay.git)\\""'))
+ o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "chown -R aibuilder.aibuilder /home/aibuilder/yay"'))
+ o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "su - aibuilder -c \\"(cd /home/aibuilder/yay; makepkg -si --noconfirm)\\" >/dev/null"'))
+ ## Do not remove aibuilder just yet, can be used later for aur packages.
+ #o = b''.join(sys_command('/usr/bin/sed -i \'s/%wheel ALL=(ALL) NO/# %wheel ALL=(ALL) NO/\' /mnt/etc/sudoers'))
+ #o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "userdel aibuilder"'))
+ #o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "rm -rf /home/aibuilder"'))
+ return True
- if args['aur-support']:
- print('[N] AUR support demanded, building "yay" before running POST steps.')
- o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "useradd -m -G wheel aibuilder"'))
- o = b''.join(sys_command("/usr/bin/sed -i 's/# %wheel ALL=(ALL) NO/%wheel ALL=(ALL) NO/' /mnt/etc/sudoers"))
-
- o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "su - aibuilder -c \\"(cd /home/aibuilder; git clone https://aur.archlinux.org/yay.git)\\""'))
- o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "chown -R aibuilder.aibuilder /home/aibuilder/yay"'))
- o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "su - aibuilder -c \\"(cd /home/aibuilder/yay; makepkg -si --noconfirm)\\" >/dev/null"'))
- ## Do not remove aibuilder just yet, can be used later for aur packages.
- #o = b''.join(sys_command('/usr/bin/sed -i \'s/%wheel ALL=(ALL) NO/# %wheel ALL=(ALL) NO/\' /mnt/etc/sudoers'))
- #o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "userdel aibuilder"'))
- #o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "rm -rf /home/aibuilder"'))
- print('[N] AUR support added. use "yay -Syy --noconfirm <package>" to deploy in POST.')
-
+def run_post_install_steps(*positionals, **kwargs):
conf = {}
if 'post' in instructions:
conf = instructions['post']
@@ -1014,6 +1048,7 @@ if __name__ == '__main__':
update_git(conf['git-branch'])
del(conf['git-branch'])
+ rerun = args['ignore-rerun']
for title in conf:
if args['rerun'] and args['rerun'] != title and not rerun:
continue
@@ -1088,33 +1123,261 @@ if __name__ == '__main__':
## "<hostname> login" followed by "Passwodd" in case it's been set in a previous step.. usually this shouldn't be nessecary
## since we set the password as the last step. And then the command itself which will be executed by looking for:
## [root@<hostname> ~]#
- o = b''.join(sys_command('/usr/bin/systemd-nspawn -D /mnt -b --machine temporary', opts={'triggers' : {
- bytes(f'login:', 'UTF-8') : b'root\n',
- #b'Password:' : bytes(args['password']+'\n', 'UTF-8'),
- bytes(f'[root@{args["hostname"]} ~]#', 'UTF-8') : bytes(command+'\n', 'UTF-8'),
- }, **opts}))
+ defaults = {
+ 'login:' : 'root\n',
+ 'Password:' : args['password']+'\n',
+ '[root@{args["hostname"]} ~]#' : command+'\n',
+ }
+ if not 'events' in opts: opts['events'] = {}
+ events = {**defaults, **opts['events']}
+ del(opts['events'])
+ o = b''.join(sys_command('/usr/bin/systemd-nspawn -D /mnt -b --machine temporary', events=events, **opts))
## Not needed anymore: And cleanup after out selves.. Don't want to leave any residue..
# os.remove('/mnt/etc/systemd/system/console-getty.service.d/override.conf')
else:
- o = b''.join(sys_command('/usr/bin/systemd-nspawn -D /mnt --machine temporary {c}'.format(c=command), opts=opts))
+ o = b''.join(sys_command('/usr/bin/systemd-nspawn -D /mnt --machine temporary {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)
- if args['aur-support']:
- o = b''.join(sys_command('/usr/bin/sed -i \'s/%wheel ALL=(ALL) NO/# %wheel ALL=(ALL) NO/\' /mnt/etc/sudoers'))
- o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "userdel aibuilder"'))
- o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "rm -rf /home/aibuilder"'))
+if __name__ == '__main__':
+ ## Setup some defaults
+ # (in case no command-line parameters or netdeploy-params were given)
+ args = setup_args_defaults(args)
+ user_args = {}
+ positionals = []
+ for arg in sys.argv[1:]:
+ if '--' == arg[:2]:
+ if '=' in arg:
+ key, val = [x.strip() for x in arg[2:].split('=')]
+ else:
+ key, val = arg[2:], True
+ args[key] = val
+ user_args[key] = val
+ else:
+ positionals.append(arg)
+
+ update_git() # Breaks and restarts the script if an update was found.
+ update_drive_list()
+
+ ## == If we got networking,
+ # Try fetching instructions for this box unless a specific profile was given, and execute them.
+ if args['profile'] is None and not args['minimal']:
+ instructions = load_automatic_instructions(user_args=user_args)
+
+ elif args['profile'] and not args['minimal']:
+ instructions = get_instructions(args['profile'])
+ if len(instructions) <= 0:
+ print('[E] No instructions by the name of {} was found.'.format(args['profile']))
+ print(' Installation won\'t continue until a valid profile is given.')
+ print(' (this is because --profile was given and a --default is not given)')
+ exit(1)
+
+ first = True
+ while not args['minimal'] and not args['profile'] and len(instructions) <= 0:
+ profile = input('What template do you want to install: ')
+ instructions = get_instructions(profile)
+ if first and len(instructions) <= 0:
+ print('[E] No instructions by the name of {} was found.'.format(profile))
+ print(' Installation won\'t continue until a valid profile is given.')
+ print(' (this is because --default is not instructed and no --profile given)')
+ first = False
+
+ # TODO: Might not need to return anything here, passed by reference?
+ instructions = merge_in_includes(instructions, user_args=user_args)
+ cleanup_args()
+
+ ## If no drive was found in args, select one.
+ if not 'drive' in args:
+ if len(harddrives):
+ drives = sorted(list(harddrives.keys()))
+ if len(drives) > 1 and 'force' not in args and not 'unattended' in args and ('minimal' in args and 'first-drive' not in args):
+ for index, drive in enumerate(drives):
+ print(f"{index}: {drive} ({harddrives[drive]['size'], harddrives[drive]['fstype'], harddrives[drive]['label']})")
+ drive = input('Select one of the above disks (by number): ')
+ if not drive.isdigit():
+ raise KeyError("Multiple disks found, --drive=/dev/X not specified (or --force/--first-drive)")
+ drives = [drives[int(drive)]] # Make sure only the selected drive is in the list of options
+ args['drive'] = drives[0] # First drive found
+ else:
+ args['drive'] = None
+
+ if args['drive'] and args['drive'][0] != '/':
+ ## Remap the selected UUID to the device to be formatted.
+ drive = get_drive_from_uuid(args['drive'])
+ if not drive:
+ print(f'[N] Could not map UUID "{args["drive"]}" to a device. Trying to match via PARTUUID instead!')
+
+ drive = get_drive_from_part_uuid(args['drive'])
+ if not drive:
+ print(f'[E] Could not map UUID "{args["drive"]}" to a device. Aborting!')
+ exit(1)
+
+ args['drive'] = drive
+
+ print(json.dumps(args, indent=4))
+ if args['minimal'] and not 'force' in args and not 'unattended' in args:
+ if(input('Are these settings OK? (No return beyond this point) N/y: ').lower() != 'y'):
+ exit(1)
+
+ cache_diskpw_on_disk()
+ #else:
+ # ## TODO: Convert to `rb` instead.
+ # # We shouldn't discriminate \xfu from being a passwd phrase.
+ # with open(args['pwfile'], 'r') as pw:
+ # PIN = pw.read().strip()
+
+ print()
+ if not args['skip-encrypt']:
+ print('[!] Disk & root PASSWORD is: {}'.format(args['password']))
+ else:
+ print('[!] root PASSWORD is: {}'.format(args['password']))
+ print()
+
+ if not args['rerun'] or args['ignore-rerun']:
+ for i in range(5, 0, -1):
+ print(f'Formatting {args["drive"]} in {i}...')
+ time.sleep(1)
+
+ close_disks()
+ print(f'[N] Setting up {args["drive"]}.')
+ if not format_disk('drive', start='start', end='size', debug=True):
+ print(f'[E] Coult not format drive {args["drive"]}')
+ exit(1)
+
+ refresh_partition_list('drive')
+ print(f'[N] Partitions: {len(args["partitions"])} (Boot: {list(args["partitions"].keys())[0]})')
+
+ if len(args['partitions']) <= 0:
+ print(f'[E] No partitions were created on {args["drive"]}', o)
+ exit(1)
+
+ if not args['rerun'] or args['ignore-rerun']:
+ if not mkfs_fat32('drive', '1'):
+ print(f'[E] Could not setup {args["drive"]}{args["partitions"]["1"]}')
+ exit(1)
+
+ if not args['skip-encrypt']:
+ # "--cipher sha512" breaks the shit.
+ # TODO: --use-random instead of --use-urandom
+ print(f'[N] Adding encryption to {args["drive"]}{args["partitions"]["2"]}.')
+ if not encrypt_partition('drive', '2', 'pwfile'):
+ print('[E] Failed to setup disk encryption.', o)
+ exit(1)
+
+ if not args['skip-encrypt']:
+ if not mount_luktsdev('drive', '2', 'pwfile'):
+ print('[E] Could not open encrypted device.', o)
+ exit(1)
+
+ if not args['rerun'] or args['ignore-rerun']:
+ print(f'[N] Creating btrfs filesystem inside {args["drive"]}{args["partitions"]["2"]}')
+
+ on_part = '/dev/mapper/luksdev'
+ if args['skip-encrypt']:
+ on_part = f'{args["drive"]}{args["partitions"]["2"]}'
+ if not mkfs_btrfs(on_part):
+ print('[E] Could not setup btrfs filesystem.')
+ exit(1)
+
+ mount_mountpoints('drive', '1')
+
+ if 'mirrors' in args and args['mirrors'] and 'country' in args and get_default_gateway_linux():
+ print('[N] Reordering mirrors.')
+ filter_mirrors_by_country_list(args['country'])
+
+ pre_conf = {}
+ if 'pre' in instructions:
+ pre_conf = instructions['pre']
+ elif 'prerequisits' in instructions:
+ pre_conf = instructions['prerequisits']
+
+ if 'git-branch' in pre_conf:
+ update_git(pre_conf['git-branch'])
+ del(pre_conf['git-branch'])
+
+ rerun = args['ignore-rerun']
+
+ ## 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))
+ if args['rerun'] and args['rerun'] != title and not rerun:
+ continue
+ else:
+ rerun = True
+
+ 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)
+ ## FIXME: Instead of deleting the two options
+ ## in order to mute command output further down,
+ ## check for a 'debug' flag per command and delete these two
+ if 'pass-args' in opts:
+ del(opts['pass-args'])
+ elif 'format' in opts:
+ del(opts['format'])
+ elif 'debug' in opts and opts['debug']:
+ print('[N] Complete command-string: '.format(command))
+ else:
+ print('[-] Options: {}'.format(opts))
+
+ #print('[N] Command: {} ({})'.format(raw_command, opts))
+ o = b''.join(sys_command('{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 b''.join(o):
+ print('[W] Prerequisit step failed: {}'.format(b''.join(o).decode('UTF-8')))
+ #print(o)
+
+ if not args['rerun'] or rerun:
+ print('[N] Straping in packages.')
+ base_return_code = strap_in_base() # TODO: check return here? we return based off pacstrap exit code.. Never tired it tho.
+ else:
+ base_return_code = None
+
+ if not os.path.isdir('/mnt/etc'): # TODO: This might not be the most long term stable thing to rely on...
+ print('[E] Failed to strap in packages', o)
+ exit(1)
+
+ if not args['rerun'] or rerun:
+ print('[N] Configuring base system.')
+ configure_base_system()
+ ## WORKAROUND: https://github.com/systemd/systemd/issues/13603#issuecomment-552246188
+ print('[N] Setting up bootloader.')
+ setup_bootloader()
+
+ if args['aur-support']:
+ print('[N] AUR support demanded, building "yay" before running POST steps.')
+ add_AUR_support()
+ print('[N] AUR support added. use "yay -Syy --noconfirm <package>" to deploy in POST.')
## == Passwords
# o = sys_command('arch-chroot /mnt usermod --password {} root'.format(args['password']))
# o = sys_command("arch-chroot /mnt sh -c 'echo {pin} | passwd --stdin root'".format(pin='"{pin}"'.format(**args, pin=args['password'])), echo=True)
o = simple_command("/usr/bin/arch-chroot /mnt sh -c \"echo 'root:{pin}' | chpasswd\"".format(**args, pin=args['password']))
+ print(o)
+ time.sleep(5)
if 'user' in args:
o = ('/usr/bin/arch-chroot /mnt useradd -m -G wheel {user}'.format(**args))
o = ("/usr/bin/arch-chroot /mnt sh -c \"echo '{user}:{pin}' | chpasswd\"".format(**args, pin=args['password']))
+ print('[N] Running post installation steps.')
+ run_post_install_steps()
+ time.sleep(2)
+
+ if args['aur-support'] and not args['aur-keep']:
+ o = b''.join(sys_command('/usr/bin/sed -i \'s/%wheel ALL=(ALL) NO/# %wheel ALL=(ALL) NO/\' /mnt/etc/sudoers'))
+ o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "userdel aibuilder"'))
+ o = b''.join(sys_command('/usr/bin/arch-chroot /mnt sh -c "rm -rf /home/aibuilder"'))
+
+ if args['phone-home']:
+ phone_home(args['phone-home'])
+
if args['post'] == 'reboot':
o = simple_command('/usr/bin/umount -R /mnt')
o = simple_command('/usr/bin/reboot now')
diff --git a/deployments/00:11:22:33:44:55.json b/deployments/00:11:22:33:44:55.json
index e96fc01b..909b4256 100644
--- a/deployments/00:11:22:33:44:55.json
+++ b/deployments/00:11:22:33:44:55.json
@@ -6,14 +6,14 @@
"post" : "don't reboot"
},
"post" : {
+ "Setup user" : {
+ "useradd -m -G wheel -s /bin/bash anton" : null,
+ "sh -c \"echo {user}:{password} | chpasswd\"" : {"pass-args" : true}
+ },
"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}
}
}
}
diff --git a/deployments/default.json b/deployments/default.json
index 5deded3e..cd205f84 100644
--- a/deployments/default.json
+++ b/deployments/default.json
@@ -1,6 +1,17 @@
{
- "args" : {
- "password" : "<STDIN>",
- "post" : "reboot"
- }
+ "args" : {
+ "password" : "0000",
+ "post" : "stay"
+ },
+ "post" : {
+ "test exit codes" : {
+ "ssh test@77.80.220.176" : {"events" : {
+ "continue connecting" : "yes\n",
+ "s password" : "test\n"
+ },
+ "boot" : true,
+ "debug" : true
+ }
+ }
+ }
}
diff --git a/deployments/gitea.json b/deployments/gitea.json
new file mode 100644
index 00000000..efb4c15e
--- /dev/null
+++ b/deployments/gitea.json
@@ -0,0 +1,13 @@
+{
+ "args" : {
+ "password" : "<STDIN>",
+ "_editor" : "nano",
+ "_utils" : "openssh git curl dhclient",
+ "post" : "don't reboot"
+ },
+ "post" : {
+ "Install workstation packages": {
+ "pacman -Syy --noconfirm {_utils} {_editor}" : {"pass-args" : true}
+ }
+ }
+}
diff --git a/deployments/vmhost.json b/deployments/vmhost.json
new file mode 100644
index 00000000..0b2dabec
--- /dev/null
+++ b/deployments/vmhost.json
@@ -0,0 +1,26 @@
+{
+ "args" : {
+ "password" : "<STDIN>",
+ "_keyboard_layout" : "us",
+ "_editor" : "vim",
+ "_window_manager" : "i3",
+ "_window_manager_dependencies" : "xorg-server xorg-xrandr xorg-xinit xterm",
+ "_window_manager_utilities" : "slock xscreensaver terminus-font-otb gnu-free-fonts ttf-liberation xsel",
+ "_virtulization" : "qemu ovmf",
+ "_utils" : "git htop dhclient curl",
+ "post" : "don't reboot"
+ },
+ "post" : {
+ "Install workstation packages": {
+ "pacman -Syy --noconfirm {_editor} {_utils} {_window_manager} {_window_manager_dependencies} {_window_manager_utilities} {_virtulization}" : {"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 localization" : {
+ "sh -c \"echo 'setxkbmap us' >> /etc/X11/xinit/xinitrc\"" : null,
+ "sh -c \"echo 'KEYMAP={_keyboard_layout}\nFONT=lat9w-16' >> /etc/vconsole.conf\"" : {"pass-args" : true}
+ },
+ "Configure desktop environment" : "i3"
+ }
+}