diff --git a/akshara b/akshara old mode 100755 new mode 100644 index 623c2c3..6bfa05b --- a/akshara +++ b/akshara @@ -19,13 +19,17 @@ import os import sys +import time import yaml +import shutil import filecmp import argparse import requests +import platform import fasteners +from threading import Event import subprocess -from datetime import datetime + __version = '1.0.0' @@ -99,10 +103,11 @@ def error(err): def interpret_track(blend_release): - result = yaml.safe_load(requests.get(blend_release.get('impl') + '/' + blend_release.get('track') + '.yaml', allow_redirects=True).content.decode()) + result = yaml.safe_load(requests.get(blend_release.get( + 'impl') + '/' + blend_release.get('track') + '.yaml', allow_redirects=True).content.decode()) if (type(result.get('impl')) == str and - type(result.get('track')) != 'custom'): + type(result.get('track')) != 'custom'): res = interpret_track(result) for i in res.keys(): @@ -114,8 +119,6 @@ def interpret_track(blend_release): def update_system(): - benchmark = True - if os.path.isdir('/.update_rootfs'): error('update already downloaded, you must reboot first') sys.exit(75) @@ -134,44 +137,61 @@ def update_system(): with open('/system.yaml') as blend_release_file: blend_release = yaml.load( blend_release_file, Loader=yaml.FullLoader) - - - # TODO: Add check that all packages actually exist + # TODO: Add check that all packages actually exist info('downloading Arch tarball...') - # TODO: currently it errors if it doesn't have arch-repo anyways, so this doesn't need any extra checking, maybe add a check for that later though # The mirror to use for downloading the bootstrap image # For example, for the Arch mirror at mirrors.acm.wpi.edu, you'd use https://mirrors.acm.wpi.edu/archlinux - # Not sure why this wouldn't just use `arch-repo` but whatever - bootstrap_mirror = blend_release.get("arch-repo") + arch_repo = blend_release.get("arch-repo") + if type(arch_repo) != str: + # default arch and bootstrap repo + arch_repo = "geo.mirror.pkgbuild.com" + + bootstrap_repo = blend_release.get("bootstrap-repo") + if type(bootstrap_repo) != str: + bootstrap_repo = arch_repo + + # TODO: default to https if http/https isn't listed + # keeping disabled for consistency because i can't find `repo` to add it (and to add a fallback) + if not (arch_repo.startswith("https://") or arch_repo.startswith("http://")): + arch_repo = "https://" + arch_repo + if not (bootstrap_repo.startswith("https://") or bootstrap_repo.startswith("http://")): + bootstrap_repo = "https://" + bootstrap_repo + + # same (fallback and https) for repo + repo = blend_release.get("repo") + if type(repo) != str: + repo = "https://pkg-repo.blendos.co" + elif not (repo.startswith("https://") or repo.startswith("http://")): + repo = "https://" + repo if not os.path.isfile('/.update.tar.zst'): - if exec('wget', '-q', '--show-progress', f'{bootstrap_mirror}/iso/latest/archlinux-bootstrap-x86_64.tar.zst', '-O', '/.update.tar.zst') != 0: + if exec('wget', '-q', '--show-progress', f'{bootstrap_repo}/iso/latest/archlinux-bootstrap-x86_64.tar.zst', '-O', '/.update.tar.zst') != 0: warn('failed download') print() info('trying download again...') print() exec('rm', '-f', '/.update.tar.zst') - if exec('wget', '-q', '--show-progress', f'{bootstrap_mirror}/iso/latest/archlinux-bootstrap-x86_64.tar.zst', '-O', '/.update.tar.zst') != 0: + if exec('wget', '-q', '--show-progress', f'{bootstrap_repo}/iso/latest/archlinux-bootstrap-x86_64.tar.zst', '-O', '/.update.tar.zst') != 0: error('failed download') print() error('update failed') sys.exit(50) - if exec('bash', '-c', f'sha256sum -c --ignore-missing <(wget -qO- {bootstrap_mirror}/iso/latest/sha256sums.txt | sed "s/archlinux-bootstrap-x86_64\\.tar\\.zst/.update.tar.zst/g") 2>/dev/null') != 0: + if exec('bash', '-c', f'sha256sum -c --ignore-missing <(wget -qO- {bootstrap_repo}/iso/latest/sha256sums.txt | sed "s/archlinux-bootstrap-x86_64\\.tar\\.zst/.update.tar.zst/g") 2>/dev/null') != 0: error('failed checksum verification') print() info('trying download again...') exec('rm', '-f', '/.update.tar.zst') - if exec('wget', '-q', '--show-progress', f'{bootstrap_mirror}/iso/latest/archlinux-bootstrap-x86_64.tar.zst', '-O', '/.update.tar.zst') != 0: + if exec('wget', '-q', '--show-progress', f'{bootstrap_repo}/iso/latest/archlinux-bootstrap-x86_64.tar.zst', '-O', '/.update.tar.zst') != 0: error('failed download') print() error('update failed') sys.exit(50) return - if exec('bash', '-c', f'sha256sum -c --ignore-missing <(wget -qO- {bootstrap_mirror}/iso/latest/sha256sums.txt | sed "s/archlinux-bootstrap-x86_64\\.tar\\.zst/.update.tar.zst/g") 2>/dev/null') != 0: + if exec('bash', '-c', f'sha256sum -c --ignore-missing <(wget -qO- {bootstrap_repo}/iso/latest/sha256sums.txt | sed "s/archlinux-bootstrap-x86_64\\.tar\\.zst/.update.tar.zst/g") 2>/dev/null') != 0: error('failed checksum verification') print() error('update failed') @@ -184,11 +204,7 @@ def update_system(): info('generating new system...') - before = datetime.now() exec('tar', '--acls', '--xattrs', '-xf', '.update.tar.zst') - after = datetime.now() - if benchmark: - print(f'[BENCH]: {(before - after).seconds} seconds to extract tarball') exec('mv', 'root.x86_64', '.new_rootfs') exec('rm', '-f', '/.new_rootfs/pkglist.x86_64.txt') exec('rm', '-f', '/.new_rootfs/version') @@ -208,10 +224,12 @@ def update_system(): 'blend-files' ] - persistent_files = [] + keep = [ + '/usr/share' + ] if (type(blend_release.get('impl')) == str and - type(blend_release.get('track')) != 'custom'): + type(blend_release.get('track')) != 'custom'): res = interpret_track(blend_release) for i in res.keys(): @@ -234,59 +252,66 @@ def update_system(): if type(blend_release.get('user-services')) == list: user_services += blend_release.get('user-services') - if type(blend_release.get('persistent-files')) == list: - persistent_files += blend_release.get('persistent-files') - exec_chroot('rm', '-f', '/.new_rootfs/etc/resolv.conf') with open('/.new_rootfs/etc/resolv.conf', 'w') as pacman_mirrorlist_conf: pacman_mirrorlist_conf.write('nameserver 1.1.1.1\n') with open('/.new_rootfs/etc/pacman.d/mirrorlist', 'w') as pacman_mirrorlist_conf: - if type(blend_release.get('arch-repo')) == str: - pacman_mirrorlist_conf.write(f'Server = {blend_release.get("arch-repo")}/$repo/os/$arch\n') - else: - pacman_mirrorlist_conf.write('Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch\n') + pacman_mirrorlist_conf.write( + f'Server = {arch_repo}/$repo/os/$arch\n') exec_chroot('mkdir', '-p', '/var/cache/pacman/pkg') exec_chroot('rm', '-rf', '/var/cache/pacman/pkg') - exec('cp', '-r', '/var/cache/pacman/pkg', '/.new_rootfs/var/cache/pacman') + exec('cp', '-r', '/var/cache/pacman/pkg', + '/.new_rootfs/var/cache/pacman') # update packages exec_chroot('pacman-key', '--init') exec_chroot('pacman-key', '--populate') - # If the GitHub API is down or something, this completely breaks - # also it's broken currently and reflector is working now anyways soooooo - # commented out - #exec_chroot('sh', '-c', 'mkdir /tmp/rate-mirrors/; cd /tmp/rate-mirrors/; curl -LO $(curl -s https://api.github.com/repos/westandskif/rate-mirrors/releases/latest | grep "browser_download_url.*rate-mirrors-v.*-x86_64-unknown-linux-musl.tar.gz" | cut -d : -f 2,3 | tr -d \\" | tr -d " ")') - #exec_chroot('bash', '-c', 'cd /tmp/rate-mirrors/; tar -xzf rate-mirrors*; cd $(find /tmp/rate-mirrors/ -mindepth 1 -maxdepth 1 -type d); ./rate_mirrors --disable-comments-in-file --entry-country=US --protocol=https arch --max-delay 7200 > /etc/pacman.d/mirrorlist') + counter = 0 + while True: + return_val = exec_chroot('pacman', '-Sy', '--ask=4', 'reflector') + counter += 1 + if counter > 30: + error('failed to download packages') + exit(50) + if return_val == 0: + break - #exec_chroot('sed', 's/#//g', '-i', '/etc/pacman.d/mirrorlist') - #exec_chroot('bash', '-c', 'grep "^Server =" /etc/pacman.d/mirrorlist > /etc/pacman.d/mirrorlist.tmp; mv /etc/pacman.d/mirrorlist.tmp /etc/pacman.d/mirrorlist') + exec_chroot('reflector', '--latest', '5', '--protocol', 'https', + '--sort', 'rate', '--save', '/etc/pacman.d/mirrorlist') - with open('/.new_rootfs/etc/pacman.conf', 'r') as original: data = original.read() - with open('/.new_rootfs/etc/pacman.conf', 'w') as modified: modified.write(data.replace("[options]", "[options]\nParallelDownloads = 32\n")) - with open('/.new_rootfs/etc/pacman.conf', 'w') as modified: modified.write(data.replace("#[multilib]\n#Include = /etc/pacman.d/mirrorlist", "[multilib]\nInclude = /etc/pacman.d/mirrorlist")) + # exec_chroot('sed', 's/#//g', '-i', '/etc/pacman.d/mirrorlist') + # exec_chroot('bash', '-c', 'grep "^Server =" /etc/pacman.d/mirrorlist > /etc/pacman.d/mirrorlist.tmp; mv /etc/pacman.d/mirrorlist.tmp /etc/pacman.d/mirrorlist') + + with open('/.new_rootfs/etc/pacman.conf', 'r') as original: + data = original.read() + with open('/.new_rootfs/etc/pacman.conf', 'w') as modified: + modified.write(data.replace( + "[options]", "[options]\nParallelDownloads = 32\n")) + with open('/.new_rootfs/etc/pacman.conf', 'w') as modified: + modified.write(data.replace( + "#[multilib]\n#Include = /etc/pacman.d/mirrorlist", "[multilib]\nInclude = /etc/pacman.d/mirrorlist")) with open('/.new_rootfs/etc/pacman.conf', 'a') as pacman_conf: pacman_conf.write(f''' [breakfast] SigLevel = Never -Server = {blend_release['repo']} +Server = {repo} ''') if type(blend_release.get('package-repos')) == list: for package_repo in blend_release.get('package-repos'): if (type(package_repo.get('name')) == str and - type(package_repo.get('repo-url')) == str): + type(package_repo.get('repo-url')) == str): pacman_conf.write(f''' [{package_repo["name"]}] SigLevel = Never Server = {package_repo["repo-url"]} ''') - before = datetime.now() counter = 0 while True: return_val = exec_chroot('pacman', '-Syu', '--noconfirm') @@ -296,16 +321,11 @@ Server = {package_repo["repo-url"]} exit(50) if return_val == 0: break - after = datetime.now() - if benchmark: - print(f'[BENCH]: {(before - after).seconds} seconds to update packages') exec('cp', '/etc/mkinitcpio.conf', '/.new_rootfs/etc/mkinitcpio.conf') - before = datetime.now() counter = 0 while True: - print('running packages again') return_val = exec_chroot('pacman', '-S', '--ask=4', *packages) counter += 1 if counter > 30: @@ -313,24 +333,18 @@ Server = {package_repo["repo-url"]} exit(50) if return_val == 0: break - after = datetime.now() - if benchmark: - print(f'[BENCH]: {(before - after).seconds} seconds to install packages') - + counter = 0 if aur_packages != []: while True: - print('running aur_packages again') - exec_chroot('useradd', '-m', '-G', 'wheel', '-s', '/bin/bash', 'aur') - exec_chroot('bash', '-c', 'echo "aur ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/aur') - before = datetime.now() + exec_chroot('useradd', '-m', '-G', 'wheel', + '-s', '/bin/bash', 'aur') + exec_chroot( + 'bash', '-c', 'echo "aur ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/aur') return_val = exec_chroot( 'runuser', '-u', 'aur', '--', 'paru', '-Sy', '--noconfirm', '--needed', '--noprogressbar', '--skipreview', '--removemake', '--cleanafter', '--ask=4', *aur_packages) - after = datetime.now() - if benchmark: - print(f'[BENCH]: {(before - after).seconds} seconds to install AUR packages') exec_chroot('userdel', '-r', 'aur') exec_chroot('rm', '-f', '/etc/sudoers.d/aur') counter += 1 @@ -339,7 +353,6 @@ Server = {package_repo["repo-url"]} exit(50) if return_val == 0: break - for service in services: if type(service) is str: @@ -349,12 +362,6 @@ Server = {package_repo["repo-url"]} if type(user_service) is str: exec_chroot('systemctl', 'enable', '--global', user_service) - for persistent_file in persistent_files: - if type(persistent_file) is str: - if os.path.exists(persistent_file): - exec('mkdir', '-p', '/.new_rootfs/'+ os.path.dirname(persistent_file)) - exec('cp', persistent_file, '/.new_rootfs/'+ persistent_file) - if type(blend_release.get('commands')) == list: for command in blend_release.get('commands'): if type(command) == str: @@ -405,7 +412,8 @@ Server = {package_repo["repo-url"]} exec('cp', '-ax', '/var/lib', '/.new.var.lib') - var_lib_diff = filecmp.dircmp('/.new_rootfs/var/lib/', '/.new.var.lib/') + var_lib_diff = filecmp.dircmp( + '/.new_rootfs/var/lib/', '/.new.var.lib/') dir_name = '/.new.var.lib/' for name in var_lib_diff.left_only: @@ -431,6 +439,7 @@ Server = {package_repo["repo-url"]} exec('cp', '-ax', '/.new_rootfs/var/lib/pacman', '/.new.var.lib/pacman') exec('mv', '.new_rootfs', '.update_rootfs') + exec('cp', '-ax', '/.update_rootfs/etc', '/.update_rootfs/usr/etc') new_boot_files = [] @@ -485,10 +494,8 @@ command_map = {'help': 'help', 'daemon': daemon} parser.add_argument('command', choices=command_map.keys(), help=argparse.SUPPRESS) -parser.add_argument('pkg', action='store', type=str, - nargs='*', help=argparse.SUPPRESS) -parser.add_argument('--headless', - action='store_true', help=argparse.SUPPRESS) +parser.add_argument('--keep-files-on-error', + action='store_true', help="keep working files on error") parser.add_argument('-v', '--version', action='version', version=f'%(prog)s {__version}', help=argparse.SUPPRESS) @@ -511,7 +518,8 @@ try: parser.parse_args(['--version']) elif command == update_system: exec('touch', '/var/lib/.akshara-system-lock') - system_lock = fasteners.InterProcessLock('/var/lib/.akshara-system-lock') + system_lock = fasteners.InterProcessLock( + '/var/lib/.akshara-system-lock') info('attempting to acquire system lock') with system_lock: command() @@ -519,12 +527,11 @@ try: command() except: error('aborting') - # remove update and akshara stuff if the program is exited - exec('umount', '-rf', '/.new_rootfs/') - exec('rmdir', '/.new_rootfs/') - exec('rm', '-rf', '/.new_rootfs') - exec('rm', '-rf', '/.update_rootfs') - # it's basically impossible to ^C before akshara has exited but after it's created this file - # and i don't *think* that "update" would be destructive at all (unless it failed to run and would't boot), but i don't quite understand it soooo just to be on the safe side - exec('rm', '-f', '/.update') - # TODO: add similar handling for errors, and an option to not delete stuff on error/early exit + # remove update and akshara stuff if the program errors (either exited by the user or an error) and is updating + if command == update_system and not args.keep_files_on_error: + exec('umount', '-rf', '/.new_rootfs/') + exec('rmdir', '/.new_rootfs/') + exec('rm', '-rf', '/.update_rootfs') + exec('rm', '-f', '/.update') + else: + print("--keep-files-on-error specified, not deleting files (/.new_rootfs/, /.update_rootfs/, /.update)")