Compare commits

..

8 commits

Author SHA1 Message Date
Asterisk
5bf806eea0 Merge branch 'fix-arch-repo' into 'main'
Handle repos better

See merge request blendOS/system-tools/akshara!8
2025-04-17 19:51:40 +00:00
askiiart
48ad492117
handle repos better
* add bootstrap repo
* add fallback for all repos
* make listing `https://` optional (http must be listed if applicable)
2025-04-16 23:47:40 -05:00
Asterisk
740d3efb5d Merge branch 'upstreaming-fixes' into 'main'
Upstreaming misc fixes

See merge request blendOS/system-tools/akshara!7
2025-04-09 02:39:00 +00:00
askiiart
53e7c32106
chore: format 2025-04-08 13:03:57 -05:00
askiiart
27da28ccca
add custom bootstrap mirror option 2025-04-08 13:03:36 -05:00
askiiart
cba45e457e
remove update on exit (with flag to not do that) 2025-04-08 12:50:25 -05:00
Asterisk
e14cc419a0 Merge branch 'fix-aur-broken' into 'main'
AUR packages work now

See merge request blendOS/system-tools/akshara!6
2024-10-09 18:01:15 +00:00
askiiart
19b90e85f4
AUR packages work now 2024-10-09 10:40:48 -05:00

165
akshara Executable file → Normal file
View file

@ -19,13 +19,17 @@
import os import os
import sys import sys
import time
import yaml import yaml
import shutil
import filecmp import filecmp
import argparse import argparse
import requests import requests
import platform
import fasteners import fasteners
from threading import Event
import subprocess import subprocess
from datetime import datetime
__version = '1.0.0' __version = '1.0.0'
@ -99,10 +103,11 @@ def error(err):
def interpret_track(blend_release): 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 if (type(result.get('impl')) == str and
type(result.get('track')) != 'custom'): type(result.get('track')) != 'custom'):
res = interpret_track(result) res = interpret_track(result)
for i in res.keys(): for i in res.keys():
@ -114,8 +119,6 @@ def interpret_track(blend_release):
def update_system(): def update_system():
benchmark = True
if os.path.isdir('/.update_rootfs'): if os.path.isdir('/.update_rootfs'):
error('update already downloaded, you must reboot first') error('update already downloaded, you must reboot first')
sys.exit(75) sys.exit(75)
@ -135,43 +138,60 @@ def update_system():
blend_release = yaml.load( blend_release = yaml.load(
blend_release_file, Loader=yaml.FullLoader) 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...') 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 # 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 # 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 arch_repo = blend_release.get("arch-repo")
bootstrap_mirror = 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 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') warn('failed download')
print() print()
info('trying download again...') info('trying download again...')
print() print()
exec('rm', '-f', '/.update.tar.zst') 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') error('failed download')
print() print()
error('update failed') error('update failed')
sys.exit(50) 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') error('failed checksum verification')
print() print()
info('trying download again...') info('trying download again...')
exec('rm', '-f', '/.update.tar.zst') 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') error('failed download')
print() print()
error('update failed') error('update failed')
sys.exit(50) sys.exit(50)
return 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') error('failed checksum verification')
print() print()
error('update failed') error('update failed')
@ -184,11 +204,7 @@ def update_system():
info('generating new system...') info('generating new system...')
before = datetime.now()
exec('tar', '--acls', '--xattrs', '-xf', '.update.tar.zst') 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('mv', 'root.x86_64', '.new_rootfs')
exec('rm', '-f', '/.new_rootfs/pkglist.x86_64.txt') exec('rm', '-f', '/.new_rootfs/pkglist.x86_64.txt')
exec('rm', '-f', '/.new_rootfs/version') exec('rm', '-f', '/.new_rootfs/version')
@ -208,10 +224,12 @@ def update_system():
'blend-files' 'blend-files'
] ]
persistent_files = [] keep = [
'/usr/share'
]
if (type(blend_release.get('impl')) == str and 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) res = interpret_track(blend_release)
for i in res.keys(): for i in res.keys():
@ -234,59 +252,66 @@ def update_system():
if type(blend_release.get('user-services')) == list: if type(blend_release.get('user-services')) == list:
user_services += blend_release.get('user-services') 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') exec_chroot('rm', '-f', '/.new_rootfs/etc/resolv.conf')
with open('/.new_rootfs/etc/resolv.conf', 'w') as pacman_mirrorlist_conf: with open('/.new_rootfs/etc/resolv.conf', 'w') as pacman_mirrorlist_conf:
pacman_mirrorlist_conf.write('nameserver 1.1.1.1\n') pacman_mirrorlist_conf.write('nameserver 1.1.1.1\n')
with open('/.new_rootfs/etc/pacman.d/mirrorlist', 'w') as pacman_mirrorlist_conf: 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(
pacman_mirrorlist_conf.write(f'Server = {blend_release.get("arch-repo")}/$repo/os/$arch\n') f'Server = {arch_repo}/$repo/os/$arch\n')
else:
pacman_mirrorlist_conf.write('Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch\n')
exec_chroot('mkdir', '-p', '/var/cache/pacman/pkg') exec_chroot('mkdir', '-p', '/var/cache/pacman/pkg')
exec_chroot('rm', '-rf', '/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 # update packages
exec_chroot('pacman-key', '--init') exec_chroot('pacman-key', '--init')
exec_chroot('pacman-key', '--populate') exec_chroot('pacman-key', '--populate')
# If the GitHub API is down or something, this completely breaks counter = 0
# also it's broken currently and reflector is working now anyways soooooo while True:
# commented out return_val = exec_chroot('pacman', '-Sy', '--ask=4', 'reflector')
#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 " ")') counter += 1
#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') 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('reflector', '--latest', '5', '--protocol', 'https',
#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') '--sort', 'rate', '--save', '/etc/pacman.d/mirrorlist')
with open('/.new_rootfs/etc/pacman.conf', 'r') as original: data = original.read() # exec_chroot('sed', 's/#//g', '-i', '/etc/pacman.d/mirrorlist')
with open('/.new_rootfs/etc/pacman.conf', 'w') as modified: modified.write(data.replace("[options]", "[options]\nParallelDownloads = 32\n")) # 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', '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', '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: with open('/.new_rootfs/etc/pacman.conf', 'a') as pacman_conf:
pacman_conf.write(f''' pacman_conf.write(f'''
[breakfast] [breakfast]
SigLevel = Never SigLevel = Never
Server = {blend_release['repo']} Server = {repo}
''') ''')
if type(blend_release.get('package-repos')) == list: if type(blend_release.get('package-repos')) == list:
for package_repo in blend_release.get('package-repos'): for package_repo in blend_release.get('package-repos'):
if (type(package_repo.get('name')) == str and 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''' pacman_conf.write(f'''
[{package_repo["name"]}] [{package_repo["name"]}]
SigLevel = Never SigLevel = Never
Server = {package_repo["repo-url"]} Server = {package_repo["repo-url"]}
''') ''')
before = datetime.now()
counter = 0 counter = 0
while True: while True:
return_val = exec_chroot('pacman', '-Syu', '--noconfirm') return_val = exec_chroot('pacman', '-Syu', '--noconfirm')
@ -296,16 +321,11 @@ Server = {package_repo["repo-url"]}
exit(50) exit(50)
if return_val == 0: if return_val == 0:
break 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') exec('cp', '/etc/mkinitcpio.conf', '/.new_rootfs/etc/mkinitcpio.conf')
before = datetime.now()
counter = 0 counter = 0
while True: while True:
print('running packages again')
return_val = exec_chroot('pacman', '-S', '--ask=4', *packages) return_val = exec_chroot('pacman', '-S', '--ask=4', *packages)
counter += 1 counter += 1
if counter > 30: if counter > 30:
@ -313,24 +333,18 @@ Server = {package_repo["repo-url"]}
exit(50) exit(50)
if return_val == 0: if return_val == 0:
break break
after = datetime.now()
if benchmark:
print(f'[BENCH]: {(before - after).seconds} seconds to install packages')
counter = 0 counter = 0
if aur_packages != []: if aur_packages != []:
while True: while True:
print('running aur_packages again') exec_chroot('useradd', '-m', '-G', 'wheel',
exec_chroot('useradd', '-m', '-G', 'wheel', '-s', '/bin/bash', 'aur') '-s', '/bin/bash', 'aur')
exec_chroot('bash', '-c', 'echo "aur ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/aur') exec_chroot(
before = datetime.now() 'bash', '-c', 'echo "aur ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/aur')
return_val = exec_chroot( return_val = exec_chroot(
'runuser', '-u', 'aur', '--', 'paru', '-Sy', '--noconfirm', '--needed', 'runuser', '-u', 'aur', '--', 'paru', '-Sy', '--noconfirm', '--needed',
'--noprogressbar', '--skipreview', '--removemake', '--cleanafter', '--ask=4', '--noprogressbar', '--skipreview', '--removemake', '--cleanafter', '--ask=4',
*aur_packages) *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('userdel', '-r', 'aur')
exec_chroot('rm', '-f', '/etc/sudoers.d/aur') exec_chroot('rm', '-f', '/etc/sudoers.d/aur')
counter += 1 counter += 1
@ -340,7 +354,6 @@ Server = {package_repo["repo-url"]}
if return_val == 0: if return_val == 0:
break break
for service in services: for service in services:
if type(service) is str: if type(service) is str:
exec_chroot('systemctl', 'enable', service) exec_chroot('systemctl', 'enable', service)
@ -349,12 +362,6 @@ Server = {package_repo["repo-url"]}
if type(user_service) is str: if type(user_service) is str:
exec_chroot('systemctl', 'enable', '--global', user_service) 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: if type(blend_release.get('commands')) == list:
for command in blend_release.get('commands'): for command in blend_release.get('commands'):
if type(command) == str: if type(command) == str:
@ -405,7 +412,8 @@ Server = {package_repo["repo-url"]}
exec('cp', '-ax', '/var/lib', '/.new.var.lib') 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/' dir_name = '/.new.var.lib/'
for name in var_lib_diff.left_only: 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('cp', '-ax', '/.new_rootfs/var/lib/pacman', '/.new.var.lib/pacman')
exec('mv', '.new_rootfs', '.update_rootfs') exec('mv', '.new_rootfs', '.update_rootfs')
exec('cp', '-ax', '/.update_rootfs/etc', '/.update_rootfs/usr/etc')
new_boot_files = [] new_boot_files = []
@ -485,10 +494,8 @@ command_map = {'help': 'help',
'daemon': daemon} 'daemon': daemon}
parser.add_argument('command', choices=command_map.keys(), parser.add_argument('command', choices=command_map.keys(),
help=argparse.SUPPRESS) help=argparse.SUPPRESS)
parser.add_argument('pkg', action='store', type=str, parser.add_argument('--keep-files-on-error',
nargs='*', help=argparse.SUPPRESS) action='store_true', help="keep working files on error")
parser.add_argument('--headless',
action='store_true', help=argparse.SUPPRESS)
parser.add_argument('-v', '--version', action='version', parser.add_argument('-v', '--version', action='version',
version=f'%(prog)s {__version}', help=argparse.SUPPRESS) version=f'%(prog)s {__version}', help=argparse.SUPPRESS)
@ -511,7 +518,8 @@ try:
parser.parse_args(['--version']) parser.parse_args(['--version'])
elif command == update_system: elif command == update_system:
exec('touch', '/var/lib/.akshara-system-lock') 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') info('attempting to acquire system lock')
with system_lock: with system_lock:
command() command()
@ -519,12 +527,11 @@ try:
command() command()
except: except:
error('aborting') error('aborting')
# remove update and akshara stuff if the program is exited # remove update and akshara stuff if the program errors (either exited by the user or an error) and is updating
exec('umount', '-rf', '/.new_rootfs/') if command == update_system and not args.keep_files_on_error:
exec('rmdir', '/.new_rootfs/') exec('umount', '-rf', '/.new_rootfs/')
exec('rm', '-rf', '/.new_rootfs') exec('rmdir', '/.new_rootfs/')
exec('rm', '-rf', '/.update_rootfs') exec('rm', '-rf', '/.update_rootfs')
# it's basically impossible to ^C before akshara has exited but after it's created this file exec('rm', '-f', '/.update')
# 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 else:
exec('rm', '-f', '/.update') print("--keep-files-on-error specified, not deleting files (/.new_rootfs/, /.update_rootfs/, /.update)")
# TODO: add similar handling for errors, and an option to not delete stuff on error/early exit