diff --git a/constants.py b/constants.py index 8fc0e35..6706835 100644 --- a/constants.py +++ b/constants.py @@ -131,3 +131,14 @@ CFLAGS_ARCHES: dict[Arch, list[str]] = { } CROSSDIRECT_PKGS = ['crossdirect', 'qemu-user-static-bin', 'binfmt-qemu-static'] + +SSH_DEFAULT_HOST = '172.16.42.1' +SSH_DEFAULT_PORT = 22 +SSH_COMMON_OPTIONS = [ + '-o', + 'GlobalKnownHostsFile=/dev/null', + '-o', + 'UserKnownHostsFile=/dev/null', + '-o', + 'StrictHostKeyChecking=no', +] diff --git a/image.py b/image.py index 20d4759..59d80dc 100644 --- a/image.py +++ b/image.py @@ -7,6 +7,7 @@ from chroot import create_chroot, create_chroot_user, get_chroot_path, run_chroo from constants import BASE_PACKAGES, DEVICES, FLAVOURS from config import config from distro import get_base_distro, get_kupfer_https, get_kupfer_local +from ssh import copy_ssh_keys from wrapper import enforce_wrap from signal import pause @@ -181,6 +182,10 @@ def cmd_build(): password=profile['password'], ) + copy_ssh_keys( + rootfs_mount, + user=profile['username'], + ) with open(os.path.join(rootfs_mount, 'etc', 'pacman.conf'), 'w') as file: file.write(get_base_distro(arch).get_pacman_conf(check_space=True, extra_repos=get_kupfer_https(arch).repos)) if post_cmds: diff --git a/packages.py b/packages.py index b80d857..d514391 100644 --- a/packages.py +++ b/packages.py @@ -11,6 +11,7 @@ from constants import REPOSITORIES, CROSSDIRECT_PKGS, GCC_HOSTSPECS, ARCHES from config import config from chroot import create_chroot, run_chroot_cmd, try_install_packages, mount_crossdirect, write_cross_makepkg_conf, mount_packages, mount_pacman_cache from distro import get_kupfer_local +from ssh import run_ssh_command, scp_put_files from wrapper import enforce_wrap from utils import mount, umount, git from binfmt import register as binfmt_register @@ -348,10 +349,14 @@ def add_package_to_repo(package: Package, arch: str): logging.info(f'Adding {package.path} to repo {package.repo}') pkgbuild_dir = os.path.join(config.get_path('pkgbuilds'), package.path) + files = [] for file in os.listdir(pkgbuild_dir): # Forced extension by makepkg.conf if file.endswith('.pkg.tar.xz') or file.endswith('.pkg.tar.zst'): + repo_dir = os.path.join(config.get_package_dir(arch), package.repo) + files.append(os.path.join(repo_dir, file)) add_file_to_repo(os.path.join(pkgbuild_dir, file), package.repo, arch) + return files def check_package_version_built(package: Package, arch) -> bool: @@ -555,6 +560,8 @@ def build_packages( if not build_levels: logging.info('Everything built already') return + + files = [] for level, need_build in enumerate(build_levels): logging.info(f"(Level {level}) Building {', '.join([x.name for x in need_build])}") for package in need_build: @@ -565,7 +572,8 @@ def build_packages( enable_crossdirect=enable_crossdirect, enable_ccache=enable_ccache, ) - add_package_to_repo(package, arch) + files += add_package_to_repo(package, arch) + return files def build_packages_by_paths( @@ -583,7 +591,7 @@ def build_packages_by_paths( for _arch in set([arch, config.runtime['arch']]): init_prebuilts(_arch) packages = filter_packages_by_paths(repo, paths) - build_packages( + return build_packages( repo, packages, arch, @@ -611,6 +619,10 @@ def cmd_update(non_interactive: bool = False): @click.option('--arch', default=None) @click.argument('paths', nargs=-1) def cmd_build(paths: list[str], force=False, arch=None): + build(paths, force, arch) + + +def build(paths: list[str], force: bool, arch: str): if arch is None: # TODO: arch = config.get_profile()... arch = 'aarch64' @@ -634,7 +646,7 @@ def cmd_build(paths: list[str], force=False, arch=None): subprocess.run(['pacman', '-Syy', '--noconfirm', '--needed'] + CROSSDIRECT_PKGS) binfmt_register(arch) - build_packages_by_paths( + return build_packages_by_paths( paths, arch, repo, @@ -645,6 +657,22 @@ def cmd_build(paths: list[str], force=False, arch=None): ) +@cmd_packages.command(name='sideload') +@click.argument('paths', nargs=-1) +def cmd_sideload(paths: list[str]): + files = build(paths, True, None) + scp_put_files(files, '/tmp') + run_ssh_command([ + 'sudo', + '-S', + 'pacman', + '-U', + ] + [os.path.join('/tmp', os.path.basename(file)) for file in files] + [ + '--noconfirm', + '--overwrite=*', + ]) + + @cmd_packages.command(name='clean') def cmd_clean(): enforce_wrap() diff --git a/ssh.py b/ssh.py index 8a768a1..ce82020 100644 --- a/ssh.py +++ b/ssh.py @@ -1,25 +1,101 @@ +import logging +import os +import pathlib import subprocess import click -from wrapper import check_programs_wrap +from config import config +from constants import SSH_COMMON_OPTIONS, SSH_DEFAULT_HOST, SSH_DEFAULT_PORT +from wrapper import enforce_wrap @click.command(name='ssh') def cmd_ssh(): - check_programs_wrap('ssh') + enforce_wrap() run_ssh_command() -def run_ssh_command(cmd: list[str] = [], host: str = '172.16.42.1', user: str = 'kupfer', port: int = 22): +def run_ssh_command(cmd: list[str] = [], user: str = None, host: str = SSH_DEFAULT_HOST, port: int = SSH_DEFAULT_PORT): + if not user: + user = config.get_profile()['username'] + keys = find_ssh_keys() + key_args = [] + if len(keys) > 0: + key_args = ['-i', keys[0]] return subprocess.run([ 'ssh', - '-o', - 'GlobalKnownHostsFile=/dev/null', - '-o', - 'UserKnownHostsFile=/dev/null', - '-o', - 'StrictHostKeyChecking=no', + ] + key_args + SSH_COMMON_OPTIONS + [ '-p', str(port), f'{user}@{host}', '--', ] + cmd) + + +def scp_put_files(src: list[str], dst: str, user: str = None, host: str = SSH_DEFAULT_HOST, port: int = SSH_DEFAULT_PORT): + if not user: + user = config.get_profile()['username'] + keys = find_ssh_keys() + key_args = [] + if len(keys) > 0: + key_args = ['-i', keys[0]] + return subprocess.run([ + 'scp', + ] + key_args + SSH_COMMON_OPTIONS + [ + '-P', + str(port), + ] + src + [ + f'{user}@{host}:{dst}', + ]) + + +def find_ssh_keys(): + dir = os.path.join(pathlib.Path.home(), '.ssh') + if not os.path.exists(dir): + return [] + keys = [] + for file in os.listdir(dir): + if file.startswith('id_') and not file.endswith('.pub'): + keys.append(os.path.join(dir, file)) + return keys + + +def copy_ssh_keys(root_dir: str, user: str): + authorized_keys_file = os.path.join( + root_dir, + 'home', + user, + '.ssh', + 'authorized_keys', + ) + if os.path.exists(authorized_keys_file): + os.unlink(authorized_keys_file) + + keys = find_ssh_keys() + if len(keys) == 0: + logging.info("Could not find any ssh key to copy") + create = click.confirm("Do you want me to generate an ssh key for you?", True) + if not create: + return + result = subprocess.run([ + 'ssh-keygen', + '-f', + os.path.join(pathlib.Path.home(), '.ssh', 'id_ed25519_kupfer'), + '-t', + 'ed25519', + '-C', + 'kupfer', + '-N', + '', + ]) + if result.returncode != 0: + logging.fatal("Failed to generate ssh key") + keys = find_ssh_keys() + + ssh_dir = os.path.join(root_dir, 'home', user, '.ssh') + if not os.path.exists(ssh_dir): + os.makedirs(ssh_dir, exist_ok=True) + + with open(authorized_keys_file, 'a') as authorized_keys: + for key in keys: + with open(f'{key}.pub', 'r') as file: + authorized_keys.write(file.read()) diff --git a/wrapper.py b/wrapper.py index 9f259ae..55a5db1 100644 --- a/wrapper.py +++ b/wrapper.py @@ -1,5 +1,6 @@ import atexit import os +import pathlib import subprocess import sys import uuid @@ -102,10 +103,14 @@ def wrap_docker(): atexit.register(at_exit) dump_config_file(file_path=wrapped_config, config=(config.file | {'paths': DOCKER_PATHS})) + ssh_dir = os.path.join(pathlib.Path.home(), '.ssh') + if not os.path.exists(ssh_dir): + os.makedirs(ssh_dir) volumes = { '/dev': '/dev', os.getcwd(): '/src', wrapped_config: '/root/.config/kupfer/kupferbootstrap.toml', + ssh_dir: '/root/.ssh', } volumes |= dict({config.get_path(vol_name): vol_dest for vol_name, vol_dest in DOCKER_PATHS.items()}) docker_cmd = [