diff --git a/exec/file.py b/exec/file.py index 852ad48..00653aa 100644 --- a/exec/file.py +++ b/exec/file.py @@ -144,7 +144,13 @@ def remove_file(path: str, recursive=False): raise Exception(f"Unable to remove {path}: cmd returned {rc}") -def makedir(path, user: Optional[Union[str, int]] = None, group: Optional[Union[str, int]] = None, parents: bool = True): +def makedir( + path, + user: Optional[Union[str, int]] = None, + group: Optional[Union[str, int]] = None, + parents: bool = True, + mode: Optional[Union[int, str]] = None, +): if not root_check_exists(path): try: if parents: @@ -153,6 +159,8 @@ def makedir(path, user: Optional[Union[str, int]] = None, group: Optional[Union[ os.mkdir(path) except: run_root_cmd(['mkdir'] + (['-p'] if parents else []) + [path]) + if mode is not None: + chmod(path, mode=mode) chown(path, user, group) diff --git a/image/image.py b/image/image.py index 0cb0bcc..6532af7 100644 --- a/image/image.py +++ b/image/image.py @@ -333,8 +333,9 @@ def install_rootfs( ) chroot.add_sudo_config(config_name='wheel', privilegee='%wheel', password_required=True) copy_ssh_keys( - chroot.path, + chroot, user=user, + allow_fail=True, ) files = { 'etc/pacman.conf': get_base_distro(arch).get_pacman_conf( diff --git a/integration_tests.py b/integration_tests.py index 2b6c526..bc4eeb7 100644 --- a/integration_tests.py +++ b/integration_tests.py @@ -37,6 +37,11 @@ def ctx() -> click.Context: return click.Context(click.Command('integration_tests')) +def test_main_import(): + from main import cli + assert cli + + def test_config_load(ctx: click.Context): path = config.runtime.config_file assert path diff --git a/net/ssh.py b/net/ssh.py index 2a5ef7f..6eb7294 100644 --- a/net/ssh.py +++ b/net/ssh.py @@ -6,7 +6,9 @@ import click from config.state import config from constants import SSH_COMMON_OPTIONS, SSH_DEFAULT_HOST, SSH_DEFAULT_PORT +from chroot.abstract import Chroot from exec.cmd import run_cmd +from exec.file import write_file from wrapper import check_programs_wrap @@ -83,21 +85,16 @@ def find_ssh_keys(): return keys -def copy_ssh_keys(root_dir: str, user: str): +def copy_ssh_keys(chroot: Chroot, user: str, allow_fail: bool = False): check_programs_wrap(['ssh-keygen']) - 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) + ssh_dir_relative = os.path.join('/home', user, '.ssh') + ssh_dir = chroot.get_path(ssh_dir_relative) + authorized_keys_file_rel = os.path.join(ssh_dir_relative, 'authorized_keys') + authorized_keys_file = chroot.get_path(authorized_keys_file_rel) keys = find_ssh_keys() if len(keys) == 0: - logging.info("Could not find any ssh key to copy") + logging.warning("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 @@ -116,15 +113,34 @@ def copy_ssh_keys(root_dir: str, user: str): 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, mode=0o700) + if not keys: + logging.warning("No SSH keys to be copied. Skipping.") + return - with open(authorized_keys_file, 'a') as authorized_keys: - for key in keys: - pub = f'{key}.pub' - if not os.path.exists(pub): - logging.debug(f'Skipping key {key}: {pub} not found') - continue + auth_key_lines = [] + for key in keys: + pub = f'{key}.pub' + if not os.path.exists(pub): + logging.debug(f'Skipping key {key}: {pub} not found') + continue + try: with open(pub, 'r') as file: - authorized_keys.write(file.read()) + contents = file.read() + if not contents.strip(): + continue + auth_key_lines.append(contents) + except Exception as ex: + logging.warning(f"Could not read ssh pub key {pub}", exc_info=ex) + continue + + if not os.path.exists(ssh_dir): + logging.info(f"Creating {ssh_dir_relative!r} dir in chroot {chroot.path!r}") + chroot.run_cmd(["mkdir", "-p", "-m", "700", ssh_dir_relative], switch_user=user) + logging.info(f"Writing SSH pub keys to {authorized_keys_file}") + try: + write_file(authorized_keys_file, "\n".join(auth_key_lines), user=str(chroot.get_uid(user)), mode="644") + except Exception as ex: + logging.error(f"Failed to write SSH authorized_keys_file at {authorized_keys_file!r}:", exc_info=ex) + if allow_fail: + return + raise ex from ex diff --git a/packages/build.py b/packages/build.py index bcd5403..b388221 100644 --- a/packages/build.py +++ b/packages/build.py @@ -575,7 +575,7 @@ def build_package( build_root = target_chroot makepkg_compile_opts += ['--nodeps' if package.nodeps else '--syncdeps'] env = deepcopy(get_makepkg_env(arch)) - if foreign_arch and enable_crossdirect and package.name not in CROSSDIRECT_PKGS: + if foreign_arch and package.crossdirect and enable_crossdirect and package.name not in CROSSDIRECT_PKGS: env['PATH'] = f"/native/usr/lib/crossdirect/{arch}:{env['PATH']}" target_chroot.mount_crossdirect(native_chroot) else: diff --git a/packages/cli.py b/packages/cli.py index c300111..3878ba4 100644 --- a/packages/cli.py +++ b/packages/cli.py @@ -313,7 +313,7 @@ def cmd_list(): logging.info(f'Done! {len(packages)} Pkgbuilds:') for name in sorted(packages.keys()): p = packages[name] - print(f'name: {p.name}; ver: {p.version}; mode: {p.mode}; provides: {p.provides}; replaces: {p.replaces};' + print(f'name: {p.name}; ver: {p.version}; mode: {p.mode}; crossdirect: {p.crossdirect} provides: {p.provides}; replaces: {p.replaces};' f'local_depends: {p.local_depends}; depends: {p.depends}') @@ -346,6 +346,7 @@ def cmd_check(paths): mode_key = '_mode' nodeps_key = '_nodeps' + crossdirect_key = '_crossdirect' pkgbase_key = 'pkgbase' pkgname_key = 'pkgname' arches_key = '_arches' @@ -356,6 +357,7 @@ def cmd_check(paths): required = { mode_key: True, nodeps_key: False, + crossdirect_key: False, pkgbase_key: False, pkgname_key: True, 'pkgdesc': False, diff --git a/packages/pkgbuild.py b/packages/pkgbuild.py index baeea83..9be89c3 100644 --- a/packages/pkgbuild.py +++ b/packages/pkgbuild.py @@ -156,6 +156,7 @@ class Pkgbuild(PackageInfo): repo: str mode: str nodeps: bool + crossdirect: bool path: str pkgver: str pkgrel: str @@ -190,6 +191,7 @@ class Pkgbuild(PackageInfo): self.repo = repo or '' self.mode = '' self.nodeps = False + self.crossdirect = True self.path = relative_path self.pkgver = '' self.pkgrel = '' @@ -223,6 +225,7 @@ class Pkgbuild(PackageInfo): self.repo = pkg.repo self.mode = pkg.mode self.nodeps = pkg.nodeps + self.crossdirect = pkg.crossdirect self.path = pkg.path self.pkgver = pkg.pkgver self.pkgrel = pkg.pkgrel @@ -357,7 +360,11 @@ def parse_pkgbuild( else: raise Exception(msg) + # if _crossdirect is unset (None), it defaults to True + crossdirect_enabled = srcinfo_cache.build_crossdirect in (None, True) + base_package = Pkgbase(relative_pkg_dir, sources_refreshed=sources_refreshed, srcinfo_cache=srcinfo_cache) + base_package.crossdirect = crossdirect_enabled base_package.mode = mode base_package.nodeps = nodeps base_package.repo = relative_pkg_dir.split('/')[0] @@ -396,7 +403,7 @@ def parse_pkgbuild( elif splits[0] in ['depends', 'makedepends', 'checkdepends', 'optdepends']: spec = splits[1].split(': ', 1)[0] if not current.depends: - current.depends = {} + current.depends = (base_package.makedepends or {}).copy() current.depends = get_version_specs(spec, current.depends) if splits[0] == 'makedepends': if not current.makedepends: diff --git a/packages/srcinfo_cache.py b/packages/srcinfo_cache.py index 5cb2373..3d9737b 100644 --- a/packages/srcinfo_cache.py +++ b/packages/srcinfo_cache.py @@ -68,11 +68,19 @@ class SrcInitialisedFile(JsonFile): raise ex +srcinfo_meta_defaults = { + 'build_mode': None, + "build_nodeps": None, + "build_crossdirect": None, +} + + class SrcinfoMetaFile(JsonFile): checksums: dict[str, str] build_mode: Optional[str] build_nodeps: Optional[bool] + build_crossdirect: Optional[bool] _changed: bool _filename: ClassVar[str] = SRCINFO_METADATA_FILE @@ -92,9 +100,8 @@ class SrcinfoMetaFile(JsonFile): s = SrcinfoMetaFile({ '_relative_path': relative_pkg_dir, '_changed': True, - 'build_mode': '', - 'build_nodeps': None, 'checksums': {}, + **srcinfo_meta_defaults, }) return s, s.refresh_all() @@ -120,9 +127,11 @@ class SrcinfoMetaFile(JsonFile): if not force_refresh: logging.debug(f'{metadata._relative_path}: srcinfo checksums match!') lines = lines or metadata.read_srcinfo_file() - for build_field in ['build_mode', 'build_nodeps']: + for build_field in srcinfo_meta_defaults.keys(): if build_field not in metadata: metadata.refresh_build_fields() + if write: + metadata.write() break else: lines = metadata.refresh_all(write=write) @@ -143,8 +152,7 @@ class SrcinfoMetaFile(JsonFile): self._changed = True def refresh_build_fields(self): - self['build_mode'] = None - self['build_nodeps'] = None + self.update(srcinfo_meta_defaults) with open(os.path.join(config.get_path('pkgbuilds'), self._relative_path, 'PKGBUILD'), 'r') as file: lines = file.read().split('\n') for line in lines: @@ -156,6 +164,8 @@ class SrcinfoMetaFile(JsonFile): self.build_mode = val elif key == '_nodeps': self.build_nodeps = val.lower() == 'true' + elif key == '_crossdirect': + self.build_crossdirect = val.lower() == 'true' else: continue