WIP: Add rootfs building and flashing and other misc commands

This commit is contained in:
jld3103 2021-08-05 20:26:48 +02:00
parent 8886725971
commit 1f5357bca6
12 changed files with 410 additions and 11 deletions

View file

@ -4,7 +4,8 @@ RUN pacman -Syu --noconfirm \
python python-pip \
devtools rsync \
aarch64-linux-gnu-gcc aarch64-linux-gnu-binutils aarch64-linux-gnu-glibc aarch64-linux-gnu-linux-api-headers \
git
git \
android-tools
RUN sed -i "s/EUID == 0/EUID == -1/g" $(which makepkg)
@ -18,7 +19,6 @@ RUN yes | pacman -Scc
RUN sed -i "s/SigLevel.*/SigLevel = Never/g" /etc/pacman.conf
RUN sed -i "s|run_pacman |run_pacman --root \"/chroot/copy\" --arch aarch64 --config \"/app/src/pacman_copy.conf\" |g" $(which makepkg)
RUN echo "Server = http://mirror.archlinuxarm.org/\$arch/\$repo" > /etc/pacman.d/aarch64_mirrorlist
RUN mkdir -p /app/bin
RUN ln -sf $(which aarch64-linux-gnu-strip) /app/bin/strip

54
boot.py Normal file
View file

@ -0,0 +1,54 @@
import os
import urllib.request
from image import get_device_and_flavour, get_image_name
from logger import *
import click
import subprocess
FASTBOOT = 'fastboot'
JUMPDRIVE = 'jumpdrive'
jumpdrive_version = '0.8'
boot_strategies = {
'oneplus-enchilada': FASTBOOT,
'xiaomi-beryllium-ebbg': FASTBOOT,
'xiaomi-beryllium-tianma': FASTBOOT,
}
def dump_bootimg(image_name: str) -> str:
path = '/tmp/boot.img'
result = subprocess.run(['debugfs',
image_name,
'-R', f'dump /boot/boot.img {path}'])
if result.returncode != 0:
logging.fatal(f'Faild to dump boot.img')
exit(1)
return path
@click.command(name='boot', help=f'Leave TYPE empty or choose \'{JUMPDRIVE}\'')
@verbose_option
@click.argument('type', required=False)
def cmd_boot(verbose, type):
setup_logging(verbose)
device, flavour = get_device_and_flavour()
image_name = get_image_name(device, flavour)
strategy = boot_strategies[device]
if strategy == FASTBOOT:
if type == JUMPDRIVE:
file = f'boot-{device}.img'
path = os.path.join('/var/cache/jumpdrive', file)
urllib.request.urlretrieve(
f'https://github.com/dreemurrs-embedded/Jumpdrive/releases/download/{jumpdrive_version}/{file}', path)
else:
path = dump_bootimg(image_name)
result = subprocess.run(['fastboot', 'boot', path])
if result.returncode != 0:
logging.fatal(f'Failed to boot {path} using fastboot')
exit(1)

View file

@ -14,7 +14,7 @@ def cmd_cache():
def cmd_clean(verbose):
setup_logging(verbose)
for dir in ['/chroot', '/var/cache/pacman/pkg']:
for dir in ['/chroot', '/var/cache/pacman/pkg', '/var/cache/jumpdrive']:
for file in os.listdir(dir):
path = os.path.join(dir, file)
if os.path.isdir(path):

81
flash.py Normal file
View file

@ -0,0 +1,81 @@
from image import get_device_and_flavour, get_image_name
from boot import dump_bootimg
import os
import subprocess
import click
from logger import *
ROOTFS = 'rootfs'
BOOTIMG = 'bootimg'
EMMC = 'emmc'
EMMCFILE = 'emmc-file'
MICROSD = 'microsd'
locations = [EMMC, EMMCFILE, MICROSD]
@click.command(name='flash')
@verbose_option
@click.argument('what')
@click.argument('location', required=False)
def cmd_flash(verbose, what, location):
setup_logging(verbose)
device, flavour = get_device_and_flavour()
image_name = get_image_name(device, flavour)
if what == ROOTFS:
if location == None:
logging.info(f'You need to specify a location to flash {what} to')
exit(1)
if location not in locations:
logging.info(
f'Invalid location {location}. Choose one of {", ".join(locations)} for location')
exit(1)
dir = '/dev/disk/by-id'
for file in os.listdir(dir):
sanitized_file = file.replace('-', '').replace('_', '').lower()
if f'jumpdrive{location.split("-")[0]}' in sanitized_file:
path = os.path.realpath(os.path.join(dir, file))
result = subprocess.run(['lsblk',
path,
'-o', 'SIZE'],
capture_output=True)
if result.returncode != 0:
logging.info(f'Failed to lsblk {path}')
exit(1)
if result.stdout == b'SIZE\n 0B\n':
logging.info(
f'Disk {path} has a size of 0B. That probably means it is not available (e.g. no microSD inserted or no microSD card slot installed in the device) or corrupt or defect')
exit(1)
if location.endswith('-file'):
logging.fatal('Not implemented yet')
exit()
else:
result = subprocess.run(['dd',
f'if={image_name}',
f'of={path}',
'bs=20M',
'iflag=direct',
'oflag=direct',
'status=progress'])
if result.returncode != 0:
logging.info(f'Failed to flash {image_name} to {path}')
exit(1)
elif what == BOOTIMG:
result = subprocess.run(['fastboot', 'erase', 'dtbo'])
if result.returncode != 0:
logging.info(f'Failed to erase dtbo')
exit(1)
path = dump_bootimg(image_name)
result = subprocess.run(['fastboot', 'flash', 'boot', path])
if result.returncode != 0:
logging.info(f'Failed to flash boot.img')
exit(1)
else:
logging.fatal(f'Unknown what {what}')
exit(1)

38
forwarding.py Normal file
View file

@ -0,0 +1,38 @@
import click
import subprocess
from logger import *
@click.command(name='forwarding')
@verbose_option
def cmd_forwarding(verbose):
setup_logging(verbose)
result = subprocess.run(['sysctl', 'net.ipv4.ip_forward=1'])
if result.returncode != 0:
logging.fatal(f'Failed to enable ipv4 forward via sysctl')
exit(1)
result = subprocess.run(['iptables', '-P', 'FORWARD', 'ACCEPT'])
if result.returncode != 0:
logging.fatal(f'Failed set iptables rule')
exit(1)
result = subprocess.run(['iptables',
'-A', 'POSTROUTING',
'-t', 'nat',
'-j', 'MASQUERADE',
'-s', '172.16.42.0/24'])
if result.returncode != 0:
logging.fatal(f'Failed set iptables rule')
exit(1)
result = subprocess.run(['ssh',
'-o', 'GlobalKnownHostsFile=/dev/null',
'-o', 'UserKnownHostsFile=/dev/null',
'-o', 'StrictHostKeyChecking=no',
'-t', 'kupfer@172.16.42.1',
'sudo route add default gw 172.16.42.2'])
if result.returncode != 0:
logging.fatal(f'Failed to add gateway over ssh')
exit(1)

198
image.py Normal file
View file

@ -0,0 +1,198 @@
import atexit
from logging import root
import os
import shutil
import signal
import subprocess
import time
import click
from logger import *
devices = {
'oneplus-enchilada': ['sdm845-oneplus-enchilada'],
'xiaomi-beryllium-ebbg': ['sdm845-xiaomi-beryllium-ebbg'],
'xiaomi-beryllium-tianma': ['sdm845-xiaomi-beryllium-tianma'],
}
flavours = {
'barebone': [],
'phosh': [],
'plasma-mobile': [],
}
def get_device_and_flavour() -> tuple[str, str]:
if not os.path.exists('.device'):
logging.fatal(
f'Please set the device using \'kupferbootstrap image device ...\'')
exit(1)
if not os.path.exists('.flavour'):
logging.fatal(
f'Please set the flavour using \'kupferbootstrap image flavour ...\'')
exit(1)
with open('.device', 'r') as file:
device = file.read()
with open('.flavour', 'r') as file:
flavour = file.read()
return (device, flavour)
def get_image_name(device, flavour):
return f'{device}-{flavour}-rootfs.img'
def mount_rootfs_image(path):
rootfs_mount = '/mnt/kupfer/rootfs'
if not os.path.exists(rootfs_mount):
os.makedirs(rootfs_mount)
def umount():
subprocess.run(['umount', '-lc', rootfs_mount],
stderr=subprocess.DEVNULL)
atexit.register(umount)
result = subprocess.run(['mount',
'-o', 'loop',
path,
rootfs_mount])
if result.returncode != 0:
logging.fatal(f'Failed to loop mount {path} to {rootfs_mount}')
exit(1)
return rootfs_mount
@click.group(name='image')
def cmd_image():
pass
@click.command(name='device')
@verbose_option
@click.argument('device')
def cmd_device(verbose, device):
setup_logging(verbose)
for key in devices.keys():
if '-'.join(key.split('-')[1:]) == device:
device = key
break
if device not in devices:
logging.fatal(
f'Unknown device {device}. Pick one from:\n{", ".join(devices.keys())}')
exit(1)
logging.info(f'Setting device to {device}')
with open('.device', 'w') as file:
file.write(device)
@click.command(name='flavour')
@verbose_option
@click.argument('flavour')
def cmd_flavour(verbose, flavour):
setup_logging(verbose)
if flavour not in flavours:
logging.fatal(
f'Unknown flavour {flavour}. Pick one from:\n{", ".join(flavours.keys())}')
exit(1)
logging.info(f'Setting flavour to {flavour}')
with open('.flavour', 'w') as file:
file.write(flavour)
@click.command(name='build')
@verbose_option
def cmd_build(verbose):
setup_logging(verbose)
device, flavour = get_device_and_flavour()
image_name = get_image_name(device, flavour)
shutil.copyfile('/app/src/pacman.conf', '/app/src/pacman_copy.conf')
with open('/app/src/pacman_copy.conf', 'a') as file:
file.write(
'\n\n[main]\nServer = https://gitlab.com/kupfer/packages/prebuilts/-/raw/main/$repo')
file.write(
'\n\n[device]\nServer = https://gitlab.com/kupfer/packages/prebuilts/-/raw/main/$repo')
if not os.path.exists(image_name):
result = subprocess.run(['fallocate',
'-l', '4G',
image_name])
if result.returncode != 0:
logging.fatal(f'Failed to allocate {image_name}')
exit(1)
result = subprocess.run(['mkfs.ext4',
'-L', 'kupfer',
image_name])
if result.returncode != 0:
logging.fatal(f'Failed to create ext4 filesystem on {image_name}')
exit(1)
rootfs_mount = mount_rootfs_image(image_name)
result = subprocess.run(['pacstrap',
'-C', '/app/src/pacman_copy.conf',
'-c',
'-G',
rootfs_mount,
'base', 'base-kupfer']
+ devices[device]
+ flavours[flavour]
+ ['--needed', '--overwrite=*', '-yyuu'])
with open(os.path.join(rootfs_mount, 'install'), 'w') as file:
user = 'kupfer'
password = '123456'
groups = ['network', 'video', 'audio', 'optical', 'storage',
'input', 'scanner', 'games', 'lp', 'rfkill', 'wheel']
file.write('\n'.join([
f'if ! id -u "{user}" >/dev/null 2>&1; then',
f' useradd -m {user}',
f'fi',
f'usermod -a -G {",".join(groups)} {user}',
f'echo "{user}:{password}" | chpasswd',
f'chown {user}:{user} /home/{user} -R',
]))
result = subprocess.run(['arch-chroot',
rootfs_mount,
'/bin/bash', '/install'])
os.unlink(os.path.join(rootfs_mount, 'install'))
if result.returncode != 0:
logging.fatal('Failed to setup user')
exit(1)
"""
This doesn't work, because the mount isn't passed through to the real host
"""
"""
@click.command(name='inspect')
@verbose_option
def cmd_inspect(verbose):
setup_logging(verbose)
device, flavour = get_device_and_flavour()
image_name = get_image_name(device, flavour)
rootfs_mount = mount_rootfs_image(image_name)
logging.info(f'Inspect the rootfs image at {rootfs_mount}')
signal.pause()
"""
cmd_image.add_command(cmd_device)
cmd_image.add_command(cmd_flavour)
cmd_image.add_command(cmd_build)
# cmd_image.add_command(cmd_inspect)

10
main.py
View file

@ -1,5 +1,10 @@
from packages import cmd_packages
from cache import cmd_cache
from image import cmd_image
from boot import cmd_boot
from flash import cmd_flash
from ssh import cmd_ssh
from forwarding import cmd_forwarding
import click
@ -10,3 +15,8 @@ def cli():
cli.add_command(cmd_cache)
cli.add_command(cmd_packages)
cli.add_command(cmd_image)
cli.add_command(cmd_boot)
cli.add_command(cmd_flash)
cli.add_command(cmd_ssh)
cli.add_command(cmd_forwarding)

View file

@ -123,8 +123,6 @@ def setup_chroot():
file.write('\n\n[device]\nServer = file:///src/prebuilts/device')
shutil.copyfile('/app/src/pacman_copy.conf',
'/chroot/root/etc/pacman.conf')
shutil.copyfile('/etc/pacman.d/aarch64_mirrorlist',
'/chroot/root/etc/pacman.d/aarch64_mirrorlist')
result = subprocess.run(pacman_cmd +
['-Sy',
'--root', '/chroot/root',

View file

@ -70,19 +70,19 @@ LocalFileSigLevel = Never
# after the header, and they will be used before the default mirrors.
[core]
Include = /etc/pacman.d/aarch64_mirrorlist
Server = http://mirror.archlinuxarm.org/$arch/$repo
[extra]
Include = /etc/pacman.d/aarch64_mirrorlist
Server = http://mirror.archlinuxarm.org/$arch/$repo
[community]
Include = /etc/pacman.d/aarch64_mirrorlist
Server = http://mirror.archlinuxarm.org/$arch/$repo
[alarm]
Include = /etc/pacman.d/aarch64_mirrorlist
Server = http://mirror.archlinuxarm.org/$arch/$repo
[aur]
Include = /etc/pacman.d/aarch64_mirrorlist
Server = http://mirror.archlinuxarm.org/$arch/$repo
# An example of a custom package repository. See the pacman manpage for
# tips on creating your own repositories.

View file

@ -28,4 +28,4 @@ sed -i "s/@CARCH@/aarch64/g" pacman.conf
sed -i "s/#ParallelDownloads.*/ParallelDownloads = 8/g" pacman.conf
sed -i "s/SigLevel.*/SigLevel = Never/g" pacman.conf
sed -i "s/^CheckSpace/#CheckSpace/g" pacman.conf
sed -i "s|/mirrorlist|/aarch64_mirrorlist|g" pacman.conf
sed -i "s|Include = /etc/pacman.d/mirrorlist|Server = http://mirror.archlinuxarm.org/\$arch/\$repo|g" pacman.conf

15
ssh.py Normal file
View file

@ -0,0 +1,15 @@
import subprocess
import click
from logger import *
@click.command(name='ssh')
@verbose_option
def cmd_ssh(verbose):
setup_logging(verbose)
subprocess.run(['ssh',
'-o', 'GlobalKnownHostsFile=/dev/null',
'-o', 'UserKnownHostsFile=/dev/null',
'-o', 'StrictHostKeyChecking=no',
'kupfer@172.16.42.1'])

View file

@ -43,10 +43,15 @@ else:
'run',
'--name', 'kupferbootstrap',
'--rm',
'--interactive',
'--tty',
'--privileged',
'-v', f'{os.getcwd()}:/src:z',
'-v', f'{os.path.join(appdirs.user_cache_dir("kupfer"),"chroot")}:/chroot:z',
'-v', f'{os.path.join(appdirs.user_cache_dir("kupfer"),"pacman")}:/var/cache/pacman/pkg:z',
'-v', f'{os.path.join(appdirs.user_cache_dir("kupfer"),"jumpdrive")}:/var/cache/jumpdrive:z',
'-v', '/dev:/dev',
#'-v', '/mnt/kupfer:/mnt/kupfer:z',
'-v', '/usr/share/i18n/locales:/usr/share/i18n/locales:ro'] +
[tag,
'kupferbootstrap']