From c01ab18dc3feac4fd5d863e4a5b7a391f9e50304 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Thu, 2 Apr 2026 23:59:19 +0200 Subject: [PATCH 01/93] captain: fix .isEnabledFor(logging.DEBUG) calls Signed-off-by: Ricardo Pardini --- captain/cli/_release.py | 2 +- captain/cli/_stages.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/captain/cli/_release.py b/captain/cli/_release.py index 41f4700..1511ab6 100644 --- a/captain/cli/_release.py +++ b/captain/cli/_release.py @@ -168,7 +168,7 @@ def _cmd_release(cfg: Config, extra_args: list[str], args: object = None) -> Non "--entrypoint", "/usr/bin/uv", docker.RELEASE_IMAGE, - *(["--verbose"] if logging.getLogger().isEnabledFor(logging.DEBUG) else []), + *(["--verbose"] if log.isEnabledFor(logging.DEBUG) else []), "run", *inner_cmd, ) diff --git a/captain/cli/_stages.py b/captain/cli/_stages.py index 358d299..f307909 100644 --- a/captain/cli/_stages.py +++ b/captain/cli/_stages.py @@ -50,7 +50,7 @@ def _build_kernel_stage(cfg: Config) -> None: "--entrypoint", "/usr/bin/uv", cfg.builder_image, - *(["--verbose"] if logging.getLogger().isEnabledFor(logging.DEBUG) else []), + *(["--verbose"] if log.isEnabledFor(logging.DEBUG) else []), "run", "/work/build.py", "kernel", @@ -86,7 +86,7 @@ def _build_tools_stage(cfg: Config) -> None: "--entrypoint", "/usr/bin/uv", cfg.builder_image, - *(["--verbose"] if logging.getLogger().isEnabledFor(logging.DEBUG) else []), + *(["--verbose"] if log.isEnabledFor(logging.DEBUG) else []), "run", "/work/build.py", "tools", @@ -181,7 +181,7 @@ def _build_iso_stage(cfg: Config) -> None: "--entrypoint", "/usr/bin/uv", cfg.builder_image, - *(["--verbose"] if logging.getLogger().isEnabledFor(logging.DEBUG) else []), + *(["--verbose"] if log.isEnabledFor(logging.DEBUG) else []), "run", "/work/build.py", "iso", From 372cf40d26a406181b53011be1d7877c996a2487 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Thu, 2 Apr 2026 23:59:47 +0200 Subject: [PATCH 02/93] captain: force type console in __init__.py Signed-off-by: Ricardo Pardini --- captain/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/captain/__init__.py b/captain/__init__.py index 0c2b429..e4082ee 100644 --- a/captain/__init__.py +++ b/captain/__init__.py @@ -16,9 +16,9 @@ # Rich console — writes to stderr so log output never pollutes piped stdout. # If running under GHA, force colors. if os.environ.get("GITHUB_ACTIONS", "") == "": - console = Console(stderr=True) + console: Console = Console(stderr=True) else: - console = Console(stderr=True, color_system="standard", width=160, highlight=False) + console: Console = Console(stderr=True, color_system="standard", width=160, highlight=False) # Install Rich traceback handler globally (once, at import time). _install_rich_traceback(console=console, show_locals=True, width=None) From e9efcac2487c7e117bb663ff86f989eee727fad5 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 00:00:51 +0200 Subject: [PATCH 03/93] captain: catch required arg and loosen type (WiP: other way around.) Signed-off-by: Ricardo Pardini --- captain/config.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/captain/config.py b/captain/config.py index a8ad061..67bdf17 100644 --- a/captain/config.py +++ b/captain/config.py @@ -88,7 +88,7 @@ def needs_docker(self) -> bool: ) @classmethod - def from_args(cls, args: argparse.Namespace, project_dir: Path) -> Config: + def from_args(cls, args: argparse.Namespace, project_dir: Path | None) -> Config: """Create a Config from a parsed :class:`argparse.Namespace`. The *args* namespace is produced by :mod:`configargparse` which @@ -98,6 +98,10 @@ def from_args(cls, args: argparse.Namespace, project_dir: Path) -> Config: ``getattr`` with fallbacks is used because per-subcommand parsers only define the flags relevant to that subcommand. """ + + if project_dir is None: + raise ValueError("project_dir must be provided to Config.from_args") + return cls( project_dir=project_dir, output_dir=project_dir / "out", From 2e3296c43bf80d2d5f30ee6320629cf317638002 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 00:01:18 +0200 Subject: [PATCH 04/93] captain: util: Rich rule's separating run() output Signed-off-by: Ricardo Pardini --- captain/util.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/captain/util.py b/captain/util.py index 9876813..5a32525 100644 --- a/captain/util.py +++ b/captain/util.py @@ -10,6 +10,10 @@ from dataclasses import dataclass from pathlib import Path +from rich.rule import Rule + +import captain + log = logging.getLogger(__name__) @@ -75,7 +79,12 @@ def run( run_env: dict[str, str] | None = None if env is not None: run_env = {**os.environ, **env} - return subprocess.run( + + # If not capturing, and debugging, emit a Rich separator line, for visual clarity. + if not capture and log.isEnabledFor(logging.DEBUG): + captain.console.print(Rule(f"⮕ Starting subprocess: {cmd} ⮕", style="green")) + + proc = subprocess.run( cmd, check=check, capture_output=capture, @@ -84,6 +93,14 @@ def run( cwd=cwd, ) + if capture: + return proc + + if log.isEnabledFor(logging.DEBUG): + captain.console.print(Rule(f"⮕ Finished subprocess: {cmd} ⮕", style="green")) + + return proc + def ensure_dir(path: Path) -> Path: """Create a directory (and parents) if it doesn't exist, return the path.""" From 515ff84f50b0bbb085fdbb1fa938de1e37799816 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 00:56:16 +0200 Subject: [PATCH 05/93] captain: better logging; `in-docker: ` prefix Signed-off-by: Ricardo Pardini --- captain/__init__.py | 9 ++++++--- captain/docker.py | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/captain/__init__.py b/captain/__init__.py index e4082ee..d4e1a27 100644 --- a/captain/__init__.py +++ b/captain/__init__.py @@ -31,6 +31,9 @@ def format(self, record: logging.LogRecord) -> str: name = record.name stage = name.split(".", 1)[1] if name.startswith("captain.") else name record.__dict__["stage"] = stage + if os.environ.get("CAPTAIN_IN_DOCKER", "") == "docker": + # Running on host: show stage names in green for visual clarity. + record.__dict__["stage"] = f"[bold][blue]in-docker[/bold]: [/blue]{stage}" return super().format(record) @@ -42,12 +45,12 @@ def format(self, record: logging.LogRecord) -> str: console=console, show_time=False, show_level=True, - show_path=False, - markup=False, + show_path=True, + markup=True, rich_tracebacks=True, tracebacks_show_locals=True, ) - _handler.setFormatter(_StageFormatter("[%(stage)s] %(message)s")) + _handler.setFormatter(_StageFormatter("%(stage)s: %(message)s")) _root.addHandler(_handler) _root.setLevel(logging.DEBUG) _root.propagate = False diff --git a/captain/docker.py b/captain/docker.py index cdf2ddb..468590c 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -116,6 +116,8 @@ def run_in_release(cfg: Config, *extra_args: str) -> None: "-w", "/work", "-e", + "CAPTAIN_IN_DOCKER=docker", + "-e", f"ARCH={cfg.arch}", "-e", "RELEASE_MODE=native", @@ -157,6 +159,8 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: "-w", "/work", "-e", + "CAPTAIN_IN_DOCKER=docker", + "-e", f"ARCH={cfg.arch}", "-e", f"KERNEL_VERSION={cfg.kernel_version}", @@ -223,7 +227,6 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: docker_args.extend(["-e", "KERNEL_CONFIG=/work/kernel-config"]) docker_args.extend(extra_args) - log.debug("Docker args (builder): %s", docker_args) run(docker_args) From 409139f790962c1868309b4ce6a28ea95140268d Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 14:14:47 +0200 Subject: [PATCH 06/93] captain: add .editorconfig matching ruff - .editorconfig for shell scripts and shell templates Signed-off-by: Ricardo Pardini --- .editorconfig | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d6a2b59 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# Match Ruff style in .editorconfig format +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.py] +indent_style = space +indent_size = 4 +tab_width = 4 +max_line_length = 100 + +[{*.sh,*.sh.j2}] +indent_style = space +indent_size = 4 +tab_width = 4 +max_line_length = 100 \ No newline at end of file From cc2e113ef347bcabab610fb7c833ee513340bb2c Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 14:15:59 +0200 Subject: [PATCH 07/93] docker/mkosi: mount a Docker Volume at /work, so mkosi can cache the tools tree across invocations Signed-off-by: Ricardo Pardini --- captain/docker.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/captain/docker.py b/captain/docker.py index 468590c..a77edda 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -188,6 +188,8 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: f"GITHUB_ACTIONS={os.environ.get('GITHUB_ACTIONS', '')}", ] + docker_args += ["--mount", "type=volume,source=captain-workdir,target=/work"] + docker_args += ["-v", f"{cfg.project_dir}/mkosi.output:/work/mkosi.output"] docker_args += ["-v", f"{cfg.project_dir}/mkosi.extra:/work/mkosi.extra"] docker_args += ["-v", f"{cfg.project_dir}/out:/work/out"] From c3f659f3ea831bb4cc7ba2858263e5df6aea4da9 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 14:16:34 +0200 Subject: [PATCH 08/93] util: use a Panel to log the full subprocess command line Signed-off-by: Ricardo Pardini --- captain/util.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/captain/util.py b/captain/util.py index 5a32525..e1c150e 100644 --- a/captain/util.py +++ b/captain/util.py @@ -10,6 +10,7 @@ from dataclasses import dataclass from pathlib import Path +from rich.panel import Panel from rich.rule import Rule import captain @@ -82,6 +83,7 @@ def run( # If not capturing, and debugging, emit a Rich separator line, for visual clarity. if not capture and log.isEnabledFor(logging.DEBUG): + captain.console.print(Panel(f"Running command: {' '.join(cmd)}", style="green")) captain.console.print(Rule(f"⮕ Starting subprocess: {cmd} ⮕", style="green")) proc = subprocess.run( From ad7341d15d9381fb43bb1f854f44c18494e9c80d Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 14:17:45 +0200 Subject: [PATCH 09/93] captain: introduce Config build_kernel boolean - this will allow using a generic/distro kernel instead of building one Signed-off-by: Ricardo Pardini --- captain/artifacts.py | 11 +++++++++++ captain/cli/_commands.py | 5 +++-- captain/cli/_stages.py | 5 +++-- captain/config.py | 3 +++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/captain/artifacts.py b/captain/artifacts.py index 1a1c769..9fb7ee9 100644 --- a/captain/artifacts.py +++ b/captain/artifacts.py @@ -43,6 +43,17 @@ def collect_kernel(cfg: Config) -> None: log.info("kernel: %s (%s)", vmlinuz_dst, _human_size(vmlinuz_dst.stat().st_size)) else: log.warning("No kernel image found in %s", cfg.kernel_output) + # In this case, mkosi might have collected the kernel from a installed linux-image package. + # Find it and use it instead. + log.debug("Looking for kernel image produced by mkosi in %s", cfg.initramfs_output) + vmlinu_files = sorted(cfg.initramfs_output.glob("*.vmlinu*")) + if vmlinu_files: + vmlinuz_src = vmlinu_files[0] + vmlinuz_dst = out / f"vmlinuz-{cfg.kernel_version}-{cfg.arch_info.output_arch}" + shutil.copy2(vmlinuz_src, vmlinuz_dst) + log.info("kernel: %s (%s)", vmlinuz_dst, _human_size(vmlinuz_dst.stat().st_size)) + else: + log.warning("No kernel image found in %s either", cfg.initramfs_output) def collect_initramfs(cfg: Config) -> None: diff --git a/captain/cli/_commands.py b/captain/cli/_commands.py index 576b51f..1621474 100644 --- a/captain/cli/_commands.py +++ b/captain/cli/_commands.py @@ -81,10 +81,11 @@ def _cmd_iso(cfg: Config, _extra_args: list[str]) -> None: def _cmd_build(cfg: Config, extra_args: list[str]) -> None: """Full build: kernel → tools → initramfs → iso → artifacts.""" - _build_kernel_stage(cfg) + if cfg.build_kernel: + _build_kernel_stage(cfg) _build_tools_stage(cfg) _build_mkosi_stage(cfg, extra_args) - _build_iso_stage(cfg) + _build_iso_stage(cfg) # TODO also conditional... artifacts.collect(cfg) log.info("Build complete!") diff --git a/captain/cli/_stages.py b/captain/cli/_stages.py index f307909..2f8987c 100644 --- a/captain/cli/_stages.py +++ b/captain/cli/_stages.py @@ -23,6 +23,7 @@ def _build_kernel_stage(cfg: Config) -> None: modules_dir = cfg.modules_output / "usr" / "lib" / "modules" vmlinuz_dir = cfg.kernel_output has_vmlinuz = vmlinuz_dir.is_dir() and any(vmlinuz_dir.glob("vmlinuz-*")) + log.debug(f"Checking kernel build idempotency: modules_dir={modules_dir}, has_vmlinuz={has_vmlinuz}") if modules_dir.is_dir() and has_vmlinuz and not cfg.force_kernel: log.info("Kernel already built (use --force-kernel to rebuild)") @@ -120,7 +121,7 @@ def _build_mkosi_stage(cfg: Config, extra_args: list[str]) -> None: "mkosi", f"--architecture={cfg.arch_info.mkosi_arch}", f"--extra-tree={tools_tree}", - f"--extra-tree={modules_tree}", + *([f"--extra-tree={modules_tree}"] if cfg.build_kernel else []), f"--output-dir={output_dir}", "build", *mkosi_args, @@ -138,7 +139,7 @@ def _build_mkosi_stage(cfg: Config, extra_args: list[str]) -> None: docker.run_mkosi( cfg, f"--extra-tree={tools_tree}", - f"--extra-tree={modules_tree}", + *([f"--extra-tree={modules_tree}"] if cfg.build_kernel else []), f"--output-dir={output_dir}", "--package-cache-dir=/cache/packages", "build", diff --git a/captain/config.py b/captain/config.py index 67bdf17..da39bb6 100644 --- a/captain/config.py +++ b/captain/config.py @@ -31,6 +31,7 @@ class Config: # Target arch: str = "amd64" + build_kernel: bool = False kernel_version: str = DEFAULT_KERNEL_VERSION kernel_config: str | None = None kernel_src: str | None = None @@ -106,6 +107,7 @@ def from_args(cls, args: argparse.Namespace, project_dir: Path | None) -> Config project_dir=project_dir, output_dir=project_dir / "out", arch=getattr(args, "arch", "amd64"), + build_kernel=getattr(args, "build_kernel", False), kernel_version=getattr(args, "kernel_version", DEFAULT_KERNEL_VERSION), kernel_config=getattr(args, "kernel_config", None) or None, kernel_src=getattr(args, "kernel_src", None) or None, @@ -136,6 +138,7 @@ def from_env(cls, project_dir: Path) -> Config: project_dir=project_dir, output_dir=project_dir / "out", arch=os.environ.get("ARCH", "amd64"), + build_kernel=os.environ.get("BUILD_KERNEL") == "1", kernel_version=os.environ.get("KERNEL_VERSION", DEFAULT_KERNEL_VERSION), kernel_config=os.environ.get("KERNEL_CONFIG") or None, kernel_src=os.environ.get("KERNEL_SRC") or None, From 39e413e6f3b0e1b74a7ba8c8c25ce78a6deacf2e Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 14:18:10 +0200 Subject: [PATCH 10/93] WiP: use linux-image-generic distro kernel - hack until it uses the mkosi-supplied kernel for everything - artifacts: look for mkosi-supplied vmlinuz first Signed-off-by: Ricardo Pardini --- captain/__init__.py | 4 ++-- captain/artifacts.py | 34 ++++++++++++++++++++-------------- captain/cli/_commands.py | 4 ++-- captain/cli/_stages.py | 6 +++++- captain/iso.py | 10 ++++------ mkosi.conf | 7 +++++++ mkosi.finalize | 18 +++++++++++++++++- 7 files changed, 57 insertions(+), 26 deletions(-) diff --git a/captain/__init__.py b/captain/__init__.py index d4e1a27..03cc81e 100644 --- a/captain/__init__.py +++ b/captain/__init__.py @@ -16,9 +16,9 @@ # Rich console — writes to stderr so log output never pollutes piped stdout. # If running under GHA, force colors. if os.environ.get("GITHUB_ACTIONS", "") == "": - console: Console = Console(stderr=True) + console: Console = Console(stderr=True) else: - console: Console = Console(stderr=True, color_system="standard", width=160, highlight=False) + console: Console = Console(stderr=True, color_system="standard", width=160, highlight=False) # Install Rich traceback handler globally (once, at import time). _install_rich_traceback(console=console, show_locals=True, width=None) diff --git a/captain/artifacts.py b/captain/artifacts.py index 9fb7ee9..26bb61c 100644 --- a/captain/artifacts.py +++ b/captain/artifacts.py @@ -34,26 +34,32 @@ def _human_size(size: int) -> str: def collect_kernel(cfg: Config) -> None: """Copy the kernel image from mkosi.output/kernel/{version}/{arch}/ to out/.""" out = ensure_dir(cfg.output_dir) - vmlinuz_dir = cfg.kernel_output - vmlinuz_files = sorted(vmlinuz_dir.glob("vmlinuz-*")) if vmlinuz_dir.is_dir() else [] - if vmlinuz_files: - vmlinuz_src = vmlinuz_files[0] + # In this case, mkosi might have collected the kernel from a installed linux-image package. + # Find it and use it instead. + log.debug("Looking for kernel image produced by mkosi in %s", cfg.initramfs_output) + vmlinu_files = sorted(cfg.initramfs_output.glob("*.vmlinu*")) + if vmlinu_files: + vmlinuz_src = vmlinu_files[0] vmlinuz_dst = out / f"vmlinuz-{cfg.kernel_version}-{cfg.arch_info.output_arch}" shutil.copy2(vmlinuz_src, vmlinuz_dst) - log.info("kernel: %s (%s)", vmlinuz_dst, _human_size(vmlinuz_dst.stat().st_size)) + log.info( + "mkosi supplied kernel: %s (%s)", vmlinuz_dst, _human_size(vmlinuz_dst.stat().st_size) + ) else: - log.warning("No kernel image found in %s", cfg.kernel_output) - # In this case, mkosi might have collected the kernel from a installed linux-image package. - # Find it and use it instead. - log.debug("Looking for kernel image produced by mkosi in %s", cfg.initramfs_output) - vmlinu_files = sorted(cfg.initramfs_output.glob("*.vmlinu*")) - if vmlinu_files: - vmlinuz_src = vmlinu_files[0] + log.warning("No kernel image produced by mkosi in %s", cfg.initramfs_output) + vmlinuz_dir = cfg.kernel_output + vmlinuz_files = sorted(vmlinuz_dir.glob("vmlinuz-*")) if vmlinuz_dir.is_dir() else [] + if vmlinuz_files: + vmlinuz_src = vmlinuz_files[0] vmlinuz_dst = out / f"vmlinuz-{cfg.kernel_version}-{cfg.arch_info.output_arch}" shutil.copy2(vmlinuz_src, vmlinuz_dst) - log.info("kernel: %s (%s)", vmlinuz_dst, _human_size(vmlinuz_dst.stat().st_size)) + log.info( + "kernel built from source: %s (%s)", + vmlinuz_dst, + _human_size(vmlinuz_dst.stat().st_size), + ) else: - log.warning("No kernel image found in %s either", cfg.initramfs_output) + log.warning("No kernel image found in %s", cfg.kernel_output) def collect_initramfs(cfg: Config) -> None: diff --git a/captain/cli/_commands.py b/captain/cli/_commands.py index 1621474..6f2e7ca 100644 --- a/captain/cli/_commands.py +++ b/captain/cli/_commands.py @@ -65,7 +65,7 @@ def _check_kernel_modules(cfg: Config) -> None: def _cmd_initramfs(cfg: Config, extra_args: list[str]) -> None: """Build only the initramfs via mkosi, then collect artifacts.""" - _check_kernel_modules(cfg) + # _check_kernel_modules(cfg) _build_mkosi_stage(cfg, extra_args) artifacts.collect_initramfs(cfg) artifacts.collect_kernel(cfg) @@ -85,7 +85,7 @@ def _cmd_build(cfg: Config, extra_args: list[str]) -> None: _build_kernel_stage(cfg) _build_tools_stage(cfg) _build_mkosi_stage(cfg, extra_args) - _build_iso_stage(cfg) # TODO also conditional... + _build_iso_stage(cfg) # TODO also conditional... artifacts.collect(cfg) log.info("Build complete!") diff --git a/captain/cli/_stages.py b/captain/cli/_stages.py index 2f8987c..a097ed0 100644 --- a/captain/cli/_stages.py +++ b/captain/cli/_stages.py @@ -23,7 +23,11 @@ def _build_kernel_stage(cfg: Config) -> None: modules_dir = cfg.modules_output / "usr" / "lib" / "modules" vmlinuz_dir = cfg.kernel_output has_vmlinuz = vmlinuz_dir.is_dir() and any(vmlinuz_dir.glob("vmlinuz-*")) - log.debug(f"Checking kernel build idempotency: modules_dir={modules_dir}, has_vmlinuz={has_vmlinuz}") + log.debug( + "Checking kernel build idempotency: modules_dir=%s, has_vmlinuz=%s", + modules_dir, + has_vmlinuz, + ) if modules_dir.is_dir() and has_vmlinuz and not cfg.force_kernel: log.info("Kernel already built (use --force-kernel to rebuild)") diff --git a/captain/iso.py b/captain/iso.py index 5aa2044..8640e5c 100644 --- a/captain/iso.py +++ b/captain/iso.py @@ -41,13 +41,11 @@ def _grub_cfg(arch: str) -> str: def _find_vmlinuz(cfg: Config) -> Path: """Locate the vmlinuz kernel image.""" - vmlinuz_dir = cfg.kernel_output - candidates = sorted(vmlinuz_dir.glob("vmlinuz-*")) if vmlinuz_dir.is_dir() else [] - if not candidates: - log.error("No vmlinuz found in %s", vmlinuz_dir) - log.error("Build the kernel first: ./build.py kernel") + vmlinuz_files = sorted(cfg.initramfs_output.glob("*.vmlinuz*")) + if not vmlinuz_files: + log.error("No vmlinuz found in %s", cfg.initramfs_output) raise SystemExit(1) - return candidates[0] + return vmlinuz_files[0] def _find_initramfs(cfg: Config) -> Path: diff --git a/mkosi.conf b/mkosi.conf index 10a5702..11e73ee 100644 --- a/mkosi.conf +++ b/mkosi.conf @@ -34,6 +34,13 @@ WithDocs=no # can coexist under mkosi.output/. Packages= + linux-image-generic + # don't install an initrd builder -- we're it + initramfs-tools- + initramfs-tools-core- + dracut- + dracut-core- + dracut-install- # systemd and core systemd systemd-sysv # Provides poweroff, shutdown, reboot, halt, etc. diff --git a/mkosi.finalize b/mkosi.finalize index 1057bcd..0607377 100755 --- a/mkosi.finalize +++ b/mkosi.finalize @@ -54,6 +54,9 @@ done # is available as a cacheable artifact. We strip unneeded modules here so # the initramfs stays small without requiring a kernel recompile. # --------------------------------------------------------------------------- +echo "==> Listing all installed kernel modules: ${BUILDROOT}" +find "${BUILDROOT}/" -type f -name "*.ko*" + MODDIR="$BUILDROOT/usr/lib/modules" if [[ -d "$MODDIR" ]]; then echo "==> Trimming unnecessary kernel modules..." @@ -81,7 +84,8 @@ if [[ -d "$MODDIR" ]]; then ) for pattern in "${EXCLUDE_PATTERNS[@]}"; do - find "$MODDIR" -path "$pattern" -type d -exec rm -rf {} + 2>/dev/null || true + echo "--> Removing modules matching pattern: '${pattern}'" + find "$MODDIR" -path "$pattern" -type d -exec rm -rfv {} + || true done AFTER=$(du -sb "$MODDIR" | awk '{print $1}') @@ -97,4 +101,16 @@ if [[ -d "$MODDIR" ]]; then done fi +echo "==> Listing remaining kernel modules: ${BUILDROOT}" +find "${BUILDROOT}/" -type f -name "*.ko*" + +echo "==> Listing contents of /boot" +ls -lah "${BUILDROOT}"/boot + +echo "==> Showing full contents of environment variables for debugging:" +env + +echo "==> Removing /boot from the image to save space" +rm -rfv "$BUILDROOT"/boot + echo "==> CaptainOS finalize complete." From 2182c2898a03e1c5f8377c482f5cf4858c1c2095 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 15:40:00 +0200 Subject: [PATCH 11/93] gha: skip all kernel stuff - self-built kernel from source will come back at a later stage, as a .deb package Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0c6572..69a560c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,7 @@ jobs: # This is the workaround. # ------------------------------------------------------------------- check-kernel-cache: + if: false # disabled runs-on: ubuntu-latest outputs: amd64-cache-hit: ${{ steps.amd64-cache.outputs.cache-hit }} @@ -67,6 +68,7 @@ jobs: # Build kernel (vmlinuz + modules) inside Docker # ------------------------------------------------------------------- build-kernel: + if: false # disabled needs: [lint, check-kernel-cache] # Forks / cache-hit → cheap default runner; mainline cache-miss → fast oracle runner runs-on: > @@ -222,7 +224,7 @@ jobs: # ------------------------------------------------------------------- build-initramfs: runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} - needs: [build-kernel, download-tools] + needs: [download-tools] # build-kernel strategy: fail-fast: false matrix: @@ -236,6 +238,7 @@ jobs: uses: actions/checkout@v6 - name: Download kernel artifacts + if: false # disabled uses: actions/download-artifact@v6 with: name: kernel-${{ matrix.arch }} @@ -339,6 +342,7 @@ jobs: docker push "${REMOTE}:${HASH}-${{ matrix.arch }}" - name: Download kernel artifacts + if: false # don't uses: actions/download-artifact@v6 with: name: kernel-${{ matrix.arch }} @@ -350,11 +354,13 @@ jobs: name: initramfs-${{ matrix.arch }} path: out - - name: Stage initramfs for ISO build + - name: Stage initramfs and kernel for ISO build run: | mkdir -p "mkosi.output/initramfs/${KERNEL_VERSION}/${{ matrix.arch }}" - cp "out/initramfs-${KERNEL_VERSION}-${{ matrix.output_arch }}" \ + cp -v "out/initramfs-${KERNEL_VERSION}-${{ matrix.output_arch }}" \ "mkosi.output/initramfs/${KERNEL_VERSION}/${{ matrix.arch }}/image.cpio.zst" + cp -v "out/vmlinuz-${KERNEL_VERSION}-${{ matrix.output_arch }}" \ + "mkosi.output/initramfs/${KERNEL_VERSION}/${{ matrix.arch }}/image.vmlinuz" - name: Install uv uses: astral-sh/setup-uv@v7 @@ -393,6 +399,7 @@ jobs: run: cat .github/config.env >> "$GITHUB_ENV" - name: Download kernel artifacts + if: false # disabled uses: actions/download-artifact@v6 with: name: kernel-${{ matrix.target }} @@ -443,6 +450,7 @@ jobs: run: cat .github/config.env >> "$GITHUB_ENV" - name: Download kernel artifacts (amd64) + if: false # no uses: actions/download-artifact@v6 with: name: kernel-amd64 @@ -461,6 +469,7 @@ jobs: path: out - name: Download kernel artifacts (arm64) + if: false # no uses: actions/download-artifact@v6 with: name: kernel-arm64 From 331fe3d37ab39c781a0a0d839d5e67f9774ef377 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 16:54:01 +0200 Subject: [PATCH 12/93] gha: single matrix except for combined amd64/arm64 image at the end - just do everything at once Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 328 +++------------------------------------ 1 file changed, 22 insertions(+), 306 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69a560c..3605828 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,152 +31,16 @@ jobs: - name: Lint run: make lint - # ------------------------------------------------------------------- - # Fast cache lookup – decides whether the OCI runner is needed - # There are consistent issues with OCI runners not getting scheduled. - # This is the workaround. - # ------------------------------------------------------------------- - check-kernel-cache: - if: false # disabled - runs-on: ubuntu-latest - outputs: - amd64-cache-hit: ${{ steps.amd64-cache.outputs.cache-hit }} - arm64-cache-hit: ${{ steps.arm64-cache.outputs.cache-hit }} - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Check amd64 kernel cache - id: amd64-cache - uses: actions/cache/restore@v5 - with: - path: | - mkosi.output/kernel/${{ env.KERNEL_VERSION }}/amd64 - key: kernel-amd64-${{ env.KERNEL_VERSION }}-${{ hashFiles('kernel.configs/*', 'Dockerfile') }} - lookup-only: true - - - name: Check arm64 kernel cache - id: arm64-cache - uses: actions/cache/restore@v5 - with: - path: | - mkosi.output/kernel/${{ env.KERNEL_VERSION }}/arm64 - key: kernel-arm64-${{ env.KERNEL_VERSION }}-${{ hashFiles('kernel.configs/*', 'Dockerfile') }} - lookup-only: true - - # ------------------------------------------------------------------- - # Build kernel (vmlinuz + modules) inside Docker - # ------------------------------------------------------------------- - build-kernel: - if: false # disabled - needs: [lint, check-kernel-cache] - # Forks / cache-hit → cheap default runner; mainline cache-miss → fast oracle runner - runs-on: > - ${{ github.repository != 'tinkerbell/captain' && matrix.fork_runner - || needs.check-kernel-cache.outputs[format('{0}-cache-hit', matrix.arch)] == 'true' && matrix.fork_runner - || fromJSON(matrix.mainline_runner) }} - strategy: - fail-fast: false - matrix: - arch: [amd64, arm64] - include: - - arch: amd64 - fork_runner: ubuntu-latest - mainline_runner: '{"group":"Default","labels":["oracle-vm-16cpu-64gb-x86-64"]}' - - arch: arm64 - fork_runner: ubuntu-24.04-arm - mainline_runner: '{"group":"Default","labels":["oracle-vm-16cpu-64gb-arm64"]}' - env: - ARCH: ${{ matrix.arch }} - KERNEL_MODE: docker - MKOSI_MODE: skip - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Load shared config - run: cat .github/config.env >> "$GITHUB_ENV" - - - name: Log in to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Compute Dockerfile hash - id: dockerfile-hash - run: echo "hash=$(sha256sum Dockerfile | awk '{print $1}')" >> "$GITHUB_OUTPUT" - - - name: Pull or build builder image - id: builder - run: | - HASH="${{ steps.dockerfile-hash.outputs.hash }}" - REMOTE="ghcr.io/${{ github.repository }}/${{ env.BUILDER_IMAGE }}" - if docker pull "${REMOTE}:${HASH}-${{ matrix.arch }}"; then - docker tag "${REMOTE}:${HASH}-${{ matrix.arch }}" "${{ env.BUILDER_IMAGE }}:${HASH}" - docker tag "${REMOTE}:${HASH}-${{ matrix.arch }}" "${{ env.BUILDER_IMAGE }}" - echo "built=false" >> "$GITHUB_OUTPUT" - else - docker buildx build --progress=plain -t "${{ env.BUILDER_IMAGE }}:${HASH}" -t "${{ env.BUILDER_IMAGE }}" . - echo "built=true" >> "$GITHUB_OUTPUT" - fi - - - name: Push builder image to GHCR - if: github.ref == 'refs/heads/main' && steps.builder.outputs.built == 'true' - run: | - HASH="${{ steps.dockerfile-hash.outputs.hash }}" - REMOTE="ghcr.io/${{ github.repository }}/${{ env.BUILDER_IMAGE }}" - docker tag "${{ env.BUILDER_IMAGE }}:${HASH}" "${REMOTE}:${HASH}-${{ matrix.arch }}" - docker push "${REMOTE}:${HASH}-${{ matrix.arch }}" - - - name: Compute kernel cache key - id: kernel-cache-key - run: echo "key=kernel-${{ matrix.arch }}-${{ env.KERNEL_VERSION }}-${{ hashFiles('kernel.configs/*', 'Dockerfile') }}" >> "$GITHUB_OUTPUT" - - - name: Restore kernel cache - id: kernel-cache - uses: actions/cache/restore@v5 - with: - path: | - mkosi.output/kernel/${{ env.KERNEL_VERSION }}/${{ matrix.arch }} - key: ${{ steps.kernel-cache-key.outputs.key }} - - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Build kernel - run: uv run ./build.py kernel - - - name: Fix output file ownership - run: sudo chown -R "$(id -u):$(id -g)" mkosi.output/ - - - name: Save kernel cache - if: github.ref == 'refs/heads/main' && steps.kernel-cache.outputs.cache-hit != 'true' - uses: actions/cache/save@v5 - with: - path: | - mkosi.output/kernel/${{ env.KERNEL_VERSION }}/${{ matrix.arch }} - key: ${{ steps.kernel-cache-key.outputs.key }} - - - name: Upload kernel artifacts - uses: actions/upload-artifact@v6 - with: - name: kernel-${{ matrix.arch }} - path: | - mkosi.output/kernel/${{ env.KERNEL_VERSION }}/${{ matrix.arch }} - retention-days: 1 - # ------------------------------------------------------------------- # Download tools (containerd, runc, nerdctl, CNI plugins) # ------------------------------------------------------------------- download-tools: - needs: [lint] + needs: [ lint ] runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} strategy: fail-fast: false matrix: - arch: [amd64, arm64] + arch: [ amd64, arm64 ] env: ARCH: ${{ matrix.arch }} KERNEL_MODE: skip @@ -222,28 +86,24 @@ jobs: # ------------------------------------------------------------------- # Build initramfs via mkosi (depends on kernel + tools) # ------------------------------------------------------------------- - build-initramfs: + build-all: runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} - needs: [download-tools] # build-kernel + needs: [ download-tools ] # build-kernel strategy: fail-fast: false matrix: - arch: [amd64, arm64] + include: + - { arch: amd64, output_arch: x86_64, iso: true } + - { arch: arm64, output_arch: aarch64, iso: true } env: ARCH: ${{ matrix.arch }} KERNEL_MODE: skip - MKOSI_MODE: native + MKOSI_MODE: docker + ISO_MODE: docker steps: - name: Checkout code uses: actions/checkout@v6 - - name: Download kernel artifacts - if: false # disabled - uses: actions/download-artifact@v6 - with: - name: kernel-${{ matrix.arch }} - path: mkosi.output/kernel/${{ env.KERNEL_VERSION }}/${{ matrix.arch }} - - name: Download tools artifacts uses: actions/download-artifact@v6 with: @@ -257,22 +117,11 @@ jobs: chmod +x mkosi.output/tools/${{ matrix.arch }}/usr/local/bin/* chmod +x mkosi.output/tools/${{ matrix.arch }}/opt/cni/bin/* - - name: Refresh apt cache - run: sudo apt-get -o "Dpkg::Use-Pty=0" update - - - name: setup-mkosi - uses: systemd/mkosi@v26 - - - name: Install bubblewrap - run: | - sudo apt-get -o "Dpkg::Use-Pty=0" update - sudo apt-get -o "Dpkg::Use-Pty=0" install -y bubblewrap - - name: Install uv uses: astral-sh/setup-uv@v7 - name: Build initramfs - run: uv run ./build.py initramfs + run: uv run ./build.py build # full build, incl initramfs and iso - name: Upload initramfs artifacts uses: actions/upload-artifact@v6 @@ -281,146 +130,24 @@ jobs: path: out/ retention-days: 1 - # ------------------------------------------------------------------- - # Build UEFI-bootable ISO (depends on initramfs) - # ------------------------------------------------------------------- - build-iso: - runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} - needs: [build-initramfs] - strategy: - fail-fast: false - matrix: - arch: [amd64, arm64] - include: - - arch: amd64 - output_arch: x86_64 - - arch: arm64 - output_arch: aarch64 - env: - ARCH: ${{ matrix.arch }} - KERNEL_MODE: skip - MKOSI_MODE: skip - ISO_MODE: docker - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Load shared config - run: cat .github/config.env >> "$GITHUB_ENV" - - - name: Log in to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Compute Dockerfile hash - id: dockerfile-hash - run: echo "hash=$(sha256sum Dockerfile | awk '{print $1}')" >> "$GITHUB_OUTPUT" - - - name: Pull or build builder image - id: builder - run: | - HASH="${{ steps.dockerfile-hash.outputs.hash }}" - REMOTE="ghcr.io/${{ github.repository }}/${{ env.BUILDER_IMAGE }}" - if docker pull "${REMOTE}:${HASH}-${{ matrix.arch }}"; then - docker tag "${REMOTE}:${HASH}-${{ matrix.arch }}" "${{ env.BUILDER_IMAGE }}:${HASH}" - docker tag "${REMOTE}:${HASH}-${{ matrix.arch }}" "${{ env.BUILDER_IMAGE }}" - echo "built=false" >> "$GITHUB_OUTPUT" - else - docker build -t "${{ env.BUILDER_IMAGE }}:${HASH}" -t "${{ env.BUILDER_IMAGE }}" . - echo "built=true" >> "$GITHUB_OUTPUT" - fi - - - name: Push builder image to GHCR - if: github.ref == 'refs/heads/main' && steps.builder.outputs.built == 'true' - run: | - HASH="${{ steps.dockerfile-hash.outputs.hash }}" - REMOTE="ghcr.io/${{ github.repository }}/${{ env.BUILDER_IMAGE }}" - docker tag "${{ env.BUILDER_IMAGE }}:${HASH}" "${REMOTE}:${HASH}-${{ matrix.arch }}" - docker push "${REMOTE}:${HASH}-${{ matrix.arch }}" - - - name: Download kernel artifacts - if: false # don't - uses: actions/download-artifact@v6 - with: - name: kernel-${{ matrix.arch }} - path: mkosi.output/kernel/${{ env.KERNEL_VERSION }}/${{ matrix.arch }} - - - name: Download initramfs artifacts - uses: actions/download-artifact@v6 - with: - name: initramfs-${{ matrix.arch }} - path: out - - - name: Stage initramfs and kernel for ISO build - run: | - mkdir -p "mkosi.output/initramfs/${KERNEL_VERSION}/${{ matrix.arch }}" - cp -v "out/initramfs-${KERNEL_VERSION}-${{ matrix.output_arch }}" \ - "mkosi.output/initramfs/${KERNEL_VERSION}/${{ matrix.arch }}/image.cpio.zst" - cp -v "out/vmlinuz-${KERNEL_VERSION}-${{ matrix.output_arch }}" \ - "mkosi.output/initramfs/${KERNEL_VERSION}/${{ matrix.arch }}/image.vmlinuz" - - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Build ISO - run: uv run ./build.py iso + # ------------------------------------------------------------------- + # Build UEFI-bootable ISO (depends on initramfs) + # ------------------------------------------------------------------- - name: Upload ISO artifact + if: ${{ matrix.iso }} # only if matrix entry had iso: true uses: actions/upload-artifact@v6 with: name: iso-${{ matrix.arch }} path: out/captainos-${{ env.KERNEL_VERSION }}-${{ matrix.output_arch }}.iso retention-days: 1 - # ------------------------------------------------------------------- - # Publish per-arch artifacts and compute checksums - # ------------------------------------------------------------------- - publish-per-arch: - if: github.ref == 'refs/heads/main' - runs-on: ${{ matrix.target == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} - needs: [build-iso] - strategy: - fail-fast: false - matrix: - target: [amd64, arm64] - env: - ARCH: ${{ matrix.target }} - TARGET: ${{ matrix.target }} - steps: - - name: Checkout code - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Load shared config - run: cat .github/config.env >> "$GITHUB_ENV" - - - name: Download kernel artifacts - if: false # disabled - uses: actions/download-artifact@v6 - with: - name: kernel-${{ matrix.target }} - path: mkosi.output/kernel/${{ env.KERNEL_VERSION }}/${{ matrix.target }} - - - name: Download initramfs artifacts - uses: actions/download-artifact@v6 - with: - name: initramfs-${{ matrix.target }} - path: out - - - name: Download ISO artifact - uses: actions/download-artifact@v6 - with: - name: iso-${{ matrix.target }} - path: out - - - name: Install uv - uses: astral-sh/setup-uv@v7 + # ------------------------------------------------------------------- + # Publish per-arch artifacts and compute checksums + # ------------------------------------------------------------------- - name: Log in to GHCR + if: github.ref == 'refs/heads/main' uses: docker/login-action@v3 with: registry: ghcr.io @@ -428,6 +155,9 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Publish artifacts to GHCR + if: github.ref == 'refs/heads/main' + env: + TARGET: ${{ matrix.arch }} run: uv run ./build.py release publish # ------------------------------------------------------------------- @@ -436,7 +166,7 @@ jobs: publish-combined: if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest - needs: [publish-per-arch] + needs: [ build-all ] env: ARCH: amd64 TARGET: combined @@ -449,13 +179,6 @@ jobs: - name: Load shared config run: cat .github/config.env >> "$GITHUB_ENV" - - name: Download kernel artifacts (amd64) - if: false # no - uses: actions/download-artifact@v6 - with: - name: kernel-amd64 - path: mkosi.output/kernel/${{ env.KERNEL_VERSION }}/amd64 - - name: Download initramfs artifacts (amd64) uses: actions/download-artifact@v6 with: @@ -468,13 +191,6 @@ jobs: name: iso-amd64 path: out - - name: Download kernel artifacts (arm64) - if: false # no - uses: actions/download-artifact@v6 - with: - name: kernel-arm64 - path: mkosi.output/kernel/${{ env.KERNEL_VERSION }}/arm64 - - name: Download initramfs artifacts (arm64) uses: actions/download-artifact@v6 with: From 9809e01ebc976d4db84460803b4a61ef8d9c975a Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 18:01:21 +0200 Subject: [PATCH 13/93] mkosi.conf: just use tiny-initramfs instead of blocking the others (dracut/initramfs-tools) Signed-off-by: Ricardo Pardini --- mkosi.conf | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mkosi.conf b/mkosi.conf index 11e73ee..fceb838 100644 --- a/mkosi.conf +++ b/mkosi.conf @@ -29,18 +29,12 @@ WithDocs=no # Pre-built kernel modules and tools are injected via separate --extra-tree= # flags on the CLI: # --extra-tree=mkosi.output/tools/{arch}/ (tools) -# --extra-tree=mkosi.output/kernel/{ver}/{arch}/modules (kernel modules) # This keeps mkosi.conf architecture-neutral so both amd64 and arm64 builds # can coexist under mkosi.output/. Packages= linux-image-generic - # don't install an initrd builder -- we're it - initramfs-tools- - initramfs-tools-core- - dracut- - dracut-core- - dracut-install- + tiny-initramfs # a tiny initrd builder to account for the linux-image dependency # systemd and core systemd systemd-sysv # Provides poweroff, shutdown, reboot, halt, etc. From 1160d9bd88cdb8145ba041ec3fca0f190e6b5b48 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 18:02:07 +0200 Subject: [PATCH 14/93] mkosi.finalize: export DTBs for arches that have them - also: show info about modules and full rootfs Signed-off-by: Ricardo Pardini --- mkosi.finalize | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/mkosi.finalize b/mkosi.finalize index 0607377..32309de 100755 --- a/mkosi.finalize +++ b/mkosi.finalize @@ -3,6 +3,9 @@ # The root filesystem is writable here. set -euo pipefail +echo "==> Showing full contents of environment variables for debugging:" +env + echo "==> CaptainOS finalize: unlocking root account..." # Unlock root with empty password @@ -54,10 +57,11 @@ done # is available as a cacheable artifact. We strip unneeded modules here so # the initramfs stays small without requiring a kernel recompile. # --------------------------------------------------------------------------- -echo "==> Listing all installed kernel modules: ${BUILDROOT}" -find "${BUILDROOT}/" -type f -name "*.ko*" - MODDIR="$BUILDROOT/usr/lib/modules" + +echo "==> Listing all installed kernel modules: ${MODDIR}" +du -h -x -d 5 "$MODDIR" | sort -h || true + if [[ -d "$MODDIR" ]]; then echo "==> Trimming unnecessary kernel modules..." BEFORE=$(du -sb "$MODDIR" | awk '{print $1}') @@ -102,15 +106,30 @@ if [[ -d "$MODDIR" ]]; then fi echo "==> Listing remaining kernel modules: ${BUILDROOT}" -find "${BUILDROOT}/" -type f -name "*.ko*" - -echo "==> Listing contents of /boot" -ls -lah "${BUILDROOT}"/boot - -echo "==> Showing full contents of environment variables for debugging:" -env +du -h -x -d 5 "$MODDIR" | sort -h || true echo "==> Removing /boot from the image to save space" -rm -rfv "$BUILDROOT"/boot +rm -rfv "${BUILDROOT}"/boot + +DTB_SRC_DIR=$(echo -n "${BUILDROOT}"/usr/lib/linux-image-*) +if [[ -d "$DTB_SRC_DIR" ]]; then + echo "==> Exporting kernel DTBs; DTB_DIR: ${DTB_SRC_DIR}" + mkdir -pv "${BUILDROOT}/root/dtb" + # Move every .dtb* file in the DTB_SRC_DIR to "${BUILDROOT}/root/dtb" -- maintain the directory structure + find "${DTB_SRC_DIR}" -type f -name "*.dtb*" -exec sh -c 'for f; do mkdir -p "${0}/$(dirname "${f#'"${DTB_SRC_DIR}"'/}")" && mv "$f" "${0}/${f#'"${DTB_SRC_DIR}"'/}" ; done' "${BUILDROOT}/root/dtb" {} + || true + + echo "--> Leftover files in ${DTB_SRC_DIR}:" + find "${DTB_SRC_DIR}" -type f + + # Now simply output the dtb directory to mkosi's OUTPUTDIR + echo "==> Copying DTBs to mkosi output directory: ${OUTPUTDIR}/dtb" + mv -v "${BUILDROOT}/root/dtb" "${OUTPUTDIR}/dtb" +else + echo "==> No DTB source directory found at ${DTB_SRC_DIR}; skipping DTB export" +fi + +# Show a 4-deep overview of the full rootfs contents +echo "==> Final contents of root filesystem (up to depth 4):" +du -h -x -d 4 "$BUILDROOT" | sort -h || true echo "==> CaptainOS finalize complete." From 611753956ddafa11c7a507c624848165cc393c42 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 18:25:28 +0200 Subject: [PATCH 15/93] artifacts: collect `dtb` `mkosi.output` folder into `out/` Signed-off-by: Ricardo Pardini --- captain/artifacts.py | 17 +++++++++++++++++ captain/cli/_commands.py | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/captain/artifacts.py b/captain/artifacts.py index 26bb61c..180d285 100644 --- a/captain/artifacts.py +++ b/captain/artifacts.py @@ -131,3 +131,20 @@ def collect(cfg: Config) -> None: collect_initramfs(cfg) collect_kernel(cfg) collect_iso(cfg) + + +def collect_dtbs(cfg): + """Copy the 'dtb' dir from mkosi.output/initramfs/{arch}/ to out/ if it exists.""" + indir = ensure_dir(cfg.initramfs_output) + dtb_dir: Path = indir / "dtb" + if dtb_dir.exists(): + log.info("Found dtb directory in %s, copying to output...", dtb_dir) + out = ensure_dir(cfg.output_dir) + target_dtb_dir = out / f"dtb-{cfg.kernel_version}-{cfg.arch_info.output_arch}" + # copy the directory incl subdirs and files + if target_dtb_dir.exists(): + shutil.rmtree(target_dtb_dir) + shutil.copytree(dtb_dir, target_dtb_dir) + log.info("Copied dtb directory: %s", target_dtb_dir) + else: + log.warning("No dtb directory found in %s", dtb_dir) diff --git a/captain/cli/_commands.py b/captain/cli/_commands.py index 6f2e7ca..4357736 100644 --- a/captain/cli/_commands.py +++ b/captain/cli/_commands.py @@ -69,6 +69,7 @@ def _cmd_initramfs(cfg: Config, extra_args: list[str]) -> None: _build_mkosi_stage(cfg, extra_args) artifacts.collect_initramfs(cfg) artifacts.collect_kernel(cfg) + artifacts.collect_dtbs(cfg) log.info("Initramfs build complete!") @@ -84,7 +85,7 @@ def _cmd_build(cfg: Config, extra_args: list[str]) -> None: if cfg.build_kernel: _build_kernel_stage(cfg) _build_tools_stage(cfg) - _build_mkosi_stage(cfg, extra_args) + _cmd_initramfs(cfg, extra_args) # delegate, so it also collects _build_iso_stage(cfg) # TODO also conditional... artifacts.collect(cfg) log.info("Build complete!") From e2de8657ebdbb5e2b12dd8dc70682a25db5abc68 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 18:51:56 +0200 Subject: [PATCH 16/93] publish: publish DTBs in OCI image as a directory Signed-off-by: Ricardo Pardini --- captain/cli/_commands.py | 2 +- captain/oci/_publish.py | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/captain/cli/_commands.py b/captain/cli/_commands.py index 4357736..55fe6c1 100644 --- a/captain/cli/_commands.py +++ b/captain/cli/_commands.py @@ -85,7 +85,7 @@ def _cmd_build(cfg: Config, extra_args: list[str]) -> None: if cfg.build_kernel: _build_kernel_stage(cfg) _build_tools_stage(cfg) - _cmd_initramfs(cfg, extra_args) # delegate, so it also collects + _cmd_initramfs(cfg, extra_args) # delegate, so it also collects _build_iso_stage(cfg) # TODO also conditional... artifacts.collect(cfg) log.info("Build complete!") diff --git a/captain/oci/_publish.py b/captain/oci/_publish.py index 0bf7941..b67ced6 100644 --- a/captain/oci/_publish.py +++ b/captain/oci/_publish.py @@ -4,13 +4,14 @@ import contextlib import logging +import tarfile from datetime import datetime, timezone from pathlib import Path from uuid import uuid4 from captain import buildah, skopeo from captain.config import Config -from captain.util import ensure_dir +from captain.util import ensure_dir, get_arch_info from ._build import _build_platform_image, _collect_arch_artifacts, _deterministic_tar from ._common import _ARCHES, _image_ref @@ -199,8 +200,22 @@ def publish( # Create deterministic layer tars (shared across manifest pushes). arch_layer_tars: dict[str, list[Path]] = {} for arch, files in arch_files.items(): + log.info("Creating layer tars for %s... files: %s", arch, files) arch_layer_tars[arch] = [_deterministic_tar(f, out) for f in files] + # A single layer for all DTBs, if any; those are highly compressible together. + dtb_dir_in = out / f"dtb-{cfg.kernel_version}-{get_arch_info(arch).output_arch}" + if not dtb_dir_in.is_dir(): + log.warning("No dtbs directory found for %s: %s", arch, dtb_dir_in) + else: + log.info(f"Found DTB directory for {arch}: {dtb_dir_in}") + all_dtb_files: list[Path] = sorted(dtb_dir_in.glob("**/*.dtb*")) + dtb_tar_path = out / f"dtbs-{cfg.kernel_version}-{arch}.tar" + with tarfile.open(dtb_tar_path, "w") as tar: + for f in all_dtb_files: + tar.add(f, arcname=f.relative_to(out)) + arch_layer_tars[arch].append(dtb_tar_path) + pushed = True try: if target == "combined": From 551a95b079b820cd3a3bf0fb962dfc7b9f7d21c6 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 20:24:40 +0200 Subject: [PATCH 17/93] kernel: drop all kernel-build related code - later to be reborn as standard apt package Signed-off-by: Ricardo Pardini --- captain/artifacts.py | 52 +- captain/cli/_commands.py | 48 +- captain/cli/_main.py | 9 +- captain/cli/_parser.py | 31 +- captain/cli/_stages.py | 66 +-- captain/config.py | 40 +- captain/docker.py | 28 - captain/kernel.py | 278 ---------- captain/oci/_build.py | 21 +- captain/qemu.py | 1 - captain/util.py | 15 - kernel.configs/6.18.y.amd64 | 1032 ----------------------------------- kernel.configs/6.18.y.arm64 | 515 ----------------- kernel.configs/6.19.y.amd64 | 1032 ----------------------------------- kernel.configs/6.19.y.arm64 | 515 ----------------- 15 files changed, 36 insertions(+), 3647 deletions(-) delete mode 100644 captain/kernel.py delete mode 100644 kernel.configs/6.18.y.amd64 delete mode 100644 kernel.configs/6.18.y.arm64 delete mode 100644 kernel.configs/6.19.y.amd64 delete mode 100644 kernel.configs/6.19.y.arm64 diff --git a/captain/artifacts.py b/captain/artifacts.py index 180d285..3b919d3 100644 --- a/captain/artifacts.py +++ b/captain/artifacts.py @@ -32,10 +32,8 @@ def _human_size(size: int) -> str: def collect_kernel(cfg: Config) -> None: - """Copy the kernel image from mkosi.output/kernel/{version}/{arch}/ to out/.""" + """Copy the kernel image produced by mkosi.""" out = ensure_dir(cfg.output_dir) - # In this case, mkosi might have collected the kernel from a installed linux-image package. - # Find it and use it instead. log.debug("Looking for kernel image produced by mkosi in %s", cfg.initramfs_output) vmlinu_files = sorted(cfg.initramfs_output.glob("*.vmlinu*")) if vmlinu_files: @@ -46,20 +44,7 @@ def collect_kernel(cfg: Config) -> None: "mkosi supplied kernel: %s (%s)", vmlinuz_dst, _human_size(vmlinuz_dst.stat().st_size) ) else: - log.warning("No kernel image produced by mkosi in %s", cfg.initramfs_output) - vmlinuz_dir = cfg.kernel_output - vmlinuz_files = sorted(vmlinuz_dir.glob("vmlinuz-*")) if vmlinuz_dir.is_dir() else [] - if vmlinuz_files: - vmlinuz_src = vmlinuz_files[0] - vmlinuz_dst = out / f"vmlinuz-{cfg.kernel_version}-{cfg.arch_info.output_arch}" - shutil.copy2(vmlinuz_src, vmlinuz_dst) - log.info( - "kernel built from source: %s (%s)", - vmlinuz_dst, - _human_size(vmlinuz_dst.stat().st_size), - ) - else: - log.warning("No kernel image found in %s", cfg.kernel_output) + log.error("No kernel image produced by mkosi in %s", cfg.initramfs_output) def collect_initramfs(cfg: Config) -> None: @@ -75,6 +60,22 @@ def collect_initramfs(cfg: Config) -> None: log.warning("No initramfs CPIO found in %s", cfg.initramfs_output) +def collect_dtbs(cfg): + """Collect the dtb directory produced by mkosi's finalize script.""" + indir = ensure_dir(cfg.initramfs_output) + dtb_dir: Path = indir / "dtb" + if dtb_dir.exists(): + log.info("Found dtb directory in %s, copying to output...", dtb_dir) + out = ensure_dir(cfg.output_dir) + target_dtb_dir = out / f"dtb-{cfg.kernel_version}-{cfg.arch_info.output_arch}" + if target_dtb_dir.exists(): + shutil.rmtree(target_dtb_dir) + shutil.copytree(dtb_dir, target_dtb_dir) + log.info("Copied dtb directory: %s", target_dtb_dir) + else: + log.warning("No dtb directory found in %s", dtb_dir) + + def collect_iso(cfg: Config) -> None: """Copy the ISO image from mkosi.output/iso/{arch}/ to out/.""" out = ensure_dir(cfg.output_dir) @@ -131,20 +132,3 @@ def collect(cfg: Config) -> None: collect_initramfs(cfg) collect_kernel(cfg) collect_iso(cfg) - - -def collect_dtbs(cfg): - """Copy the 'dtb' dir from mkosi.output/initramfs/{arch}/ to out/ if it exists.""" - indir = ensure_dir(cfg.initramfs_output) - dtb_dir: Path = indir / "dtb" - if dtb_dir.exists(): - log.info("Found dtb directory in %s, copying to output...", dtb_dir) - out = ensure_dir(cfg.output_dir) - target_dtb_dir = out / f"dtb-{cfg.kernel_version}-{cfg.arch_info.output_arch}" - # copy the directory incl subdirs and files - if target_dtb_dir.exists(): - shutil.rmtree(target_dtb_dir) - shutil.copytree(dtb_dir, target_dtb_dir) - log.info("Copied dtb directory: %s", target_dtb_dir) - else: - log.warning("No dtb directory found in %s", dtb_dir) diff --git a/captain/cli/_commands.py b/captain/cli/_commands.py index 55fe6c1..c131224 100644 --- a/captain/cli/_commands.py +++ b/captain/cli/_commands.py @@ -13,7 +13,6 @@ from ._stages import ( _build_iso_stage, - _build_kernel_stage, _build_mkosi_stage, _build_tools_stage, ) @@ -21,51 +20,14 @@ log = logging.getLogger(__name__) -def _cmd_kernel(cfg: Config, _extra_args: list[str]) -> None: - """Build only the kernel (no tools, no mkosi).""" - _build_kernel_stage(cfg) - artifacts.collect_kernel(cfg) - log.info("Kernel build stage complete!") - - def _cmd_tools(cfg: Config, _extra_args: list[str]) -> None: """Download tools (containerd, runc, nerdctl, CNI plugins).""" _build_tools_stage(cfg) log.info("Tools stage complete!") -def _check_kernel_modules(cfg: Config) -> None: - """Verify kernel modules exist before building the initramfs. - - The initramfs depends on pre-built kernel modules in the ExtraTrees - directory. If they are missing (e.g. due to an artifact download - issue) the build should fail immediately rather than silently - producing an initramfs without modules. - """ - modules_dir = cfg.modules_output / "usr" / "lib" / "modules" - if not modules_dir.is_dir(): - log.error("Kernel modules directory not found: %s", modules_dir) - log.error("Ensure the kernel build artifacts are downloaded correctly.") - raise SystemExit(1) - # Check that at least one module version directory exists with modules - version_dirs = [d for d in modules_dir.iterdir() if d.is_dir()] - if not version_dirs: - log.error("No kernel version directories found in %s", modules_dir) - raise SystemExit(1) - # Search all version directories for at least one kernel module - for version_dir in version_dirs: - if any(version_dir.rglob("*.ko*")): - log.info("Kernel modules found in %s (version: %s)", version_dir, version_dir.name) - return - searched = ", ".join(str(d) for d in version_dirs) - log.error("No kernel modules (.ko/.ko.zst) found in any kernel version directory.") - log.error("Searched directories: %s", searched) - raise SystemExit(1) - - def _cmd_initramfs(cfg: Config, extra_args: list[str]) -> None: """Build only the initramfs via mkosi, then collect artifacts.""" - # _check_kernel_modules(cfg) _build_mkosi_stage(cfg, extra_args) artifacts.collect_initramfs(cfg) artifacts.collect_kernel(cfg) @@ -81,12 +43,10 @@ def _cmd_iso(cfg: Config, _extra_args: list[str]) -> None: def _cmd_build(cfg: Config, extra_args: list[str]) -> None: - """Full build: kernel → tools → initramfs → iso → artifacts.""" - if cfg.build_kernel: - _build_kernel_stage(cfg) + """Full build: tools → initramfs → iso → artifacts.""" _build_tools_stage(cfg) _cmd_initramfs(cfg, extra_args) # delegate, so it also collects - _build_iso_stage(cfg) # TODO also conditional... + _build_iso_stage(cfg) # TODO also conditional... / and/or include dtb's for arm64 artifacts.collect(cfg) log.info("Build complete!") @@ -218,18 +178,15 @@ def _clean_all(cfg: Config) -> None: def _cmd_summary(cfg: Config, _extra_args: list[str]) -> None: """Print mkosi configuration summary.""" tools_tree = str(cfg.tools_output) - modules_tree = str(cfg.modules_output) output_dir = str(cfg.initramfs_output) match cfg.mkosi_mode: case "docker": docker.build_builder(cfg) container_tree = f"/work/mkosi.output/tools/{cfg.arch}" - container_modules = f"/work/mkosi.output/kernel/{cfg.kernel_version}/{cfg.arch}/modules" container_outdir = f"/work/mkosi.output/initramfs/{cfg.kernel_version}/{cfg.arch}" docker.run_mkosi( cfg, f"--extra-tree={container_tree}", - f"--extra-tree={container_modules}", f"--output-dir={container_outdir}", "summary", ) @@ -239,7 +196,6 @@ def _cmd_summary(cfg: Config, _extra_args: list[str]) -> None: "mkosi", f"--architecture={cfg.arch_info.mkosi_arch}", f"--extra-tree={tools_tree}", - f"--extra-tree={modules_tree}", f"--output-dir={output_dir}", "summary", ], diff --git a/captain/cli/_main.py b/captain/cli/_main.py index 33ded5d..b299d83 100644 --- a/captain/cli/_main.py +++ b/captain/cli/_main.py @@ -29,7 +29,6 @@ _cmd_clean, _cmd_initramfs, _cmd_iso, - _cmd_kernel, _cmd_qemu_test, _cmd_shell, _cmd_summary, @@ -86,7 +85,6 @@ def main(project_dir: Path | None = None) -> None: # 7. Dispatch. dispatch: dict[str, object] = { "build": _cmd_build, - "kernel": _cmd_kernel, "tools": _cmd_tools, "initramfs": _cmd_initramfs, "iso": _cmd_iso, @@ -105,23 +103,19 @@ def main(project_dir: Path | None = None) -> None: else: handler(cfg, extra) # type: ignore[operator] else: + log.error("You hit ungolden path; passing stuff to mkosi directly is not reproducible.") # Pass through to mkosi (shouldn't happen with _extract_command # but kept as a safety net). tools_tree = str(cfg.tools_output) - modules_tree = str(cfg.modules_output) output_dir = str(cfg.initramfs_output) match cfg.mkosi_mode: case "docker": docker.build_builder(cfg) container_tree = f"/work/mkosi.output/tools/{cfg.arch}" - container_modules = ( - f"/work/mkosi.output/kernel/{cfg.kernel_version}/{cfg.arch}/modules" - ) container_outdir = f"/work/mkosi.output/initramfs/{cfg.kernel_version}/{cfg.arch}" docker.run_mkosi( cfg, f"--extra-tree={container_tree}", - f"--extra-tree={container_modules}", f"--output-dir={container_outdir}", command, *extra, @@ -132,7 +126,6 @@ def main(project_dir: Path | None = None) -> None: "mkosi", f"--architecture={cfg.arch_info.mkosi_arch}", f"--extra-tree={tools_tree}", - f"--extra-tree={modules_tree}", f"--output-dir={output_dir}", command, *extra, diff --git a/captain/cli/_parser.py b/captain/cli/_parser.py index 4e6d129..0b685de 100644 --- a/captain/cli/_parser.py +++ b/captain/cli/_parser.py @@ -198,7 +198,7 @@ def _add_common_flags(parser: configargparse.ArgParser) -> None: def _add_kernel_flags(parser: configargparse.ArgParser) -> None: - """--kernel-version, --kernel-src, --kernel-mode, --force-kernel""" + """--kernel-version""" g = parser.add_argument_group("kernel") g.add_argument( "--kernel-version", @@ -207,34 +207,6 @@ def _add_kernel_flags(parser: configargparse.ArgParser) -> None: default=DEFAULT_KERNEL_VERSION, help="kernel version to build", ) - g.add_argument( - "--kernel-config", - env_var="KERNEL_CONFIG", - metavar="PATH", - default=None, - help="path to kernel config file (overrides auto-detection from kernel.configs/)", - ) - g.add_argument( - "--kernel-src", - env_var="KERNEL_SRC", - metavar="PATH", - default=None, - help="path to local kernel source tree", - ) - g.add_argument( - "--kernel-mode", - env_var="KERNEL_MODE", - default="docker", - choices=list(VALID_MODES), - help="kernel stage execution mode", - ) - g.add_argument( - "--force-kernel", - env_var="FORCE_KERNEL", - action="store_true", - default=False, - help="force kernel rebuild even if outputs exist", - ) def _add_tools_flags(parser: configargparse.ArgParser) -> None: @@ -570,7 +542,6 @@ def _add_tink_flags(parser: configargparse.ArgParser) -> None: _add_mkosi_flags, _add_iso_flags, ], - "kernel": [_add_common_flags, _add_kernel_flags], "tools": [_add_common_flags, _add_tools_flags], "initramfs": [_add_common_flags, _add_kernel_flags, _add_mkosi_flags], "iso": [_add_common_flags, _add_kernel_flags, _add_iso_flags], diff --git a/captain/cli/_stages.py b/captain/cli/_stages.py index a097ed0..b29726c 100644 --- a/captain/cli/_stages.py +++ b/captain/cli/_stages.py @@ -4,71 +4,13 @@ import logging -from captain import docker, iso, kernel, tools +from captain import docker, iso, tools from captain.config import Config -from captain.util import check_kernel_dependencies, check_mkosi_dependencies, run +from captain.util import check_mkosi_dependencies, run log = logging.getLogger(__name__) -def _build_kernel_stage(cfg: Config) -> None: - """Run the kernel build stage according to *cfg.kernel_mode*.""" - - # --- skip --------------------------------------------------------- - if cfg.kernel_mode == "skip": - log.info("KERNEL_MODE=skip — skipping kernel build") - return - - # --- idempotency -------------------------------------------------- - modules_dir = cfg.modules_output / "usr" / "lib" / "modules" - vmlinuz_dir = cfg.kernel_output - has_vmlinuz = vmlinuz_dir.is_dir() and any(vmlinuz_dir.glob("vmlinuz-*")) - log.debug( - "Checking kernel build idempotency: modules_dir=%s, has_vmlinuz=%s", - modules_dir, - has_vmlinuz, - ) - - if modules_dir.is_dir() and has_vmlinuz and not cfg.force_kernel: - log.info("Kernel already built (use --force-kernel to rebuild)") - return - - if modules_dir.is_dir() and not has_vmlinuz: - log.warning("Modules exist but vmlinuz is missing — rebuilding kernel") - - # --- native ------------------------------------------------------- - if cfg.kernel_mode == "native": - missing = check_kernel_dependencies(cfg.arch) - if missing: - log.error("Missing kernel build tools: %s", ", ".join(missing)) - log.error("Install them or set --kernel-mode=docker.") - raise SystemExit(1) - log.info("Building kernel (native)...") - kernel.build(cfg) - return - - # --- docker ------------------------------------------------------- - docker.build_builder(cfg) - log.info("Building kernel (docker)...") - docker.run_in_builder( - cfg, - "--entrypoint", - "/usr/bin/uv", - cfg.builder_image, - *(["--verbose"] if log.isEnabledFor(logging.DEBUG) else []), - "run", - "/work/build.py", - "kernel", - ) - docker.fix_docker_ownership( - cfg, - [ - f"/work/mkosi.output/kernel/{cfg.kernel_version}/{cfg.arch}", - "/work/out", - ], - ) - - def _build_tools_stage(cfg: Config) -> None: """Run the tools download stage according to *cfg.tools_mode*.""" @@ -118,14 +60,12 @@ def _build_mkosi_stage(cfg: Config, extra_args: list[str]) -> None: raise SystemExit(1) log.info("Building initrd with mkosi (native)...") tools_tree = str(cfg.tools_output) - modules_tree = str(cfg.modules_output) output_dir = str(cfg.initramfs_output) run( [ "mkosi", f"--architecture={cfg.arch_info.mkosi_arch}", f"--extra-tree={tools_tree}", - *([f"--extra-tree={modules_tree}"] if cfg.build_kernel else []), f"--output-dir={output_dir}", "build", *mkosi_args, @@ -138,12 +78,10 @@ def _build_mkosi_stage(cfg: Config, extra_args: list[str]) -> None: docker.build_builder(cfg) log.info("Building initrd with mkosi (docker)...") tools_tree = f"/work/mkosi.output/tools/{cfg.arch}" - modules_tree = f"/work/mkosi.output/kernel/{cfg.kernel_version}/{cfg.arch}/modules" output_dir = f"/work/mkosi.output/initramfs/{cfg.kernel_version}/{cfg.arch}" docker.run_mkosi( cfg, f"--extra-tree={tools_tree}", - *([f"--extra-tree={modules_tree}"] if cfg.build_kernel else []), f"--output-dir={output_dir}", "--package-cache-dir=/cache/packages", "build", diff --git a/captain/config.py b/captain/config.py index da39bb6..523eaf2 100644 --- a/captain/config.py +++ b/captain/config.py @@ -31,24 +31,19 @@ class Config: # Target arch: str = "amd64" - build_kernel: bool = False kernel_version: str = DEFAULT_KERNEL_VERSION - kernel_config: str | None = None - kernel_src: str | None = None # Docker builder_image: str = "captainos-builder" no_cache: bool = False # Per-stage mode: "docker" | "native" | "skip" - kernel_mode: str = "docker" tools_mode: str = "docker" mkosi_mode: str = "docker" iso_mode: str = "docker" release_mode: str = "docker" # Force flags - force_kernel: bool = False force_tools: bool = False force_iso: bool = False @@ -67,7 +62,6 @@ def __post_init__(self) -> None: self.arch_info = get_arch_info(self.arch) self.arch = self.arch_info.arch # normalise aliases (x86_64 → amd64, etc.) for name, value in ( - ("KERNEL_MODE", self.kernel_mode), ("TOOLS_MODE", self.tools_mode), ("MKOSI_MODE", self.mkosi_mode), ("ISO_MODE", self.iso_mode), @@ -81,8 +75,7 @@ def __post_init__(self) -> None: def needs_docker(self) -> bool: """True if any stage requires Docker.""" return ( - self.kernel_mode == "docker" - or self.tools_mode == "docker" + self.tools_mode == "docker" or self.mkosi_mode == "docker" or self.iso_mode == "docker" or self.release_mode == "docker" @@ -107,18 +100,13 @@ def from_args(cls, args: argparse.Namespace, project_dir: Path | None) -> Config project_dir=project_dir, output_dir=project_dir / "out", arch=getattr(args, "arch", "amd64"), - build_kernel=getattr(args, "build_kernel", False), kernel_version=getattr(args, "kernel_version", DEFAULT_KERNEL_VERSION), - kernel_config=getattr(args, "kernel_config", None) or None, - kernel_src=getattr(args, "kernel_src", None) or None, builder_image=getattr(args, "builder_image", "captainos-builder"), no_cache=getattr(args, "no_cache", False), - kernel_mode=getattr(args, "kernel_mode", "docker"), tools_mode=getattr(args, "tools_mode", "docker"), mkosi_mode=getattr(args, "mkosi_mode", "docker"), iso_mode=getattr(args, "iso_mode", "docker"), release_mode=getattr(args, "release_mode", "docker"), - force_kernel=getattr(args, "force_kernel", False), force_tools=getattr(args, "force_tools", False), force_iso=getattr(args, "force_iso", False), qemu_append=getattr(args, "qemu_append", ""), @@ -138,18 +126,13 @@ def from_env(cls, project_dir: Path) -> Config: project_dir=project_dir, output_dir=project_dir / "out", arch=os.environ.get("ARCH", "amd64"), - build_kernel=os.environ.get("BUILD_KERNEL") == "1", kernel_version=os.environ.get("KERNEL_VERSION", DEFAULT_KERNEL_VERSION), - kernel_config=os.environ.get("KERNEL_CONFIG") or None, - kernel_src=os.environ.get("KERNEL_SRC") or None, builder_image=os.environ.get("BUILDER_IMAGE", "captainos-builder"), no_cache=os.environ.get("NO_CACHE") == "1", - kernel_mode=os.environ.get("KERNEL_MODE", "docker"), tools_mode=os.environ.get("TOOLS_MODE", "docker"), mkosi_mode=os.environ.get("MKOSI_MODE", "docker"), iso_mode=os.environ.get("ISO_MODE", "docker"), release_mode=os.environ.get("RELEASE_MODE", "docker"), - force_kernel=os.environ.get("FORCE_KERNEL") == "1", force_tools=os.environ.get("FORCE_TOOLS") == "1", force_iso=os.environ.get("FORCE_ISO") == "1", qemu_append=os.environ.get("QEMU_APPEND", ""), @@ -167,27 +150,6 @@ def tools_output(self) -> Path: """ return self.project_dir / "mkosi.output" / "tools" / self.arch - @property - def kernel_output(self) -> Path: - """Per-version, per-arch directory for all kernel build artifacts. - - Contains the vmlinuz image (loaded separately by iPXE) and - a ``modules/`` subtree that mirrors a root filesystem layout - (``usr/lib/modules/{kver}/``) so it can be passed directly - as an ``--extra-tree=`` to mkosi. - """ - return self.project_dir / "mkosi.output" / "kernel" / self.kernel_version / self.arch - - @property - def modules_output(self) -> Path: - """Per-version, per-arch root for kernel modules. - - Returns ``kernel/{version}/{arch}/modules`` which contains a - merged-usr tree (``usr/lib/modules/{kver}/``) suitable for - passing as ``--extra-tree=`` to mkosi. - """ - return self.kernel_output / "modules" - @property def mkosi_output(self) -> Path: return self.project_dir / "mkosi.output" diff --git a/captain/docker.py b/captain/docker.py index a77edda..c56cdc3 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -167,12 +167,8 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: "-e", f"FORCE_TOOLS={int(cfg.force_tools)}", "-e", - f"FORCE_KERNEL={int(cfg.force_kernel)}", - "-e", f"FORCE_ISO={int(cfg.force_iso)}", "-e", - "KERNEL_MODE=native", - "-e", "TOOLS_MODE=native", "-e", "MKOSI_MODE=native", @@ -202,32 +198,8 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: docker_args += ["-v", f"{cfg.project_dir}/pyproject.toml:/work/pyproject.toml"] docker_args += ["-v", f"{cfg.project_dir}/build.py:/work/build.py"] - docker_args += ["-v", f"{cfg.project_dir}/kernel.configs:/work/kernel.configs"] - docker_args += ["--mount", "type=volume,source=captain-cache-packages,target=/cache/packages"] - # Mount kernel source if provided - if cfg.kernel_src is not None: - kernel_src_path = Path(cfg.kernel_src).resolve() - if not kernel_src_path.is_dir(): - log.error("KERNEL_SRC=%s does not exist", cfg.kernel_src) - raise SystemExit(1) - docker_args.extend(["-v", f"{kernel_src_path}:/work/kernel-src:ro"]) - docker_args.extend(["-e", "KERNEL_SRC=/work/kernel-src"]) - - # Mount kernel config override and point KERNEL_CONFIG to the container path - if cfg.kernel_config is not None: - kernel_cfg_path = Path(cfg.kernel_config) - if not kernel_cfg_path.is_absolute(): - kernel_cfg_path = (cfg.project_dir / kernel_cfg_path).resolve() - else: - kernel_cfg_path = kernel_cfg_path.resolve() - if not kernel_cfg_path.is_file(): - log.error("KERNEL_CONFIG=%s does not exist", cfg.kernel_config) - raise SystemExit(1) - docker_args.extend(["-v", f"{kernel_cfg_path}:/work/kernel-config:ro"]) - docker_args.extend(["-e", "KERNEL_CONFIG=/work/kernel-config"]) - docker_args.extend(extra_args) run(docker_args) diff --git a/captain/kernel.py b/captain/kernel.py deleted file mode 100644 index a528b0c..0000000 --- a/captain/kernel.py +++ /dev/null @@ -1,278 +0,0 @@ -"""Kernel download, configuration, compilation, and installation. - -Heavy lifting (make, strip) is still done via subprocess — only the -orchestration is in Python. Called directly by ``cli._build_kernel_stage`` -in both native and Docker modes (inside the container ``build.py kernel`` -re-enters via the CLI with all modes forced to native). -""" - -from __future__ import annotations - -import logging -import os -import re -import shutil -import tarfile -import urllib.error -import urllib.request -from pathlib import Path - -from rich.progress import ( - BarColumn, - DownloadColumn, - Progress, - TextColumn, - TimeRemainingColumn, - TransferSpeedColumn, -) - -from captain import console -from captain.config import Config -from captain.util import ensure_dir, run, safe_extractall - -log = logging.getLogger(__name__) - -_DOWNLOAD_TIMEOUT = 60 # seconds - - -def _download_with_progress(url: str, filename: Path) -> None: - """Download *url* to *filename* with a Rich progress bar.""" - req = urllib.request.Request(url) - with urllib.request.urlopen(req, timeout=_DOWNLOAD_TIMEOUT) as resp: - total = int(resp.headers.get("Content-Length", 0)) or None - with Progress( - TextColumn("[progress.description]{task.description}"), - BarColumn(), - DownloadColumn(), - TransferSpeedColumn(), - TimeRemainingColumn(), - console=console, - ) as progress: - task = progress.add_task(" Downloading", total=total) - with open(filename, "wb") as out: - while True: - buf = resp.read(8192) - if not buf: - break - out.write(buf) - progress.update(task, advance=len(buf)) - - -def download_kernel(version: str, dest_dir: Path) -> Path: - """Download and extract a kernel tarball. Returns the source directory.""" - src_dir = dest_dir / f"linux-{version}" - if src_dir.is_dir(): - log.info("Using cached kernel source at %s", src_dir) - return src_dir - - major = version.split(".")[0] - url = f"https://cdn.kernel.org/pub/linux/kernel/v{major}.x/linux-{version}.tar.xz" - tarball = dest_dir / f"linux-{version}.tar.xz" - - log.info("Downloading kernel %s...", version) - log.info(" URL: %s", url) - ensure_dir(dest_dir) - try: - _download_with_progress(url, tarball) - except urllib.error.HTTPError as exc: - log.error("Download failed: %s — %s", exc, url) - raise SystemExit(1) from None - except urllib.error.URLError as exc: - log.error("Download failed: %s — %s", exc.reason, url) - raise SystemExit(1) from None - - log.info("Extracting kernel source...") - with tarfile.open(tarball, "r:xz") as tf: - safe_extractall(tf, path=dest_dir) - tarball.unlink() - - return src_dir - - -def _kernel_branch(version: str) -> str: - """Derive the stable branch prefix from a full kernel version.""" - parts = version.split(".") - if len(parts) < 2: - log.error("Invalid kernel version format: %s", version) - raise SystemExit(1) - return f"{parts[0]}.{parts[1]}.y" - - -def _find_defconfig(cfg: Config) -> Path: - """Locate the defconfig for the current kernel version and architecture.""" - if cfg.kernel_config: - explicit = Path(cfg.kernel_config) - if not explicit.is_absolute(): - explicit = cfg.project_dir / explicit - if explicit.is_file(): - return explicit - log.error("Kernel config not found: %s", explicit) - raise SystemExit(1) - - ai = cfg.arch_info - branch = _kernel_branch(cfg.kernel_version) - defconfig = cfg.project_dir / "kernel.configs" / f"{branch}.{ai.arch}" - if defconfig.is_file(): - return defconfig - - configs_dir = cfg.project_dir / "kernel.configs" - available = sorted( - { - p.name.rsplit(".", 1)[0] - for p in configs_dir.glob(f"*.{ai.arch}") - if not p.name.startswith(".") - } - ) - avail_str = ", ".join(available) if available else "(none)" - log.error( - "No kernel config found for %s on %s\n Expected: %s\n Available branches for %s: %s", - branch, - ai.arch, - defconfig, - ai.arch, - avail_str, - ) - raise SystemExit(1) - - -def configure_kernel(cfg: Config, src_dir: Path) -> None: - """Apply defconfig and run olddefconfig.""" - ai = cfg.arch_info - defconfig = _find_defconfig(cfg) - - make_env = {"ARCH": ai.kernel_arch} - if ai.cross_compile: - make_env["CROSS_COMPILE"] = ai.cross_compile - - log.info("Using defconfig: %s", defconfig) - shutil.copy2(defconfig, src_dir / ".config") - run(["make", "olddefconfig"], env=make_env, cwd=src_dir) - branch = _kernel_branch(cfg.kernel_version) - resolved = cfg.project_dir / "kernel.configs" / f".config.resolved.{branch}.{ai.arch}" - shutil.copy2(src_dir / ".config", resolved) - log.info("Resolved config saved to kernel.configs/.config.resolved.%s.%s", branch, ai.arch) - - if ai.kernel_arch == "x86_64": - log.info("Increasing COMMAND_LINE_SIZE to 4096 (x86_64)...") - setup_h = src_dir / "arch" / "x86" / "include" / "asm" / "setup.h" - text = setup_h.read_text() - new_text = re.sub( - r"#define COMMAND_LINE_SIZE\s+2048", - "#define COMMAND_LINE_SIZE 4096", - text, - ) - if new_text == text: - log.warning( - "COMMAND_LINE_SIZE patch did not match — the kernel default may have changed" - ) - setup_h.write_text(new_text) - - -def build_kernel(cfg: Config, src_dir: Path) -> str: - """Compile the kernel image and modules. Returns the built kernel version string.""" - ai = cfg.arch_info - nproc = os.cpu_count() or 1 - - make_env = {"ARCH": ai.kernel_arch} - if ai.cross_compile: - make_env["CROSS_COMPILE"] = ai.cross_compile - - log.info("Building kernel with %d jobs...", nproc) - run( - ["make", f"-j{nproc}", ai.image_target, "modules"], - env=make_env, - cwd=src_dir, - ) - - result = run( - ["make", "-s", "kernelrelease"], - env={"ARCH": ai.kernel_arch}, - capture=True, - cwd=src_dir, - ) - built_kver = result.stdout.strip() - log.info("Built kernel version: %s", built_kver) - return built_kver - - -def install_kernel(cfg: Config, src_dir: Path, built_kver: str) -> None: - """Install modules and vmlinuz into mkosi.output/kernel/{version}/{arch}/.""" - ai = cfg.arch_info - modules_root = cfg.modules_output - - make_env = {"ARCH": ai.kernel_arch} - if ai.cross_compile: - make_env["CROSS_COMPILE"] = ai.cross_compile - - log.info("Installing modules...") - run( - ["make", f"INSTALL_MOD_PATH={modules_root}", "modules_install"], - env=make_env, - cwd=src_dir, - ) - - log.info("Stripping debug symbols from modules...") - strip_cmd = f"{ai.strip_prefix}strip" - for ko in modules_root.rglob("*.ko"): - run([strip_cmd, "--strip-unneeded", str(ko)], check=False) - - log.info("Compressing kernel modules with zstd...") - for ko in modules_root.rglob("*.ko"): - run(["zstd", "--rm", "-q", "-19", str(ko)], check=True) - - mod_base = modules_root / "lib" / "modules" / built_kver - (mod_base / "build").unlink(missing_ok=True) - (mod_base / "source").unlink(missing_ok=True) - - usr_moddir = ensure_dir(modules_root / "usr" / "lib" / "modules" / built_kver) - if mod_base.is_dir(): - for item in mod_base.iterdir(): - dest = usr_moddir / item.name - if dest.exists(): - if dest.is_dir(): - shutil.rmtree(dest) - else: - dest.unlink() - shutil.move(str(item), str(dest)) - shutil.rmtree(modules_root / "lib", ignore_errors=True) - - log.info("Running depmod for compressed modules...") - run( - ["depmod", "-a", "-b", str(modules_root / "usr"), built_kver], - check=True, - ) - - kernel_image = src_dir / ai.kernel_image_path - vmlinuz_dir = ensure_dir(cfg.kernel_output) - - for old in vmlinuz_dir.glob("vmlinuz-*"): - old.unlink(missing_ok=True) - - shutil.copy2(kernel_image, vmlinuz_dir / f"vmlinuz-{built_kver}") - - log.info("Kernel build complete:") - vmlinuz = vmlinuz_dir / f"vmlinuz-{built_kver}" - vmlinuz_size = vmlinuz.stat().st_size / (1024 * 1024) - log.info(" Image: %s (%.1fM)", vmlinuz, vmlinuz_size) - log.info(" Modules: %s/", usr_moddir) - log.info(" Version: %s", built_kver) - log.info(" Output: %s", cfg.kernel_output) - - -def build(cfg: Config) -> None: - """Full kernel build pipeline — download, configure, build, install.""" - if cfg.kernel_output.exists(): - shutil.rmtree(cfg.kernel_output) - ensure_dir(cfg.kernel_output) - - build_dir = Path("/var/tmp/kernel-build") - - if cfg.kernel_src and Path(cfg.kernel_src).is_dir(): - log.info("Using provided kernel source at %s", cfg.kernel_src) - src_dir = Path(cfg.kernel_src) - else: - src_dir = download_kernel(cfg.kernel_version, build_dir) - - configure_kernel(cfg, src_dir) - built_kver = build_kernel(cfg, src_dir) - install_kernel(cfg, src_dir, built_kver) diff --git a/captain/oci/_build.py b/captain/oci/_build.py index 4b0a22b..23776ee 100644 --- a/captain/oci/_build.py +++ b/captain/oci/_build.py @@ -4,7 +4,6 @@ import contextlib import logging -import shutil import tarfile from datetime import datetime from pathlib import Path @@ -46,16 +45,18 @@ def _collect_arch_artifacts( Returns [vmlinuz, initramfs, iso, checksums] paths in *out*. """ - # Collect kernel - vmlinuz_dir = project_dir / "mkosi.output" / "kernel" / kernel_version / arch - vmlinuz_files = sorted(vmlinuz_dir.glob("vmlinuz-*")) if vmlinuz_dir.is_dir() else [] oarch = get_arch_info(arch).output_arch - vmlinuz_dst = out / f"vmlinuz-{kernel_version}-{oarch}" - if vmlinuz_files: - shutil.copy2(vmlinuz_files[0], vmlinuz_dst) - log.info("kernel: %s", vmlinuz_dst) - else: - log.warning("No kernel image found for %s", arch) + + ## # Collect kernel @TODO probably fix or remove? + ## vmlinuz_dir = project_dir / "mkosi.output" / "kernel" / kernel_version / arch + ## vmlinuz_files = sorted(vmlinuz_dir.glob("vmlinuz-*")) if vmlinuz_dir.is_dir() else [] + ## + ## vmlinuz_dst = out / f"vmlinuz-{kernel_version}-{oarch}" + ## if vmlinuz_files: + ## shutil.copy2(vmlinuz_files[0], vmlinuz_dst) + ## log.info("kernel: %s", vmlinuz_dst) + ## else: + ## log.warning("No kernel image found for %s", arch) arch_files = [ out / f"vmlinuz-{kernel_version}-{oarch}", diff --git a/captain/qemu.py b/captain/qemu.py index d8af60f..bbbd8d5 100644 --- a/captain/qemu.py +++ b/captain/qemu.py @@ -77,7 +77,6 @@ def run_qemu(cfg: Config, args: argparse.Namespace | None = None) -> None: log.error("Build artifacts not found:") for m in missing: log.error(" %s", m) - log.error("Run './build.py --kernel-version %s' first.", cfg.kernel_version) sys.exit(1) tink = _tink_cmdline(args) if args is not None else "" diff --git a/captain/util.py b/captain/util.py index e1c150e..797f8fa 100644 --- a/captain/util.py +++ b/captain/util.py @@ -24,14 +24,9 @@ class ArchInfo: arch: str # canonical name: amd64 | arm64 output_arch: str # user-facing name in artifact filenames: x86_64 | aarch64 - kernel_arch: str # kernel ARCH value - cross_compile: str # CROSS_COMPILE prefix (empty for native) - image_target: str # kernel image make target - kernel_image_path: str # relative path to built kernel image dl_arch: str # architecture name in download URLs mkosi_arch: str # mkosi --architecture value qemu_binary: str # QEMU system emulator binary - strip_prefix: str # prefix for strip command def get_arch_info(arch: str) -> ArchInfo: @@ -41,27 +36,17 @@ def get_arch_info(arch: str) -> ArchInfo: return ArchInfo( arch="amd64", output_arch="x86_64", - kernel_arch="x86_64", - cross_compile="", - image_target="bzImage", - kernel_image_path="arch/x86/boot/bzImage", dl_arch="amd64", mkosi_arch="x86-64", qemu_binary="qemu-system-x86_64", - strip_prefix="", ) case "arm64" | "aarch64": return ArchInfo( arch="arm64", output_arch="aarch64", - kernel_arch="arm64", - cross_compile="aarch64-linux-gnu-", - image_target="Image", - kernel_image_path="arch/arm64/boot/Image", dl_arch="arm64", mkosi_arch="arm64", qemu_binary="qemu-system-aarch64", - strip_prefix="aarch64-linux-gnu-", ) case _: log.error("Unsupported architecture: %s", arch) diff --git a/kernel.configs/6.18.y.amd64 b/kernel.configs/6.18.y.amd64 deleted file mode 100644 index c6d243c..0000000 --- a/kernel.configs/6.18.y.amd64 +++ /dev/null @@ -1,1032 +0,0 @@ -# CaptainOS kernel defconfig for x86_64 -# Comprehensive config for bare-metal provisioning with container/network support. -# Derived from HookOS generic-6.6.y-x86_64 proven config, adapted for kernel 6.18. -# -# Generate a full .config from this: -# cp 6.18.y.amd64 .config && make olddefconfig - -# --- Architecture --- -CONFIG_64BIT=y -CONFIG_X86_64=y -CONFIG_SMP=y -CONFIG_NR_CPUS=128 -# CONFIG_X86_EXTENDED_PLATFORM is not set -CONFIG_X86_INTEL_LPSS=y -CONFIG_HYPERVISOR_GUEST=y -CONFIG_PARAVIRT_SPINLOCKS=y -CONFIG_XEN=y -CONFIG_XEN_PVH=y -CONFIG_X86_REROUTE_FOR_BROKEN_BOOT_IRQS=y -# CONFIG_X86_MCE is not set -CONFIG_X86_MSR=y -CONFIG_X86_CPUID=y -# CONFIG_X86_5LEVEL is not set -CONFIG_HZ_1000=y -CONFIG_PHYSICAL_ALIGN=0x1000000 -CONFIG_LEGACY_VSYSCALL_NONE=y -# CONFIG_MODIFY_LDT_SYSCALL is not set -CONFIG_IA32_EMULATION=y - -# --- General --- -CONFIG_LOCALVERSION="-captainos" -# CONFIG_LOCALVERSION_AUTO is not set -CONFIG_DEFAULT_HOSTNAME="captainos" -CONFIG_SYSVIPC=y -CONFIG_POSIX_MQUEUE=y -CONFIG_AUDIT=y -CONFIG_NO_HZ_IDLE=y -CONFIG_HIGH_RES_TIMERS=y -CONFIG_PREEMPT_VOLUNTARY=y -CONFIG_BSD_PROCESS_ACCT=y -CONFIG_BSD_PROCESS_ACCT_V3=y -CONFIG_IKCONFIG=y -CONFIG_IKCONFIG_PROC=y -CONFIG_CC_OPTIMIZE_FOR_SIZE=y -CONFIG_EXPERT=y -CONFIG_KEXEC=y -CONFIG_KEXEC_FILE=y -CONFIG_SCHED_AUTOGROUP=y -CONFIG_CHECKPOINT_RESTORE=y -CONFIG_KPROBES=y -CONFIG_JUMP_LABEL=y -CONFIG_BINFMT_MISC=y - -# --- Cgroups --- -CONFIG_CGROUPS=y -CONFIG_CGROUP_FREEZER=y -CONFIG_CGROUP_PIDS=y -CONFIG_CGROUP_RDMA=y -CONFIG_CGROUP_DEVICE=y -CONFIG_CGROUP_CPUACCT=y -CONFIG_CGROUP_PERF=y -CONFIG_CGROUP_BPF=y -CONFIG_CGROUP_MISC=y -CONFIG_CGROUP_HUGETLB=y -CONFIG_CGROUP_NET_PRIO=y -CONFIG_MEMCG=y -CONFIG_BLK_CGROUP=y -CONFIG_CFS_BANDWIDTH=y -CONFIG_RT_GROUP_SCHED=y -CONFIG_CGROUP_SCHED=y -CONFIG_CPUSETS=y - -# --- Namespaces --- -CONFIG_NAMESPACES=y -CONFIG_USER_NS=y -CONFIG_PID_NS=y -CONFIG_NET_NS=y -CONFIG_UTS_NS=y -CONFIG_IPC_NS=y - -# --- Init / Initramfs --- -CONFIG_BLK_DEV_INITRD=y -CONFIG_INITRAMFS_SOURCE="" -# CONFIG_RD_BZIP2 is not set -# CONFIG_RD_LZMA is not set -CONFIG_RD_GZIP=y -CONFIG_RD_XZ=y -# CONFIG_RD_LZO is not set -# CONFIG_RD_LZ4 is not set -CONFIG_RD_ZSTD=y - -# --- Memory management --- -CONFIG_SLAB_FREELIST_RANDOM=y -# CONFIG_COMPAT_BRK is not set -CONFIG_MEMORY_HOTPLUG=y -CONFIG_MEMORY_HOTREMOVE=y -CONFIG_KSM=y -CONFIG_DEFAULT_MMAP_MIN_ADDR=65536 -CONFIG_TRANSPARENT_HUGEPAGE=y -CONFIG_ZONE_DEVICE=y -CONFIG_HUGETLBFS=y - -# --- Power / Suspend --- -# CONFIG_SUSPEND is not set - -# --- ACPI --- -CONFIG_ACPI=y -# CONFIG_ACPI_REV_OVERRIDE_POSSIBLE is not set -CONFIG_ACPI_DOCK=y -CONFIG_ACPI_IPMI=y -CONFIG_ACPI_PROCESSOR_AGGREGATOR=y -CONFIG_ACPI_SBS=y -CONFIG_ACPI_NFIT=y -CONFIG_ACPI_APEI=y -CONFIG_ACPI_APEI_GHES=y - -# --- CPU frequency --- -CONFIG_CPU_FREQ_STAT=y -CONFIG_CPU_FREQ_GOV_POWERSAVE=y -CONFIG_CPU_FREQ_GOV_USERSPACE=y -CONFIG_CPU_FREQ_GOV_ONDEMAND=y -CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y -CONFIG_X86_PCC_CPUFREQ=y -CONFIG_X86_ACPI_CPUFREQ=y -CONFIG_X86_POWERNOW_K8=y -CONFIG_X86_P4_CLOCKMOD=y -CONFIG_CPU_IDLE_GOV_LADDER=y -CONFIG_INTEL_IDLE=y - -# --- KVM --- -CONFIG_KVM=m -CONFIG_KVM_INTEL=m -CONFIG_KVM_AMD=m - -# --- Networking --- -CONFIG_NET=y -CONFIG_PACKET=y -CONFIG_PACKET_DIAG=y -CONFIG_UNIX=y -CONFIG_UNIX_DIAG=y -CONFIG_XFRM_USER=m -CONFIG_XFRM_SUB_POLICY=y -CONFIG_XFRM_STATISTICS=y -CONFIG_NET_KEY=m -CONFIG_NET_KEY_MIGRATE=y -CONFIG_XDP_SOCKETS=y -CONFIG_INET=y -CONFIG_IP_MULTICAST=y -CONFIG_IP_ADVANCED_ROUTER=y -CONFIG_IP_FIB_TRIE_STATS=y -CONFIG_IP_MULTIPLE_TABLES=y -CONFIG_IP_ROUTE_MULTIPATH=y -CONFIG_IP_ROUTE_VERBOSE=y -CONFIG_IP_PNP=y -CONFIG_IP_PNP_DHCP=y -CONFIG_IP_PNP_BOOTP=y -CONFIG_NET_IPIP=y -CONFIG_NET_IPGRE_DEMUX=y -CONFIG_NET_IPGRE=m -CONFIG_NET_IPGRE_BROADCAST=y -CONFIG_IP_MROUTE=y -CONFIG_IP_MROUTE_MULTIPLE_TABLES=y -CONFIG_IP_PIMSM_V1=y -CONFIG_IP_PIMSM_V2=y -CONFIG_NET_IPVTI=m -CONFIG_NET_FOU_IP_TUNNELS=y -CONFIG_INET_AH=m -CONFIG_INET_ESP=m -CONFIG_INET_IPCOMP=m -CONFIG_INET_UDP_DIAG=y -CONFIG_TCP_MD5SIG=y -CONFIG_SYN_COOKIES=y -CONFIG_IPV6=y -CONFIG_IPV6_ROUTER_PREF=y -CONFIG_INET6_AH=m -CONFIG_INET6_ESP=m -CONFIG_INET6_IPCOMP=m -CONFIG_IPV6_MIP6=m -CONFIG_IPV6_ILA=m -CONFIG_IPV6_VTI=m -CONFIG_IPV6_SIT=m -CONFIG_IPV6_SIT_6RD=y -CONFIG_IPV6_GRE=m -CONFIG_IPV6_MULTIPLE_TABLES=y -CONFIG_IPV6_SUBTREES=y -CONFIG_NETLABEL=y -CONFIG_NETWORK_SECMARK=y -CONFIG_BRIDGE=y -CONFIG_BRIDGE_VLAN_FILTERING=y -CONFIG_VLAN_8021Q=y -CONFIG_BONDING=m -CONFIG_TUN=y -CONFIG_VETH=y -CONFIG_MACVLAN=y -CONFIG_MACVTAP=y -CONFIG_IPVLAN=y -CONFIG_VXLAN=y -CONFIG_GENEVE=m -CONFIG_NETCONSOLE=y -CONFIG_TAP=y -CONFIG_DUMMY=m -CONFIG_NLMON=y -CONFIG_IP_SCTP=m -CONFIG_L2TP=m -CONFIG_NET_TEAM=m -CONFIG_NET_TEAM_MODE_BROADCAST=m -CONFIG_NET_TEAM_MODE_ROUNDROBIN=m -CONFIG_NET_TEAM_MODE_RANDOM=m -CONFIG_NET_TEAM_MODE_ACTIVEBACKUP=m -CONFIG_NET_TEAM_MODE_LOADBALANCE=m -CONFIG_OPENVSWITCH=m -CONFIG_VSOCKETS=m -CONFIG_VIRTIO_VSOCKETS=m -CONFIG_HYPERV_VSOCKETS=m -CONFIG_NETLINK_DIAG=y -CONFIG_MPLS_ROUTING=m -CONFIG_MPLS_IPTUNNEL=m -CONFIG_NET_SWITCHDEV=y -CONFIG_NET_9P=y -CONFIG_NET_9P_VIRTIO=y - -# --- Traffic control --- -CONFIG_NET_SCHED=y -CONFIG_NET_SCH_HTB=m -CONFIG_NET_SCH_HFSC=m -CONFIG_NET_SCH_PRIO=m -CONFIG_NET_SCH_MULTIQ=m -CONFIG_NET_SCH_RED=m -CONFIG_NET_SCH_SFB=m -CONFIG_NET_SCH_SFQ=m -CONFIG_NET_SCH_TEQL=m -CONFIG_NET_SCH_TBF=m -CONFIG_NET_SCH_GRED=m -CONFIG_NET_SCH_NETEM=m -CONFIG_NET_SCH_DRR=m -CONFIG_NET_SCH_MQPRIO=m -CONFIG_NET_SCH_CHOKE=m -CONFIG_NET_SCH_QFQ=m -CONFIG_NET_SCH_INGRESS=m -CONFIG_NET_CLS_BASIC=y -CONFIG_NET_CLS_ROUTE4=y -CONFIG_NET_CLS_FW=y -CONFIG_NET_CLS_U32=y -CONFIG_CLS_U32_PERF=y -CONFIG_CLS_U32_MARK=y -CONFIG_NET_CLS_FLOW=y -CONFIG_NET_CLS_CGROUP=y -CONFIG_NET_CLS_BPF=y -CONFIG_NET_CLS_MATCHALL=y -CONFIG_NET_EMATCH=y -CONFIG_NET_EMATCH_CMP=y -CONFIG_NET_EMATCH_NBYTE=y -CONFIG_NET_EMATCH_U32=y -CONFIG_NET_EMATCH_META=y -CONFIG_NET_EMATCH_TEXT=y -CONFIG_NET_EMATCH_IPSET=y -CONFIG_NET_CLS_ACT=y -CONFIG_NET_ACT_POLICE=y -CONFIG_NET_ACT_GACT=y -CONFIG_GACT_PROB=y -CONFIG_NET_ACT_MIRRED=y -CONFIG_NET_ACT_IPT=y -CONFIG_NET_ACT_NAT=y -CONFIG_NET_ACT_PEDIT=y -CONFIG_NET_ACT_SIMP=y -CONFIG_NET_ACT_SKBEDIT=y -CONFIG_NET_ACT_CSUM=y -CONFIG_NET_ACT_BPF=y - -# --- Netfilter --- -CONFIG_NETFILTER=y -CONFIG_NETFILTER_ADVANCED=y -CONFIG_BRIDGE_NETFILTER=y -CONFIG_NF_CONNTRACK=y -CONFIG_NF_CONNTRACK_ZONES=y -CONFIG_NF_CONNTRACK_PROCFS=y -CONFIG_NF_CONNTRACK_EVENTS=y -CONFIG_NF_CONNTRACK_TIMEOUT=y -CONFIG_NF_CONNTRACK_TIMESTAMP=y -CONFIG_NF_CONNTRACK_AMANDA=y -CONFIG_NF_CONNTRACK_FTP=y -CONFIG_NF_CONNTRACK_H323=y -CONFIG_NF_CONNTRACK_IRC=y -CONFIG_NF_CONNTRACK_NETBIOS_NS=y -CONFIG_NF_CONNTRACK_SNMP=y -CONFIG_NF_CONNTRACK_PPTP=y -CONFIG_NF_CONNTRACK_SANE=y -CONFIG_NF_CONNTRACK_SIP=y -CONFIG_NF_CONNTRACK_TFTP=y -CONFIG_NF_CT_NETLINK=y -CONFIG_NF_CT_NETLINK_TIMEOUT=y -CONFIG_NF_CT_NETLINK_HELPER=y -CONFIG_NETFILTER_NETLINK_GLUE_CT=y -CONFIG_NF_NAT=y -CONFIG_NF_TABLES=y -CONFIG_NF_TABLES_INET=y -CONFIG_NF_TABLES_NETDEV=y -CONFIG_NFT_CT=y -CONFIG_NFT_CONNLIMIT=y -CONFIG_NFT_LOG=y -CONFIG_NFT_LIMIT=y -CONFIG_NFT_MASQ=y -CONFIG_NFT_REDIR=y -CONFIG_NFT_NAT=y -CONFIG_NFT_TUNNEL=y -CONFIG_NFT_QUEUE=y -CONFIG_NFT_REJECT=y -CONFIG_NFT_COMPAT=y -CONFIG_NFT_HASH=y -CONFIG_NFT_OSF=y -CONFIG_NFT_TPROXY=y -CONFIG_NFT_DUP_NETDEV=y -CONFIG_NFT_FWD_NETDEV=y -CONFIG_NETFILTER_XT_SET=y -CONFIG_NETFILTER_XT_TARGET_CHECKSUM=y -CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y -CONFIG_NETFILTER_XT_TARGET_CONNMARK=y -CONFIG_NETFILTER_XT_TARGET_DSCP=y -CONFIG_NETFILTER_XT_TARGET_HMARK=y -CONFIG_NETFILTER_XT_TARGET_IDLETIMER=y -CONFIG_NETFILTER_XT_TARGET_LOG=y -CONFIG_NETFILTER_XT_TARGET_MARK=y -CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y -CONFIG_NETFILTER_XT_TARGET_NFLOG=y -CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y -CONFIG_NETFILTER_XT_TARGET_NOTRACK=y -CONFIG_NETFILTER_XT_TARGET_TEE=y -CONFIG_NETFILTER_XT_TARGET_TPROXY=y -CONFIG_NETFILTER_XT_TARGET_TRACE=y -CONFIG_NETFILTER_XT_TARGET_TCPMSS=y -CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=y -CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y -CONFIG_NETFILTER_XT_MATCH_BPF=y -CONFIG_NETFILTER_XT_MATCH_CGROUP=y -CONFIG_NETFILTER_XT_MATCH_CLUSTER=y -CONFIG_NETFILTER_XT_MATCH_COMMENT=y -CONFIG_NETFILTER_XT_MATCH_CONNBYTES=y -CONFIG_NETFILTER_XT_MATCH_CONNLABEL=y -CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y -CONFIG_NETFILTER_XT_MATCH_CONNMARK=y -CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y -CONFIG_NETFILTER_XT_MATCH_CPU=y -CONFIG_NETFILTER_XT_MATCH_DCCP=y -CONFIG_NETFILTER_XT_MATCH_DEVGROUP=y -CONFIG_NETFILTER_XT_MATCH_DSCP=y -CONFIG_NETFILTER_XT_MATCH_ESP=y -CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y -CONFIG_NETFILTER_XT_MATCH_HELPER=y -CONFIG_NETFILTER_XT_MATCH_IPCOMP=y -CONFIG_NETFILTER_XT_MATCH_IPRANGE=y -CONFIG_NETFILTER_XT_MATCH_IPVS=y -CONFIG_NETFILTER_XT_MATCH_L2TP=y -CONFIG_NETFILTER_XT_MATCH_LENGTH=y -CONFIG_NETFILTER_XT_MATCH_LIMIT=y -CONFIG_NETFILTER_XT_MATCH_MAC=y -CONFIG_NETFILTER_XT_MATCH_MARK=y -CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y -CONFIG_NETFILTER_XT_MATCH_NFACCT=y -CONFIG_NETFILTER_XT_MATCH_OSF=y -CONFIG_NETFILTER_XT_MATCH_OWNER=y -CONFIG_NETFILTER_XT_MATCH_POLICY=y -CONFIG_NETFILTER_XT_MATCH_PHYSDEV=y -CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y -CONFIG_NETFILTER_XT_MATCH_QUOTA=y -CONFIG_NETFILTER_XT_MATCH_RATEEST=y -CONFIG_NETFILTER_XT_MATCH_REALM=y -CONFIG_NETFILTER_XT_MATCH_RECENT=y -CONFIG_NETFILTER_XT_MATCH_SCTP=y -CONFIG_NETFILTER_XT_MATCH_SOCKET=y -CONFIG_NETFILTER_XT_MATCH_STATE=y -CONFIG_NETFILTER_XT_MATCH_STATISTIC=y -CONFIG_NETFILTER_XT_MATCH_STRING=y -CONFIG_NETFILTER_XT_MATCH_TCPMSS=y -CONFIG_NETFILTER_XT_MATCH_TIME=y -CONFIG_NETFILTER_XT_MATCH_U32=y -CONFIG_IP_SET=y -CONFIG_IP_SET_BITMAP_IP=y -CONFIG_IP_SET_BITMAP_IPMAC=y -CONFIG_IP_SET_BITMAP_PORT=y -CONFIG_IP_SET_HASH_IP=y -CONFIG_IP_SET_HASH_IPPORT=y -CONFIG_IP_SET_HASH_IPPORTIP=y -CONFIG_IP_SET_HASH_IPPORTNET=y -CONFIG_IP_SET_HASH_NET=y -CONFIG_IP_SET_HASH_NETPORT=y -CONFIG_IP_SET_HASH_NETIFACE=y -CONFIG_IP_SET_LIST_SET=y -CONFIG_IP_VS=y -CONFIG_IP_VS_IPV6=y -CONFIG_IP_VS_PROTO_TCP=y -CONFIG_IP_VS_PROTO_UDP=y -CONFIG_IP_VS_PROTO_ESP=y -CONFIG_IP_VS_PROTO_AH=y -CONFIG_IP_VS_PROTO_SCTP=y -CONFIG_IP_VS_RR=y -CONFIG_IP_VS_WRR=y -CONFIG_IP_VS_LC=y -CONFIG_IP_VS_WLC=y -CONFIG_IP_VS_FO=y -CONFIG_IP_VS_OVF=y -CONFIG_IP_VS_LBLC=y -CONFIG_IP_VS_LBLCR=y -CONFIG_IP_VS_DH=y -CONFIG_IP_VS_SH=y -CONFIG_IP_VS_MH=y -CONFIG_IP_VS_SED=y -CONFIG_IP_VS_NQ=y -CONFIG_IP_VS_FTP=y -CONFIG_NFT_DUP_IPV4=y -CONFIG_NF_TABLES_ARP=y -CONFIG_NF_LOG_ARP=y -CONFIG_IP_NF_IPTABLES=y -CONFIG_IP_NF_MATCH_AH=y -CONFIG_IP_NF_MATCH_ECN=y -CONFIG_IP_NF_MATCH_RPFILTER=y -CONFIG_IP_NF_MATCH_TTL=y -CONFIG_IP_NF_FILTER=y -CONFIG_IP_NF_TARGET_REJECT=y -CONFIG_IP_NF_TARGET_SYNPROXY=y -CONFIG_IP_NF_NAT=y -CONFIG_IP_NF_TARGET_MASQUERADE=y -CONFIG_IP_NF_TARGET_NETMAP=y -CONFIG_IP_NF_TARGET_REDIRECT=y -CONFIG_IP_NF_MANGLE=y -CONFIG_IP_NF_TARGET_ECN=y -CONFIG_IP_NF_TARGET_TTL=y -CONFIG_IP_NF_RAW=y -CONFIG_IP_NF_SECURITY=y -CONFIG_IP_NF_ARPTABLES=y -CONFIG_IP_NF_ARPFILTER=y -CONFIG_IP_NF_ARP_MANGLE=y -CONFIG_NFT_DUP_IPV6=y -CONFIG_IP6_NF_IPTABLES=y -CONFIG_IP6_NF_MATCH_AH=y -CONFIG_IP6_NF_MATCH_EUI64=y -CONFIG_IP6_NF_MATCH_FRAG=y -CONFIG_IP6_NF_MATCH_OPTS=y -CONFIG_IP6_NF_MATCH_HL=y -CONFIG_IP6_NF_MATCH_IPV6HEADER=y -CONFIG_IP6_NF_MATCH_MH=y -CONFIG_IP6_NF_MATCH_RPFILTER=y -CONFIG_IP6_NF_MATCH_RT=y -CONFIG_IP6_NF_TARGET_HL=y -CONFIG_IP6_NF_FILTER=y -CONFIG_IP6_NF_TARGET_REJECT=y -CONFIG_IP6_NF_TARGET_SYNPROXY=y -CONFIG_IP6_NF_MANGLE=y -CONFIG_IP6_NF_RAW=y -CONFIG_IP6_NF_SECURITY=y -CONFIG_IP6_NF_NAT=y -CONFIG_IP6_NF_TARGET_MASQUERADE=y -CONFIG_IP6_NF_TARGET_NPT=y -CONFIG_NF_TABLES_BRIDGE=y -CONFIG_NFT_BRIDGE_REJECT=y -CONFIG_BRIDGE_NF_EBTABLES=y -CONFIG_BRIDGE_EBT_BROUTE=y -CONFIG_BRIDGE_EBT_T_FILTER=y -CONFIG_BRIDGE_EBT_T_NAT=y -CONFIG_BRIDGE_EBT_802_3=y -CONFIG_BRIDGE_EBT_AMONG=y -CONFIG_BRIDGE_EBT_ARP=y -CONFIG_BRIDGE_EBT_IP=y -CONFIG_BRIDGE_EBT_IP6=y -CONFIG_BRIDGE_EBT_LIMIT=y -CONFIG_BRIDGE_EBT_MARK=y -CONFIG_BRIDGE_EBT_PKTTYPE=y -CONFIG_BRIDGE_EBT_STP=y -CONFIG_BRIDGE_EBT_VLAN=y -CONFIG_BRIDGE_EBT_ARPREPLY=y -CONFIG_BRIDGE_EBT_DNAT=y -CONFIG_BRIDGE_EBT_MARK_T=y -CONFIG_BRIDGE_EBT_REDIRECT=y -CONFIG_BRIDGE_EBT_SNAT=y -CONFIG_BRIDGE_EBT_LOG=y -CONFIG_BRIDGE_EBT_NFLOG=y - -# --- Block devices --- -CONFIG_BLK_DEV=y -CONFIG_BLK_DEV_INTEGRITY=y -CONFIG_BLK_DEV_THROTTLING=y -CONFIG_BLK_CGROUP_IOLATENCY=y -CONFIG_PARTITION_ADVANCED=y -CONFIG_LDM_PARTITION=y -CONFIG_CMDLINE_PARTITION=y -CONFIG_BLK_DEV_LOOP=y -CONFIG_BLK_DEV_DRBD=m -CONFIG_BLK_DEV_NBD=y -CONFIG_BLK_DEV_RAM=y -CONFIG_BLK_DEV_RAM_SIZE=65536 -CONFIG_ATA_OVER_ETH=m -CONFIG_BLK_DEV_NVME=y - -# --- SCSI --- -CONFIG_SCSI=y -# CONFIG_SCSI_PROC_FS is not set -CONFIG_BLK_DEV_SD=y -CONFIG_BLK_DEV_SR=y -CONFIG_CHR_DEV_SG=y -CONFIG_ISCSI_TCP=m -CONFIG_SCSI_HPSA=y -CONFIG_MEGARAID_SAS=y -CONFIG_SCSI_MPT3SAS=y -CONFIG_SCSI_MPI3MR=m -CONFIG_SCSI_SMARTPQI=m -CONFIG_VMWARE_PVSCSI=y -CONFIG_XEN_SCSI_FRONTEND=y -CONFIG_SCSI_VIRTIO=y - -# --- ATA / SATA --- -CONFIG_ATA=y -# CONFIG_ATA_VERBOSE_ERROR is not set -# CONFIG_SATA_PMP is not set -CONFIG_SATA_AHCI=y -CONFIG_ATA_PIIX=y -CONFIG_SATA_MV=y -CONFIG_SATA_NV=y -CONFIG_SATA_PROMISE=y -CONFIG_SATA_SIL=y -CONFIG_SATA_SIS=y -CONFIG_SATA_SVW=y -CONFIG_SATA_ULI=y -CONFIG_SATA_VIA=y -CONFIG_SATA_VITESSE=y -CONFIG_ATA_GENERIC=y - -# --- Device mapper / MD --- -CONFIG_MD=y -CONFIG_BLK_DEV_MD=y -CONFIG_MD_LINEAR=y -CONFIG_MD_RAID0=y -CONFIG_MD_RAID1=y -CONFIG_MD_RAID10=y -CONFIG_MD_RAID456=y -CONFIG_MD_MULTIPATH=y -CONFIG_BLK_DEV_DM=y -CONFIG_DM_CRYPT=y -CONFIG_DM_SNAPSHOT=y -CONFIG_DM_THIN_PROVISIONING=y -CONFIG_DM_MULTIPATH=m -CONFIG_DM_MULTIPATH_QL=m -CONFIG_DM_MULTIPATH_ST=m - -# --- Fusion --- -CONFIG_FUSION=y -CONFIG_FUSION_SPI=y - -# --- Network drivers (built-in for reliable boot) --- -CONFIG_NETDEVICES=y -# CONFIG_NET_VENDOR_3COM is not set -# CONFIG_NET_VENDOR_ADAPTEC is not set -# CONFIG_NET_VENDOR_AGERE is not set -# CONFIG_NET_VENDOR_ALACRITECH is not set -# CONFIG_NET_VENDOR_ALTEON is not set -# CONFIG_NET_VENDOR_AMD is not set -# CONFIG_NET_VENDOR_AQUANTIA is not set -# CONFIG_NET_VENDOR_ARC is not set -# CONFIG_NET_VENDOR_ATHEROS is not set -# CONFIG_NET_VENDOR_CADENCE is not set -# CONFIG_NET_VENDOR_CAVIUM is not set -# CONFIG_NET_VENDOR_CHELSIO is not set -# CONFIG_NET_VENDOR_CORTINA is not set -# CONFIG_NET_VENDOR_DEC is not set -# CONFIG_NET_VENDOR_DLINK is not set -# CONFIG_NET_VENDOR_EMULEX is not set -# CONFIG_NET_VENDOR_EZCHIP is not set -# CONFIG_NET_VENDOR_HUAWEI is not set -# CONFIG_NET_VENDOR_I825XX is not set -# CONFIG_NET_VENDOR_MARVELL is not set -# CONFIG_NET_VENDOR_MICREL is not set -# CONFIG_NET_VENDOR_MICROCHIP is not set -# CONFIG_NET_VENDOR_MYRI is not set -# CONFIG_NET_VENDOR_NI is not set -# CONFIG_NET_VENDOR_NATSEMI is not set -# CONFIG_NET_VENDOR_NETERION is not set -# CONFIG_NET_VENDOR_NVIDIA is not set -# CONFIG_NET_VENDOR_OKI is not set -# CONFIG_NET_VENDOR_PACKET_ENGINES is not set -# CONFIG_NET_VENDOR_QLOGIC is not set -# CONFIG_NET_VENDOR_BROCADE is not set -# CONFIG_NET_VENDOR_QUALCOMM is not set -# CONFIG_NET_VENDOR_RDC is not set -# CONFIG_NET_VENDOR_RENESAS is not set -# CONFIG_NET_VENDOR_ROCKER is not set -# CONFIG_NET_VENDOR_SAMSUNG is not set -# CONFIG_NET_VENDOR_SEEQ is not set -# CONFIG_NET_VENDOR_SILAN is not set -# CONFIG_NET_VENDOR_SIS is not set -# CONFIG_NET_VENDOR_SMSC is not set -# CONFIG_NET_VENDOR_SOCIONEXT is not set -# CONFIG_NET_VENDOR_STMICRO is not set -# CONFIG_NET_VENDOR_SUN is not set -# CONFIG_NET_VENDOR_SYNOPSYS is not set -# CONFIG_NET_VENDOR_TEHUTI is not set -# CONFIG_NET_VENDOR_TI is not set -# CONFIG_NET_VENDOR_VIA is not set -# CONFIG_NET_VENDOR_WIZNET is not set -CONFIG_E1000=y -CONFIG_E1000E=y -CONFIG_IGB=y -CONFIG_IGBVF=y -CONFIG_IXGBE=y -CONFIG_IXGBEVF=y -CONFIG_I40E=y -CONFIG_I40EVF=y -CONFIG_ICE=y -CONFIG_IGC=y -CONFIG_MLX4_EN=y -CONFIG_MLX5_CORE=y -CONFIG_MLX5_CORE_EN=y -CONFIG_BNXT=y -CONFIG_CNIC=m -CONFIG_TIGON3=y -CONFIG_BNX2X=y -CONFIG_R8169=y -CONFIG_8139CP=y -CONFIG_8139TOO=y -CONFIG_VMXNET3=y -CONFIG_HYPERV_NET=y -CONFIG_ENA_ETHERNET=m -CONFIG_GVE=m -CONFIG_NFP=m -CONFIG_IONIC=y -CONFIG_VIRTIO_NET=y - -# --- USB network --- -CONFIG_USB_CATC=m -CONFIG_USB_KAWETH=m -CONFIG_USB_PEGASUS=m -CONFIG_USB_RTL8150=y -CONFIG_USB_RTL8152=y -CONFIG_USB_LAN78XX=m -CONFIG_USB_USBNET=y -CONFIG_USB_NET_AX8817X=m -CONFIG_USB_NET_AX88179_178A=m -CONFIG_USB_NET_CDC_EEM=m -CONFIG_USB_NET_CDC_NCM=m -CONFIG_USB_NET_NET1080=m -CONFIG_USB_NET_CDC_SUBSET=m -CONFIG_USB_NET_ZAURUS=m - -# --- WAN --- -CONFIG_WAN=y -CONFIG_HDLC=m -CONFIG_HDLC_CISCO=m - -# --- PPP --- -CONFIG_PPP=m -CONFIG_PPP_BSDCOMP=m -CONFIG_PPP_DEFLATE=m -CONFIG_PPP_FILTER=y -CONFIG_PPP_MPPE=m -CONFIG_PPP_MULTILINK=y -CONFIG_PPPOE=m -CONFIG_PPTP=m -CONFIG_PPPOL2TP=m -CONFIG_PPP_ASYNC=m -CONFIG_PPP_SYNC_TTY=m - -# --- USB --- -CONFIG_USB_SUPPORT=y -CONFIG_USB=y -CONFIG_USB_XHCI_HCD=y -CONFIG_USB_EHCI_HCD=y -CONFIG_USB_OHCI_HCD=y -CONFIG_USB_UHCI_HCD=y -CONFIG_USB_XHCI_PCI=y -CONFIG_USB_UAS=y -CONFIG_USB_STORAGE=y - -# --- USB serial (BMC/console access) --- -CONFIG_USB_SERIAL=y -CONFIG_USB_SERIAL_CONSOLE=y -CONFIG_USB_SERIAL_GENERIC=y -CONFIG_USB_SERIAL_CH341=y -CONFIG_USB_SERIAL_CP210X=y -CONFIG_USB_SERIAL_FTDI_SIO=y -CONFIG_USB_SERIAL_PL2303=y -CONFIG_USB_SERIAL_TI=y -CONFIG_USB_SERIAL_OPTION=y - -# --- Input --- -CONFIG_INPUT_FF_MEMLESS=y -CONFIG_INPUT_SPARSEKMAP=y -CONFIG_INPUT_MOUSEDEV=y -CONFIG_INPUT_MOUSEDEV_PSAUX=y -CONFIG_INPUT_EVDEV=y -# CONFIG_INPUT_MOUSE is not set -CONFIG_INPUT_MISC=y -CONFIG_INPUT_PCSPKR=y -CONFIG_INPUT_ATLAS_BTNS=y -CONFIG_INPUT_UINPUT=y -CONFIG_SERIO_PCIPS2=y -CONFIG_SERIO_RAW=y -# CONFIG_LEGACY_PTYS is not set -# CONFIG_INPUT_JOYSTICK is not set -# CONFIG_INPUT_TOUCHSCREEN is not set - -# --- MMC --- -CONFIG_MMC=y -CONFIG_MMC_SDHCI=y -CONFIG_MMC_SDHCI_PCI=y -# CONFIG_MMC_RICOH_MMC is not set -CONFIG_MMC_SDHCI_ACPI=y -CONFIG_MMC_SDHCI_PLTFM=y - -# --- Filesystems --- -CONFIG_EXT4_FS=y -CONFIG_EXT4_FS_POSIX_ACL=y -CONFIG_EXT4_FS_SECURITY=y -CONFIG_XFS_FS=y -CONFIG_XFS_QUOTA=y -CONFIG_XFS_POSIX_ACL=y -CONFIG_BTRFS_FS=m -CONFIG_BTRFS_FS_POSIX_ACL=y -CONFIG_FS_DAX=y -CONFIG_FS_ENCRYPTION=y -CONFIG_FANOTIFY=y -CONFIG_QUOTA=y -CONFIG_QUOTA_NETLINK_INTERFACE=y -CONFIG_VFAT_FS=y -CONFIG_MSDOS_FS=y -CONFIG_FAT_DEFAULT_IOCHARSET="utf8" -CONFIG_NTFS_FS=m -CONFIG_TMPFS=y -CONFIG_TMPFS_POSIX_ACL=y -CONFIG_TMPFS_XATTR=y -CONFIG_PROC_FS=y -CONFIG_PROC_KCORE=y -CONFIG_SYSFS=y -CONFIG_EFIVAR_FS=y -CONFIG_OVERLAY_FS=y -CONFIG_SQUASHFS=y -CONFIG_SQUASHFS_XATTR=y -CONFIG_SQUASHFS_LZ4=y -CONFIG_SQUASHFS_LZO=y -CONFIG_SQUASHFS_XZ=y -CONFIG_FUSE_FS=y -CONFIG_CUSE=y -CONFIG_ISO9660_FS=y -CONFIG_JOLIET=y -CONFIG_ZISOFS=y -CONFIG_UDF_FS=y -CONFIG_FSCACHE=y -CONFIG_FSCACHE_STATS=y -CONFIG_CACHEFILES=y -CONFIG_NFS_FS=m -# CONFIG_NFS_V2 is not set -CONFIG_NFS_V4=m -CONFIG_NFS_V4_1=y -CONFIG_NFS_V4_2=y -CONFIG_NFS_FSCACHE=y -CONFIG_NFSD=m -CONFIG_NFSD_V4=y -CONFIG_CEPH_FS=m -CONFIG_CEPH_FSCACHE=y -CONFIG_CEPH_FS_POSIX_ACL=y -CONFIG_CIFS=y -# CONFIG_CIFS_ALLOW_INSECURE_LEGACY is not set -CONFIG_CIFS_XATTR=y -CONFIG_CIFS_DFS_UPCALL=y -CONFIG_CIFS_FSCACHE=y -CONFIG_9P_FS=y -CONFIG_9P_FSCACHE=y -CONFIG_9P_FS_POSIX_ACL=y -CONFIG_9P_FS_SECURITY=y - -# --- NLS --- -CONFIG_NLS_CODEPAGE_437=y -CONFIG_NLS_ASCII=y -CONFIG_NLS_ISO8859_1=y -CONFIG_NLS_UTF8=y - -# --- Virtio (built-in for reliable boot) --- -CONFIG_VIRTIO=y -CONFIG_VIRTIO_PCI=y -CONFIG_VIRTIO_BLK=y -CONFIG_VIRTIO_CONSOLE=y -CONFIG_VIRTIO_BALLOON=y -CONFIG_VIRTIO_INPUT=y -CONFIG_VIRTIO_MMIO=y -CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y -CONFIG_HW_RANDOM_VIRTIO=y -CONFIG_VHOST_NET=m -CONFIG_VHOST_VSOCK=m - -# --- Console / Serial --- -CONFIG_VT=y -CONFIG_VT_CONSOLE=y -CONFIG_SERIAL_8250=y -CONFIG_SERIAL_8250_CONSOLE=y -CONFIG_SERIAL_8250_NR_UARTS=32 -# CONFIG_SERIAL_8250_MID is not set -CONFIG_SERIAL_DEV_BUS=y -CONFIG_TTY_PRINTK=y -CONFIG_HW_CONSOLE=y -CONFIG_VGA_CONSOLE=y -CONFIG_DUMMY_CONSOLE=y - -# --- IPMI --- -CONFIG_IPMI_HANDLER=y -CONFIG_IPMI_DMI_DECODE=y -CONFIG_IPMI_DEVICE_INTERFACE=y -CONFIG_IPMI_SI=y -CONFIG_IPMI_POWEROFF=y - -# --- Hardware RNG --- -CONFIG_HW_RANDOM=y -CONFIG_HW_RANDOM_TIMERIOMEM=y - -# --- I2C --- -CONFIG_I2C_CHARDEV=y -CONFIG_I2C_MUX=y - -# --- TPM --- -CONFIG_TCG_TIS_I2C_ATMEL=m -CONFIG_TCG_TIS_I2C_INFINEON=m -CONFIG_TCG_TIS_I2C_NUVOTON=m -CONFIG_TCG_NSC=m -CONFIG_TCG_ATMEL=m -CONFIG_TCG_INFINEON=m -CONFIG_TCG_XEN=m -CONFIG_TCG_VTPM_PROXY=m -CONFIG_TCG_TIS_ST33ZP24_I2C=m - -# --- Platform / MFD --- -CONFIG_NVRAM=y -CONFIG_HPET=y -CONFIG_HANGCHECK_TIMER=y -CONFIG_LPC_ICH=y -CONFIG_LPC_SCH=y -CONFIG_MFD_INTEL_LPSS_ACPI=y -CONFIG_MFD_INTEL_LPSS_PCI=y -CONFIG_MFD_SM501=y -CONFIG_MFD_VX855=y -# CONFIG_THERMAL_HWMON is not set - -# --- RTC --- -CONFIG_RTC_CLASS=y - -# --- DMA --- -CONFIG_DMADEVICES=y - -# --- Security --- -CONFIG_SECCOMP=y -CONFIG_SECCOMP_FILTER=y -CONFIG_SECURITY=y -CONFIG_SECURITY_NETWORK=y -CONFIG_SECURITY_NETWORK_XFRM=y -CONFIG_SECURITY_PATH=y -CONFIG_SECURITY_DMESG_RESTRICT=y -CONFIG_KEYS=y -CONFIG_PERSISTENT_KEYRINGS=y -CONFIG_TRUSTED_KEYS=y -CONFIG_KEY_DH_OPERATIONS=y -CONFIG_HARDENED_USERCOPY=y -CONFIG_FORTIFY_SOURCE=y -CONFIG_STATIC_USERMODEHELPER=y -CONFIG_SECURITY_YAMA=y -CONFIG_INTEGRITY_SIGNATURE=y -CONFIG_INTEGRITY_ASYMMETRIC_KEYS=y -CONFIG_IMA=y -CONFIG_IMA_DEFAULT_HASH_SHA256=y -CONFIG_IMA_READ_POLICY=y -CONFIG_IMA_APPRAISE=y -CONFIG_EVM=y -CONFIG_LSM="yama,loadpin,safesetid,integrity" - -# --- Crypto --- -CONFIG_CRYPTO=y -CONFIG_CRYPTO_USER=y -CONFIG_CRYPTO_AES=y -CONFIG_CRYPTO_SHA256=y -CONFIG_CRYPTO_SHA512=y -CONFIG_CRYPTO_XTS=y -CONFIG_CRYPTO_ANUBIS=y -CONFIG_CRYPTO_BLOWFISH=y -CONFIG_CRYPTO_CAMELLIA=y -CONFIG_CRYPTO_DES=y -CONFIG_CRYPTO_FCRYPT=y -CONFIG_CRYPTO_KHAZAD=y -CONFIG_CRYPTO_SEED=y -CONFIG_CRYPTO_TEA=y -CONFIG_CRYPTO_TWOFISH=y -CONFIG_CRYPTO_ARC4=y -CONFIG_CRYPTO_KEYWRAP=y -CONFIG_CRYPTO_LRW=y -CONFIG_CRYPTO_PCBC=y -CONFIG_CRYPTO_CHACHA20POLY1305=y -CONFIG_CRYPTO_SEQIV=y -CONFIG_CRYPTO_ECHAINIV=y -CONFIG_CRYPTO_MICHAEL_MIC=y -CONFIG_CRYPTO_RMD160=y -CONFIG_CRYPTO_VMAC=y -CONFIG_CRYPTO_WP512=y -CONFIG_CRYPTO_XCBC=y -CONFIG_CRYPTO_CRC32=y -CONFIG_CRYPTO_LZO=y -CONFIG_CRYPTO_842=y -CONFIG_CRYPTO_LZ4=y -CONFIG_CRYPTO_LZ4HC=y -CONFIG_CRYPTO_ANSI_CPRNG=y -CONFIG_CRYPTO_USER_API_HASH=y -CONFIG_CRYPTO_USER_API_SKCIPHER=y -CONFIG_CRYPTO_USER_API_RNG=y -CONFIG_CRYPTO_USER_API_AEAD=y - -# --- x86_64 hardware crypto acceleration --- -CONFIG_CRYPTO_AES_NI_INTEL=y -CONFIG_CRYPTO_BLOWFISH_X86_64=y -CONFIG_CRYPTO_CAMELLIA_AESNI_AVX2_X86_64=y -CONFIG_CRYPTO_CAST5_AVX_X86_64=y -CONFIG_CRYPTO_CAST6_AVX_X86_64=y -CONFIG_CRYPTO_DES3_EDE_X86_64=y -CONFIG_CRYPTO_SERPENT_SSE2_X86_64=y -CONFIG_CRYPTO_SERPENT_AVX2_X86_64=y -CONFIG_CRYPTO_TWOFISH_AVX_X86_64=y -CONFIG_CRYPTO_CHACHA20_X86_64=y -CONFIG_CRYPTO_POLY1305_X86_64=y -CONFIG_CRYPTO_SHA1_SSSE3=y -CONFIG_CRYPTO_SHA256_SSSE3=y -CONFIG_CRYPTO_SHA512_SSSE3=y -CONFIG_CRYPTO_GHASH_CLMUL_NI_INTEL=y -CONFIG_CRYPTO_CRC32C_INTEL=y -CONFIG_CRYPTO_CRC32_PCLMUL=y -CONFIG_CRYPTO_DEV_PADLOCK=y -CONFIG_CRYPTO_DEV_PADLOCK_AES=y -CONFIG_CRYPTO_DEV_PADLOCK_SHA=y -CONFIG_CRYPTO_DEV_VIRTIO=m -CONFIG_PKCS7_MESSAGE_PARSER=y - -# --- BPF --- -CONFIG_BPF=y -CONFIG_BPF_SYSCALL=y -CONFIG_BPF_JIT=y -CONFIG_BPF_JIT_ALWAYS_ON=y - -# --- PCI --- -CONFIG_PCI=y -CONFIG_PCIEPORTBUS=y -CONFIG_HOTPLUG_PCI_PCIE=y -CONFIG_PCI_STUB=y -CONFIG_PCI_IOV=y -# CONFIG_VGA_ARB is not set -CONFIG_HOTPLUG_PCI=y -CONFIG_HOTPLUG_PCI_ACPI=y -CONFIG_HOTPLUG_PCI_SHPC=y -CONFIG_PCI_HYPERV_INTERFACE=m - -# --- IOMMU --- -CONFIG_AMD_IOMMU=y -CONFIG_INTEL_IOMMU=y -CONFIG_IRQ_REMAP=y - -# --- Xen --- -# CONFIG_XEN_BACKEND is not set -CONFIG_XEN_GNTDEV=y -CONFIG_XEN_GRANT_DEV_ALLOC=y -CONFIG_XEN_PVCALLS_FRONTEND=y -CONFIG_XEN_ACPI_PROCESSOR=y -# CONFIG_XEN_SYMS is not set - -# --- Intel platform --- -CONFIG_INTEL_IPS=y - -# --- DEVFREQ --- -CONFIG_PM_DEVFREQ=y -CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y - -# --- Reset --- -CONFIG_RESET_CONTROLLER=y - -# --- DAX --- -CONFIG_DEV_DAX=y -CONFIG_DEV_DAX_PMEM=m - -# --- Misc --- -CONFIG_FW_LOADER=y -CONFIG_PRINTK=y -CONFIG_PRINTK_TIME=y -CONFIG_MODULES=y -CONFIG_MODULE_UNLOAD=y -CONFIG_EARLY_PRINTK=y -CONFIG_PANIC_TIMEOUT=10 -CONFIG_PANIC_ON_OOPS=y -CONFIG_MAGIC_SYSRQ=y -CONFIG_UEVENT_HELPER=y -CONFIG_DEVTMPFS=y -CONFIG_DEVTMPFS_MOUNT=y -CONFIG_CONNECTOR=y -CONFIG_DMI_SYSFS=y -CONFIG_SYSFB_SIMPLEFB=y -CONFIG_RESET_ATTACK_MITIGATION=y -# CONFIG_PNP_DEBUG_MESSAGES is not set -CONFIG_HYPERV=y -CONFIG_HYPERV_UTILS=y -CONFIG_HYPERV_BALLOON=y - -# --- Module compression --- -CONFIG_MODULE_COMPRESS_ZSTD=y -CONFIG_MODULE_DECOMPRESS=y - -# --- EFI boot support --- -CONFIG_EFI=y -CONFIG_EFI_STUB=y -CONFIG_EFI_MIXED=y - -# --- Framebuffer/Console (required for EFI boot video output) --- -CONFIG_DRM=y -CONFIG_DRM_I915=y -CONFIG_DRM_SIMPLEDRM=y -CONFIG_FB=y -CONFIG_FB_EFI=y -CONFIG_FRAMEBUFFER_CONSOLE=y - -# --- Debugging / Diagnostics --- -CONFIG_FRAME_WARN=1024 -CONFIG_BUG_ON_DATA_CORRUPTION=y -CONFIG_HARDLOCKUP_DETECTOR=y -CONFIG_WQ_WATCHDOG=y -CONFIG_DEBUG_NOTIFIERS=y -CONFIG_RCU_CPU_STALL_TIMEOUT=60 -# CONFIG_RCU_TRACE is not set -CONFIG_IO_STRICT_DEVMEM=y - -# --- Disable unnecessary subsystems --- -# CONFIG_SOUND is not set -# CONFIG_WIRELESS is not set -# CONFIG_WLAN is not set -# CONFIG_BLUETOOTH is not set -# CONFIG_NFC is not set -# CONFIG_INFINIBAND is not set -# CONFIG_MEDIA_SUPPORT is not set diff --git a/kernel.configs/6.18.y.arm64 b/kernel.configs/6.18.y.arm64 deleted file mode 100644 index 795c91e..0000000 --- a/kernel.configs/6.18.y.arm64 +++ /dev/null @@ -1,515 +0,0 @@ -# CaptainOS kernel defconfig for arm64 (aarch64) -# Minimal config for bare-metal provisioning with container/network support. -# Based on 6.12.y config with targeted additions for CaptainOS use case, adapted for kernel 6.18. -# -# Generate a full .config from this: -# cp 6.18.y.arm64 .config && make ARCH=arm64 olddefconfig - -# --- Architecture --- -CONFIG_ARM64=y -CONFIG_SMP=y -CONFIG_NR_CPUS=128 -CONFIG_NUMA=y -CONFIG_ARM64_VA_BITS_48=y -CONFIG_SCHED_MC=y -CONFIG_SCHED_SMT=y -CONFIG_HZ_1000=y -CONFIG_XEN=y -CONFIG_COMPAT=y -CONFIG_RANDOMIZE_BASE=y -CONFIG_ARM64_ACPI_PARKING_PROTOCOL=y - -# --- General --- -CONFIG_LOCALVERSION="-captainos" -CONFIG_DEFAULT_HOSTNAME="captainos" -CONFIG_SYSVIPC=y -CONFIG_POSIX_MQUEUE=y -CONFIG_AUDIT=y -CONFIG_NO_HZ_IDLE=y -CONFIG_HIGH_RES_TIMERS=y -CONFIG_PREEMPT_VOLUNTARY=y -CONFIG_BSD_PROCESS_ACCT=y -CONFIG_KEXEC=y -CONFIG_KEXEC_FILE=y -CONFIG_CC_OPTIMIZE_FOR_SIZE=y -CONFIG_CGROUPS=y -CONFIG_CGROUP_FREEZER=y -CONFIG_CGROUP_PIDS=y -CONFIG_CGROUP_DEVICE=y -CONFIG_CGROUP_CPUACCT=y -CONFIG_CGROUP_PERF=y -CONFIG_CGROUP_BPF=y -CONFIG_CGROUP_MISC=y -CONFIG_CGROUP_HUGETLB=y -CONFIG_CGROUP_RDMA=y -CONFIG_CPUSETS=y -CONFIG_MEMCG=y -CONFIG_BLK_CGROUP=y -CONFIG_BLK_CGROUP_IOLATENCY=y -CONFIG_BLK_DEV_THROTTLING=y -CONFIG_CFS_BANDWIDTH=y -CONFIG_RT_GROUP_SCHED=y -CONFIG_CGROUP_SCHED=y -CONFIG_CGROUP_NET_PRIO=y -CONFIG_NAMESPACES=y -CONFIG_USER_NS=y -CONFIG_PID_NS=y -CONFIG_NET_NS=y -CONFIG_UTS_NS=y -CONFIG_IPC_NS=y -CONFIG_CHECKPOINT_RESTORE=y -# Memory management -CONFIG_TRANSPARENT_HUGEPAGE=y -CONFIG_KSM=y -CONFIG_MEMORY_HOTPLUG=y -CONFIG_MEMORY_HOTREMOVE=y -CONFIG_HUGETLBFS=y - -# --- Init / Initramfs --- -CONFIG_BLK_DEV_INITRD=y -CONFIG_INITRAMFS_SOURCE="" - -# --- Networking --- -CONFIG_NET=y -CONFIG_PACKET=y -CONFIG_UNIX=y -CONFIG_INET=y -CONFIG_IP_MULTICAST=y -CONFIG_IP_ADVANCED_ROUTER=y -CONFIG_IP_MULTIPLE_TABLES=y -CONFIG_IP_ROUTE_MULTIPATH=y -CONFIG_IP_PNP=y -CONFIG_IP_PNP_DHCP=y -CONFIG_IP_PNP_BOOTP=y -CONFIG_NET_IPIP=m -CONFIG_NET_IPGRE_DEMUX=m -CONFIG_NET_IPGRE=m -CONFIG_SYN_COOKIES=y -CONFIG_IPV6=y -CONFIG_BRIDGE=y -CONFIG_VLAN_8021Q=m -CONFIG_BONDING=m -CONFIG_TUN=y -CONFIG_VETH=y -CONFIG_MACVLAN=y -CONFIG_MACVTAP=y -CONFIG_IPVLAN=y -CONFIG_TAP=y -CONFIG_DUMMY=m -CONFIG_VXLAN=y -CONFIG_GENEVE=m -CONFIG_NLMON=y -# Traffic control / QoS (needed by CNI plugins) -CONFIG_NET_SCHED=y -CONFIG_NET_SCH_HTB=m -CONFIG_NET_SCH_PRIO=m -CONFIG_NET_SCH_SFQ=m -CONFIG_NET_SCH_TBF=m -CONFIG_NET_SCH_NETEM=m -CONFIG_NET_SCH_INGRESS=m -CONFIG_NET_CLS_BASIC=y -CONFIG_NET_CLS_FW=y -CONFIG_NET_CLS_U32=y -CONFIG_NET_CLS_CGROUP=y -CONFIG_NET_CLS_BPF=y -CONFIG_NET_CLS_MATCHALL=y -CONFIG_NET_CLS_ACT=y -CONFIG_NET_ACT_POLICE=y -CONFIG_NET_ACT_GACT=y -CONFIG_NET_ACT_MIRRED=y -CONFIG_NET_ACT_BPF=y -CONFIG_NET_EMATCH=y -CONFIG_NET_EMATCH_U32=y - -# --- Netfilter --- -CONFIG_NETFILTER=y -CONFIG_NETFILTER_ADVANCED=y -CONFIG_NF_CONNTRACK=y -CONFIG_NF_NAT=y -CONFIG_NF_TABLES=y -CONFIG_NF_TABLES_INET=y -CONFIG_NF_TABLES_IPV4=y -CONFIG_NF_TABLES_IPV6=y -CONFIG_NFT_NAT=y -CONFIG_NFT_MASQ=y -CONFIG_NFT_CT=y -CONFIG_NFT_COMPAT=y -CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y -CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y -CONFIG_NETFILTER_XT_MATCH_COMMENT=y -CONFIG_NETFILTER_XT_MATCH_MARK=y -CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y -CONFIG_IP_NF_IPTABLES=y -CONFIG_IP_NF_FILTER=y -CONFIG_IP_NF_NAT=y -CONFIG_IP6_NF_IPTABLES=y -CONFIG_IP6_NF_FILTER=y -CONFIG_IP6_NF_NAT=y -CONFIG_BRIDGE_NETFILTER=y -CONFIG_BRIDGE_NF_EBTABLES=y -CONFIG_BRIDGE_EBT_BROUTE=y -CONFIG_BRIDGE_EBT_T_FILTER=y -CONFIG_BRIDGE_EBT_T_NAT=y -CONFIG_BRIDGE_EBT_ARP=y -CONFIG_BRIDGE_EBT_IP=y -CONFIG_BRIDGE_EBT_IP6=y -CONFIG_BRIDGE_EBT_MARK=y -CONFIG_BRIDGE_EBT_MARK_T=y -CONFIG_BRIDGE_EBT_VLAN=y -# IPVS (container load balancing / kube-proxy) -CONFIG_IP_VS=y -CONFIG_IP_VS_IPV6=y -CONFIG_IP_VS_PROTO_TCP=y -CONFIG_IP_VS_PROTO_UDP=y -CONFIG_IP_VS_RR=y -CONFIG_IP_VS_WRR=y -CONFIG_IP_VS_SH=y -CONFIG_IP_VS_NFCT=y -# IP sets (used by iptables/nftables for efficient matching) -CONFIG_IP_SET=y -CONFIG_IP_SET_HASH_IP=y -CONFIG_IP_SET_HASH_IPPORT=y -CONFIG_IP_SET_HASH_IPPORTNET=y -CONFIG_IP_SET_HASH_NET=y -CONFIG_IP_SET_HASH_NETPORT=y -CONFIG_IP_SET_BITMAP_IP=y -CONFIG_IP_SET_BITMAP_PORT=y -CONFIG_IP_SET_LIST_SET=y -CONFIG_NETFILTER_XT_SET=y - -# --- Block devices --- -CONFIG_BLK_DEV=y -CONFIG_BLK_DEV_LOOP=y -CONFIG_BLK_DEV_NBD=y -CONFIG_BLK_DEV_RAM=y -CONFIG_BLK_DEV_RAM_SIZE=65536 -CONFIG_BLK_DEV_NVME=y -CONFIG_ATA=y -CONFIG_SATA_AHCI=y -CONFIG_SATA_AHCI_PLATFORM=y - -# --- SCSI --- -CONFIG_SCSI=y -CONFIG_BLK_DEV_SD=y -CONFIG_CHR_DEV_SG=y -CONFIG_MEGARAID_SAS=y -CONFIG_SCSI_MPT3SAS=y -CONFIG_SCSI_MPI3MR=m -CONFIG_SCSI_HPSA=y -CONFIG_SCSI_HISI_SAS=y -CONFIG_SCSI_HISI_SAS_PCI=y -CONFIG_XEN_SCSI_FRONTEND=y -CONFIG_SCSI_VIRTIO=y - -# --- Device mapper --- -CONFIG_MD=y -CONFIG_BLK_DEV_DM=y -CONFIG_DM_CRYPT=y -CONFIG_DM_THIN_PROVISIONING=y -CONFIG_DM_SNAPSHOT=y -CONFIG_DM_MULTIPATH=m -CONFIG_MD_RAID0=m -CONFIG_MD_RAID1=m - -# --- Network drivers (built-in for reliable boot) --- -CONFIG_NETDEVICES=y -CONFIG_VIRTIO_NET=y -CONFIG_E1000E=y -CONFIG_IGB=y -CONFIG_IXGBE=y -CONFIG_I40E=y -CONFIG_ICE=y -CONFIG_IGC=y -CONFIG_MLX4_EN=y -CONFIG_MLX5_CORE=y -CONFIG_MLX5_CORE_EN=y -CONFIG_BNXT=y -CONFIG_TIGON3=y -CONFIG_R8169=y -CONFIG_THUNDER_NIC_BGX=m -CONFIG_THUNDER_NIC_PF=m -CONFIG_THUNDER_NIC_VF=m -CONFIG_MACB=m -CONFIG_HNS3=m -CONFIG_NET_XGENE=y -CONFIG_MVNETA=y -CONFIG_MVPP2=y -CONFIG_STMMAC_ETH=m -CONFIG_ENA_ETHERNET=m -CONFIG_GVE=m -CONFIG_VMXNET3=y -CONFIG_AMD_XGBE=y -# Broadcom GENET (Raspberry Pi 4B built-in Ethernet) -CONFIG_BCMGENET=y -# PHY subsystem (required by GENET and other MAC drivers) -CONFIG_PHYLIB=y -CONFIG_MDIO_BUS=y -CONFIG_BROADCOM_PHY=y -CONFIG_BCM7XXX_PHY=y -CONFIG_MDIO_BCM_UNIMAC=y -CONFIG_MARVELL_PHY=y -CONFIG_MARVELL_10G_PHY=y -CONFIG_MICREL_PHY=y -CONFIG_ROCKCHIP_PHY=y -CONFIG_REALTEK_PHY=y -CONFIG_MESON_GXL_PHY=y -CONFIG_AMD_PHY=y - -# --- USB --- -CONFIG_USB_SUPPORT=y -CONFIG_USB=y -CONFIG_USB_XHCI_HCD=y -CONFIG_USB_EHCI_HCD=y -CONFIG_USB_OHCI_HCD=y -CONFIG_USB_XHCI_PCI=y -CONFIG_USB_UAS=y -CONFIG_USB_STORAGE=y -CONFIG_USB_DWC3=y -CONFIG_USB_DWC3_HOST=y -CONFIG_USB_DWC2=y -CONFIG_USB_ISP1760=y - -# --- MMC/SD (Raspberry Pi, Rockchip, and other SBCs) --- -CONFIG_MMC=y -CONFIG_MMC_BLOCK_MINORS=32 -CONFIG_MMC_SDHCI=y -CONFIG_MMC_SDHCI_PCI=y -CONFIG_MMC_SDHCI_ACPI=y -CONFIG_MMC_SDHCI_PLTFM=y -CONFIG_MMC_SDHCI_OF_ARASAN=y -CONFIG_MMC_SDHCI_OF_DWCMSHC=y -CONFIG_MMC_SDHCI_CADENCE=y -CONFIG_MMC_SDHCI_TEGRA=y -CONFIG_MMC_SDHCI_MSM=y -CONFIG_MMC_SDHCI_XENON=y -CONFIG_MMC_SDHCI_F_SDH30=y -CONFIG_MMC_ARMMMCI=y -CONFIG_MMC_DW=y -CONFIG_MMC_DW_ROCKCHIP=y -CONFIG_MMC_DW_K3=y -CONFIG_MMC_MESON_GX=y -CONFIG_MMC_SUNXI=m -CONFIG_MMC_SPI=y -CONFIG_MMC_HSQ=y - -# --- Filesystems --- -CONFIG_EXT4_FS=y -CONFIG_XFS_FS=m -CONFIG_VFAT_FS=y -CONFIG_TMPFS=y -CONFIG_TMPFS_POSIX_ACL=y -CONFIG_PROC_FS=y -CONFIG_SYSFS=y -CONFIG_OVERLAY_FS=y -CONFIG_SQUASHFS=y -CONFIG_SQUASHFS_XATTR=y -CONFIG_SQUASHFS_LZ4=y -CONFIG_SQUASHFS_LZO=y -CONFIG_SQUASHFS_XZ=y -CONFIG_NFS_FS=m -CONFIG_NFS_V4=m -CONFIG_NFS_V4_1=y -CONFIG_NFS_V4_2=y -CONFIG_FUSE_FS=y -CONFIG_CUSE=y -CONFIG_EFIVAR_FS=y -CONFIG_ISO9660_FS=y -CONFIG_JOLIET=y -CONFIG_UDF_FS=y -CONFIG_MSDOS_FS=y -CONFIG_FAT_DEFAULT_IOCHARSET="utf8" -CONFIG_NTFS_FS=m -CONFIG_TMPFS_XATTR=y -CONFIG_NLS_CODEPAGE_437=y -CONFIG_NLS_ASCII=y -CONFIG_NLS_ISO8859_1=y -CONFIG_NLS_UTF8=y - -# --- Virtio (built-in for reliable boot) --- -CONFIG_VIRTIO=y -CONFIG_VIRTIO_PCI=y -CONFIG_VIRTIO_BLK=y -CONFIG_VIRTIO_CONSOLE=y -CONFIG_VIRTIO_BALLOON=y -CONFIG_VIRTIO_INPUT=y -CONFIG_VIRTIO_MMIO=y -CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y -CONFIG_HW_RANDOM_VIRTIO=y - -# --- Console / Serial --- -CONFIG_SERIAL_AMBA_PL011=y -CONFIG_SERIAL_AMBA_PL011_CONSOLE=y -CONFIG_SERIAL_8250=y -CONFIG_SERIAL_8250_CONSOLE=y -CONFIG_VT=y -CONFIG_VT_CONSOLE=y -CONFIG_HW_CONSOLE=y -CONFIG_DUMMY_CONSOLE=y - -# --- Security --- -CONFIG_SECCOMP=y -CONFIG_SECCOMP_FILTER=y -CONFIG_SECURITY=y -CONFIG_KEYS=y - -# --- Crypto --- -CONFIG_CRYPTO=y -CONFIG_CRYPTO_AES=y -CONFIG_CRYPTO_SHA256=y -CONFIG_CRYPTO_SHA512=y -CONFIG_CRYPTO_XTS=y -CONFIG_ARM64_CRYPTO=y -CONFIG_CRYPTO_AES_ARM64_CE=y -CONFIG_CRYPTO_SHA2_ARM64_CE=y - -# --- BPF --- -CONFIG_BPF=y -CONFIG_BPF_SYSCALL=y -CONFIG_BPF_JIT=y -CONFIG_BPF_JIT_ALWAYS_ON=y -CONFIG_XDP_SOCKETS=y - -# --- ARM64 SoC/platform support --- -CONFIG_ARCH_BCM2835=y -CONFIG_ARCH_ROCKCHIP=y -CONFIG_ARCH_MESON=y -CONFIG_ARCH_SUNXI=y -CONFIG_ARCH_TEGRA=y -CONFIG_ARCH_MVEBU=y -CONFIG_ARCH_HISI=y -CONFIG_ARCH_QCOM=y -CONFIG_ARCH_EXYNOS=y -CONFIG_ARCH_VEXPRESS=y -CONFIG_ARCH_XGENE=y -CONFIG_ARCH_THUNDER=y -CONFIG_ARCH_THUNDER2=y -CONFIG_ARCH_SEATTLE=y -CONFIG_ARCH_SYNQUACER=y -CONFIG_ARCH_UNIPHIER=y -# ACPI / UEFI -CONFIG_ACPI=y -CONFIG_ACPI_DOCK=y -CONFIG_ACPI_IPMI=y -CONFIG_ACPI_APEI=y -CONFIG_ACPI_APEI_GHES=y -CONFIG_IPMI_HANDLER=m -CONFIG_IPMI_DMI_DECODE=y -CONFIG_IPMI_DEVICE_INTERFACE=m -CONFIG_IPMI_SI=m -CONFIG_PCI=y -CONFIG_PCIEPORTBUS=y -CONFIG_HOTPLUG_PCI=y -CONFIG_PCI_HOST_GENERIC=y -CONFIG_PCIE_ROCKCHIP_HOST=y -CONFIG_PCIE_ARMADA_8K=y -CONFIG_PCIE_TEGRA194_HOST=y -# Broadcom STB PCIe controller (Raspberry Pi 4B — needed for VL805 USB 3.0) -CONFIG_PCIE_BRCMSTB=y -CONFIG_PCIE_ALTERA=y -CONFIG_PCI_AARDVARK=y -CONFIG_PCI_TEGRA=y -CONFIG_PCI_XGENE=y -CONFIG_PCI_HOST_THUNDER_PEM=y -CONFIG_PCI_HOST_THUNDER_ECAM=y -CONFIG_PCIE_QCOM=y -CONFIG_PCIE_AL=y -CONFIG_PCI_MESON=y -CONFIG_PCI_HISI=y -CONFIG_PCIE_KIRIN=y -CONFIG_PCIE_HISI_STB=y -CONFIG_PCI_IOV=y -CONFIG_PCI_PASID=y -CONFIG_HOTPLUG_PCI_ACPI=y -# IOMMU -CONFIG_ARM_SMMU=y -CONFIG_ARM_SMMU_V3=y -CONFIG_FW_LOADER=y -# Raspberry Pi firmware mailbox (required for GENET MAC address, clocks, etc.) -CONFIG_RASPBERRYPI_FIRMWARE=y -CONFIG_RASPBERRYPI_POWER=y -CONFIG_ARM_SCMI_PROTOCOL=y -CONFIG_ARM_SCPI_PROTOCOL=y -CONFIG_OF=y -CONFIG_USE_OF=y -# RTC -CONFIG_RTC_CLASS=y -CONFIG_RTC_DRV_EFI=y -# DMA -CONFIG_DMADEVICES=y -CONFIG_PL330_DMA=y -CONFIG_PRINTK=y -CONFIG_PRINTK_TIME=y -CONFIG_MODULES=y -CONFIG_MODULE_UNLOAD=y -CONFIG_PANIC_TIMEOUT=10 -CONFIG_PANIC_ON_OOPS=y -CONFIG_MAGIC_SYSRQ=y -CONFIG_RD_GZIP=y -CONFIG_RD_XZ=y -CONFIG_RD_ZSTD=y -CONFIG_UEVENT_HELPER=y -CONFIG_DEVTMPFS=y -CONFIG_DEVTMPFS_MOUNT=y -CONFIG_CONNECTOR=y -CONFIG_DMI_SYSFS=y -CONFIG_EFI=y -CONFIG_EFI_STUB=y - -# --- Framebuffer/Console (required for EFI boot video output) --- -CONFIG_DRM=y -CONFIG_DRM_SIMPLEDRM=y -CONFIG_DRM_VIRTIO_GPU=y -CONFIG_FB=y -CONFIG_FB_EFI=y -CONFIG_FRAMEBUFFER_CONSOLE=y -CONFIG_SYSFB_SIMPLEFB=y -# Virtualization -CONFIG_VIRTUALIZATION=y -CONFIG_KVM=y -CONFIG_VFIO=y -CONFIG_VFIO_PCI=y -CONFIG_VHOST_NET=m -CONFIG_VHOST_VSOCK=m - -# --- Module compression --- -CONFIG_MODULE_COMPRESS_ZSTD=y -CONFIG_MODULE_DECOMPRESS=y - -# --- I2C (SBC board management, EEPROM, sensors) --- -CONFIG_I2C=y -CONFIG_I2C_CHARDEV=y -CONFIG_I2C_MV64XXX=y -CONFIG_I2C_RK3X=y -CONFIG_I2C_TEGRA=y -CONFIG_I2C_DESIGNWARE_PLATFORM=y -# SPI -CONFIG_SPI=y -CONFIG_SPI_ROCKCHIP=y -CONFIG_SPI_PL022=y -CONFIG_SPI_ORION=y -# GPIO -CONFIG_GPIOLIB=y -# Hardware watchdog -CONFIG_WATCHDOG=y -CONFIG_SOFTLOCKUP_DETECTOR=y -CONFIG_WQ_WATCHDOG=y -# Hardware RNG -CONFIG_HW_RANDOM=y -CONFIG_HW_RANDOM_TIMERIOMEM=y -# TPM -CONFIG_TCG_TIS=y -CONFIG_TCG_FTPM_TEE=y -CONFIG_TEE=y -CONFIG_OPTEE=y - -# --- Disable unnecessary subsystems --- -# CONFIG_SOUND is not set -# CONFIG_WIRELESS is not set -# CONFIG_WLAN is not set -# CONFIG_BLUETOOTH is not set -# CONFIG_NFC is not set -# CONFIG_INFINIBAND is not set -# CONFIG_MEDIA_SUPPORT is not set -CONFIG_INPUT_EVDEV=y -# CONFIG_INPUT_JOYSTICK is not set -# CONFIG_INPUT_TOUCHSCREEN is not set diff --git a/kernel.configs/6.19.y.amd64 b/kernel.configs/6.19.y.amd64 deleted file mode 100644 index c6d243c..0000000 --- a/kernel.configs/6.19.y.amd64 +++ /dev/null @@ -1,1032 +0,0 @@ -# CaptainOS kernel defconfig for x86_64 -# Comprehensive config for bare-metal provisioning with container/network support. -# Derived from HookOS generic-6.6.y-x86_64 proven config, adapted for kernel 6.18. -# -# Generate a full .config from this: -# cp 6.18.y.amd64 .config && make olddefconfig - -# --- Architecture --- -CONFIG_64BIT=y -CONFIG_X86_64=y -CONFIG_SMP=y -CONFIG_NR_CPUS=128 -# CONFIG_X86_EXTENDED_PLATFORM is not set -CONFIG_X86_INTEL_LPSS=y -CONFIG_HYPERVISOR_GUEST=y -CONFIG_PARAVIRT_SPINLOCKS=y -CONFIG_XEN=y -CONFIG_XEN_PVH=y -CONFIG_X86_REROUTE_FOR_BROKEN_BOOT_IRQS=y -# CONFIG_X86_MCE is not set -CONFIG_X86_MSR=y -CONFIG_X86_CPUID=y -# CONFIG_X86_5LEVEL is not set -CONFIG_HZ_1000=y -CONFIG_PHYSICAL_ALIGN=0x1000000 -CONFIG_LEGACY_VSYSCALL_NONE=y -# CONFIG_MODIFY_LDT_SYSCALL is not set -CONFIG_IA32_EMULATION=y - -# --- General --- -CONFIG_LOCALVERSION="-captainos" -# CONFIG_LOCALVERSION_AUTO is not set -CONFIG_DEFAULT_HOSTNAME="captainos" -CONFIG_SYSVIPC=y -CONFIG_POSIX_MQUEUE=y -CONFIG_AUDIT=y -CONFIG_NO_HZ_IDLE=y -CONFIG_HIGH_RES_TIMERS=y -CONFIG_PREEMPT_VOLUNTARY=y -CONFIG_BSD_PROCESS_ACCT=y -CONFIG_BSD_PROCESS_ACCT_V3=y -CONFIG_IKCONFIG=y -CONFIG_IKCONFIG_PROC=y -CONFIG_CC_OPTIMIZE_FOR_SIZE=y -CONFIG_EXPERT=y -CONFIG_KEXEC=y -CONFIG_KEXEC_FILE=y -CONFIG_SCHED_AUTOGROUP=y -CONFIG_CHECKPOINT_RESTORE=y -CONFIG_KPROBES=y -CONFIG_JUMP_LABEL=y -CONFIG_BINFMT_MISC=y - -# --- Cgroups --- -CONFIG_CGROUPS=y -CONFIG_CGROUP_FREEZER=y -CONFIG_CGROUP_PIDS=y -CONFIG_CGROUP_RDMA=y -CONFIG_CGROUP_DEVICE=y -CONFIG_CGROUP_CPUACCT=y -CONFIG_CGROUP_PERF=y -CONFIG_CGROUP_BPF=y -CONFIG_CGROUP_MISC=y -CONFIG_CGROUP_HUGETLB=y -CONFIG_CGROUP_NET_PRIO=y -CONFIG_MEMCG=y -CONFIG_BLK_CGROUP=y -CONFIG_CFS_BANDWIDTH=y -CONFIG_RT_GROUP_SCHED=y -CONFIG_CGROUP_SCHED=y -CONFIG_CPUSETS=y - -# --- Namespaces --- -CONFIG_NAMESPACES=y -CONFIG_USER_NS=y -CONFIG_PID_NS=y -CONFIG_NET_NS=y -CONFIG_UTS_NS=y -CONFIG_IPC_NS=y - -# --- Init / Initramfs --- -CONFIG_BLK_DEV_INITRD=y -CONFIG_INITRAMFS_SOURCE="" -# CONFIG_RD_BZIP2 is not set -# CONFIG_RD_LZMA is not set -CONFIG_RD_GZIP=y -CONFIG_RD_XZ=y -# CONFIG_RD_LZO is not set -# CONFIG_RD_LZ4 is not set -CONFIG_RD_ZSTD=y - -# --- Memory management --- -CONFIG_SLAB_FREELIST_RANDOM=y -# CONFIG_COMPAT_BRK is not set -CONFIG_MEMORY_HOTPLUG=y -CONFIG_MEMORY_HOTREMOVE=y -CONFIG_KSM=y -CONFIG_DEFAULT_MMAP_MIN_ADDR=65536 -CONFIG_TRANSPARENT_HUGEPAGE=y -CONFIG_ZONE_DEVICE=y -CONFIG_HUGETLBFS=y - -# --- Power / Suspend --- -# CONFIG_SUSPEND is not set - -# --- ACPI --- -CONFIG_ACPI=y -# CONFIG_ACPI_REV_OVERRIDE_POSSIBLE is not set -CONFIG_ACPI_DOCK=y -CONFIG_ACPI_IPMI=y -CONFIG_ACPI_PROCESSOR_AGGREGATOR=y -CONFIG_ACPI_SBS=y -CONFIG_ACPI_NFIT=y -CONFIG_ACPI_APEI=y -CONFIG_ACPI_APEI_GHES=y - -# --- CPU frequency --- -CONFIG_CPU_FREQ_STAT=y -CONFIG_CPU_FREQ_GOV_POWERSAVE=y -CONFIG_CPU_FREQ_GOV_USERSPACE=y -CONFIG_CPU_FREQ_GOV_ONDEMAND=y -CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y -CONFIG_X86_PCC_CPUFREQ=y -CONFIG_X86_ACPI_CPUFREQ=y -CONFIG_X86_POWERNOW_K8=y -CONFIG_X86_P4_CLOCKMOD=y -CONFIG_CPU_IDLE_GOV_LADDER=y -CONFIG_INTEL_IDLE=y - -# --- KVM --- -CONFIG_KVM=m -CONFIG_KVM_INTEL=m -CONFIG_KVM_AMD=m - -# --- Networking --- -CONFIG_NET=y -CONFIG_PACKET=y -CONFIG_PACKET_DIAG=y -CONFIG_UNIX=y -CONFIG_UNIX_DIAG=y -CONFIG_XFRM_USER=m -CONFIG_XFRM_SUB_POLICY=y -CONFIG_XFRM_STATISTICS=y -CONFIG_NET_KEY=m -CONFIG_NET_KEY_MIGRATE=y -CONFIG_XDP_SOCKETS=y -CONFIG_INET=y -CONFIG_IP_MULTICAST=y -CONFIG_IP_ADVANCED_ROUTER=y -CONFIG_IP_FIB_TRIE_STATS=y -CONFIG_IP_MULTIPLE_TABLES=y -CONFIG_IP_ROUTE_MULTIPATH=y -CONFIG_IP_ROUTE_VERBOSE=y -CONFIG_IP_PNP=y -CONFIG_IP_PNP_DHCP=y -CONFIG_IP_PNP_BOOTP=y -CONFIG_NET_IPIP=y -CONFIG_NET_IPGRE_DEMUX=y -CONFIG_NET_IPGRE=m -CONFIG_NET_IPGRE_BROADCAST=y -CONFIG_IP_MROUTE=y -CONFIG_IP_MROUTE_MULTIPLE_TABLES=y -CONFIG_IP_PIMSM_V1=y -CONFIG_IP_PIMSM_V2=y -CONFIG_NET_IPVTI=m -CONFIG_NET_FOU_IP_TUNNELS=y -CONFIG_INET_AH=m -CONFIG_INET_ESP=m -CONFIG_INET_IPCOMP=m -CONFIG_INET_UDP_DIAG=y -CONFIG_TCP_MD5SIG=y -CONFIG_SYN_COOKIES=y -CONFIG_IPV6=y -CONFIG_IPV6_ROUTER_PREF=y -CONFIG_INET6_AH=m -CONFIG_INET6_ESP=m -CONFIG_INET6_IPCOMP=m -CONFIG_IPV6_MIP6=m -CONFIG_IPV6_ILA=m -CONFIG_IPV6_VTI=m -CONFIG_IPV6_SIT=m -CONFIG_IPV6_SIT_6RD=y -CONFIG_IPV6_GRE=m -CONFIG_IPV6_MULTIPLE_TABLES=y -CONFIG_IPV6_SUBTREES=y -CONFIG_NETLABEL=y -CONFIG_NETWORK_SECMARK=y -CONFIG_BRIDGE=y -CONFIG_BRIDGE_VLAN_FILTERING=y -CONFIG_VLAN_8021Q=y -CONFIG_BONDING=m -CONFIG_TUN=y -CONFIG_VETH=y -CONFIG_MACVLAN=y -CONFIG_MACVTAP=y -CONFIG_IPVLAN=y -CONFIG_VXLAN=y -CONFIG_GENEVE=m -CONFIG_NETCONSOLE=y -CONFIG_TAP=y -CONFIG_DUMMY=m -CONFIG_NLMON=y -CONFIG_IP_SCTP=m -CONFIG_L2TP=m -CONFIG_NET_TEAM=m -CONFIG_NET_TEAM_MODE_BROADCAST=m -CONFIG_NET_TEAM_MODE_ROUNDROBIN=m -CONFIG_NET_TEAM_MODE_RANDOM=m -CONFIG_NET_TEAM_MODE_ACTIVEBACKUP=m -CONFIG_NET_TEAM_MODE_LOADBALANCE=m -CONFIG_OPENVSWITCH=m -CONFIG_VSOCKETS=m -CONFIG_VIRTIO_VSOCKETS=m -CONFIG_HYPERV_VSOCKETS=m -CONFIG_NETLINK_DIAG=y -CONFIG_MPLS_ROUTING=m -CONFIG_MPLS_IPTUNNEL=m -CONFIG_NET_SWITCHDEV=y -CONFIG_NET_9P=y -CONFIG_NET_9P_VIRTIO=y - -# --- Traffic control --- -CONFIG_NET_SCHED=y -CONFIG_NET_SCH_HTB=m -CONFIG_NET_SCH_HFSC=m -CONFIG_NET_SCH_PRIO=m -CONFIG_NET_SCH_MULTIQ=m -CONFIG_NET_SCH_RED=m -CONFIG_NET_SCH_SFB=m -CONFIG_NET_SCH_SFQ=m -CONFIG_NET_SCH_TEQL=m -CONFIG_NET_SCH_TBF=m -CONFIG_NET_SCH_GRED=m -CONFIG_NET_SCH_NETEM=m -CONFIG_NET_SCH_DRR=m -CONFIG_NET_SCH_MQPRIO=m -CONFIG_NET_SCH_CHOKE=m -CONFIG_NET_SCH_QFQ=m -CONFIG_NET_SCH_INGRESS=m -CONFIG_NET_CLS_BASIC=y -CONFIG_NET_CLS_ROUTE4=y -CONFIG_NET_CLS_FW=y -CONFIG_NET_CLS_U32=y -CONFIG_CLS_U32_PERF=y -CONFIG_CLS_U32_MARK=y -CONFIG_NET_CLS_FLOW=y -CONFIG_NET_CLS_CGROUP=y -CONFIG_NET_CLS_BPF=y -CONFIG_NET_CLS_MATCHALL=y -CONFIG_NET_EMATCH=y -CONFIG_NET_EMATCH_CMP=y -CONFIG_NET_EMATCH_NBYTE=y -CONFIG_NET_EMATCH_U32=y -CONFIG_NET_EMATCH_META=y -CONFIG_NET_EMATCH_TEXT=y -CONFIG_NET_EMATCH_IPSET=y -CONFIG_NET_CLS_ACT=y -CONFIG_NET_ACT_POLICE=y -CONFIG_NET_ACT_GACT=y -CONFIG_GACT_PROB=y -CONFIG_NET_ACT_MIRRED=y -CONFIG_NET_ACT_IPT=y -CONFIG_NET_ACT_NAT=y -CONFIG_NET_ACT_PEDIT=y -CONFIG_NET_ACT_SIMP=y -CONFIG_NET_ACT_SKBEDIT=y -CONFIG_NET_ACT_CSUM=y -CONFIG_NET_ACT_BPF=y - -# --- Netfilter --- -CONFIG_NETFILTER=y -CONFIG_NETFILTER_ADVANCED=y -CONFIG_BRIDGE_NETFILTER=y -CONFIG_NF_CONNTRACK=y -CONFIG_NF_CONNTRACK_ZONES=y -CONFIG_NF_CONNTRACK_PROCFS=y -CONFIG_NF_CONNTRACK_EVENTS=y -CONFIG_NF_CONNTRACK_TIMEOUT=y -CONFIG_NF_CONNTRACK_TIMESTAMP=y -CONFIG_NF_CONNTRACK_AMANDA=y -CONFIG_NF_CONNTRACK_FTP=y -CONFIG_NF_CONNTRACK_H323=y -CONFIG_NF_CONNTRACK_IRC=y -CONFIG_NF_CONNTRACK_NETBIOS_NS=y -CONFIG_NF_CONNTRACK_SNMP=y -CONFIG_NF_CONNTRACK_PPTP=y -CONFIG_NF_CONNTRACK_SANE=y -CONFIG_NF_CONNTRACK_SIP=y -CONFIG_NF_CONNTRACK_TFTP=y -CONFIG_NF_CT_NETLINK=y -CONFIG_NF_CT_NETLINK_TIMEOUT=y -CONFIG_NF_CT_NETLINK_HELPER=y -CONFIG_NETFILTER_NETLINK_GLUE_CT=y -CONFIG_NF_NAT=y -CONFIG_NF_TABLES=y -CONFIG_NF_TABLES_INET=y -CONFIG_NF_TABLES_NETDEV=y -CONFIG_NFT_CT=y -CONFIG_NFT_CONNLIMIT=y -CONFIG_NFT_LOG=y -CONFIG_NFT_LIMIT=y -CONFIG_NFT_MASQ=y -CONFIG_NFT_REDIR=y -CONFIG_NFT_NAT=y -CONFIG_NFT_TUNNEL=y -CONFIG_NFT_QUEUE=y -CONFIG_NFT_REJECT=y -CONFIG_NFT_COMPAT=y -CONFIG_NFT_HASH=y -CONFIG_NFT_OSF=y -CONFIG_NFT_TPROXY=y -CONFIG_NFT_DUP_NETDEV=y -CONFIG_NFT_FWD_NETDEV=y -CONFIG_NETFILTER_XT_SET=y -CONFIG_NETFILTER_XT_TARGET_CHECKSUM=y -CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y -CONFIG_NETFILTER_XT_TARGET_CONNMARK=y -CONFIG_NETFILTER_XT_TARGET_DSCP=y -CONFIG_NETFILTER_XT_TARGET_HMARK=y -CONFIG_NETFILTER_XT_TARGET_IDLETIMER=y -CONFIG_NETFILTER_XT_TARGET_LOG=y -CONFIG_NETFILTER_XT_TARGET_MARK=y -CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y -CONFIG_NETFILTER_XT_TARGET_NFLOG=y -CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y -CONFIG_NETFILTER_XT_TARGET_NOTRACK=y -CONFIG_NETFILTER_XT_TARGET_TEE=y -CONFIG_NETFILTER_XT_TARGET_TPROXY=y -CONFIG_NETFILTER_XT_TARGET_TRACE=y -CONFIG_NETFILTER_XT_TARGET_TCPMSS=y -CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=y -CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y -CONFIG_NETFILTER_XT_MATCH_BPF=y -CONFIG_NETFILTER_XT_MATCH_CGROUP=y -CONFIG_NETFILTER_XT_MATCH_CLUSTER=y -CONFIG_NETFILTER_XT_MATCH_COMMENT=y -CONFIG_NETFILTER_XT_MATCH_CONNBYTES=y -CONFIG_NETFILTER_XT_MATCH_CONNLABEL=y -CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y -CONFIG_NETFILTER_XT_MATCH_CONNMARK=y -CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y -CONFIG_NETFILTER_XT_MATCH_CPU=y -CONFIG_NETFILTER_XT_MATCH_DCCP=y -CONFIG_NETFILTER_XT_MATCH_DEVGROUP=y -CONFIG_NETFILTER_XT_MATCH_DSCP=y -CONFIG_NETFILTER_XT_MATCH_ESP=y -CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y -CONFIG_NETFILTER_XT_MATCH_HELPER=y -CONFIG_NETFILTER_XT_MATCH_IPCOMP=y -CONFIG_NETFILTER_XT_MATCH_IPRANGE=y -CONFIG_NETFILTER_XT_MATCH_IPVS=y -CONFIG_NETFILTER_XT_MATCH_L2TP=y -CONFIG_NETFILTER_XT_MATCH_LENGTH=y -CONFIG_NETFILTER_XT_MATCH_LIMIT=y -CONFIG_NETFILTER_XT_MATCH_MAC=y -CONFIG_NETFILTER_XT_MATCH_MARK=y -CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y -CONFIG_NETFILTER_XT_MATCH_NFACCT=y -CONFIG_NETFILTER_XT_MATCH_OSF=y -CONFIG_NETFILTER_XT_MATCH_OWNER=y -CONFIG_NETFILTER_XT_MATCH_POLICY=y -CONFIG_NETFILTER_XT_MATCH_PHYSDEV=y -CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y -CONFIG_NETFILTER_XT_MATCH_QUOTA=y -CONFIG_NETFILTER_XT_MATCH_RATEEST=y -CONFIG_NETFILTER_XT_MATCH_REALM=y -CONFIG_NETFILTER_XT_MATCH_RECENT=y -CONFIG_NETFILTER_XT_MATCH_SCTP=y -CONFIG_NETFILTER_XT_MATCH_SOCKET=y -CONFIG_NETFILTER_XT_MATCH_STATE=y -CONFIG_NETFILTER_XT_MATCH_STATISTIC=y -CONFIG_NETFILTER_XT_MATCH_STRING=y -CONFIG_NETFILTER_XT_MATCH_TCPMSS=y -CONFIG_NETFILTER_XT_MATCH_TIME=y -CONFIG_NETFILTER_XT_MATCH_U32=y -CONFIG_IP_SET=y -CONFIG_IP_SET_BITMAP_IP=y -CONFIG_IP_SET_BITMAP_IPMAC=y -CONFIG_IP_SET_BITMAP_PORT=y -CONFIG_IP_SET_HASH_IP=y -CONFIG_IP_SET_HASH_IPPORT=y -CONFIG_IP_SET_HASH_IPPORTIP=y -CONFIG_IP_SET_HASH_IPPORTNET=y -CONFIG_IP_SET_HASH_NET=y -CONFIG_IP_SET_HASH_NETPORT=y -CONFIG_IP_SET_HASH_NETIFACE=y -CONFIG_IP_SET_LIST_SET=y -CONFIG_IP_VS=y -CONFIG_IP_VS_IPV6=y -CONFIG_IP_VS_PROTO_TCP=y -CONFIG_IP_VS_PROTO_UDP=y -CONFIG_IP_VS_PROTO_ESP=y -CONFIG_IP_VS_PROTO_AH=y -CONFIG_IP_VS_PROTO_SCTP=y -CONFIG_IP_VS_RR=y -CONFIG_IP_VS_WRR=y -CONFIG_IP_VS_LC=y -CONFIG_IP_VS_WLC=y -CONFIG_IP_VS_FO=y -CONFIG_IP_VS_OVF=y -CONFIG_IP_VS_LBLC=y -CONFIG_IP_VS_LBLCR=y -CONFIG_IP_VS_DH=y -CONFIG_IP_VS_SH=y -CONFIG_IP_VS_MH=y -CONFIG_IP_VS_SED=y -CONFIG_IP_VS_NQ=y -CONFIG_IP_VS_FTP=y -CONFIG_NFT_DUP_IPV4=y -CONFIG_NF_TABLES_ARP=y -CONFIG_NF_LOG_ARP=y -CONFIG_IP_NF_IPTABLES=y -CONFIG_IP_NF_MATCH_AH=y -CONFIG_IP_NF_MATCH_ECN=y -CONFIG_IP_NF_MATCH_RPFILTER=y -CONFIG_IP_NF_MATCH_TTL=y -CONFIG_IP_NF_FILTER=y -CONFIG_IP_NF_TARGET_REJECT=y -CONFIG_IP_NF_TARGET_SYNPROXY=y -CONFIG_IP_NF_NAT=y -CONFIG_IP_NF_TARGET_MASQUERADE=y -CONFIG_IP_NF_TARGET_NETMAP=y -CONFIG_IP_NF_TARGET_REDIRECT=y -CONFIG_IP_NF_MANGLE=y -CONFIG_IP_NF_TARGET_ECN=y -CONFIG_IP_NF_TARGET_TTL=y -CONFIG_IP_NF_RAW=y -CONFIG_IP_NF_SECURITY=y -CONFIG_IP_NF_ARPTABLES=y -CONFIG_IP_NF_ARPFILTER=y -CONFIG_IP_NF_ARP_MANGLE=y -CONFIG_NFT_DUP_IPV6=y -CONFIG_IP6_NF_IPTABLES=y -CONFIG_IP6_NF_MATCH_AH=y -CONFIG_IP6_NF_MATCH_EUI64=y -CONFIG_IP6_NF_MATCH_FRAG=y -CONFIG_IP6_NF_MATCH_OPTS=y -CONFIG_IP6_NF_MATCH_HL=y -CONFIG_IP6_NF_MATCH_IPV6HEADER=y -CONFIG_IP6_NF_MATCH_MH=y -CONFIG_IP6_NF_MATCH_RPFILTER=y -CONFIG_IP6_NF_MATCH_RT=y -CONFIG_IP6_NF_TARGET_HL=y -CONFIG_IP6_NF_FILTER=y -CONFIG_IP6_NF_TARGET_REJECT=y -CONFIG_IP6_NF_TARGET_SYNPROXY=y -CONFIG_IP6_NF_MANGLE=y -CONFIG_IP6_NF_RAW=y -CONFIG_IP6_NF_SECURITY=y -CONFIG_IP6_NF_NAT=y -CONFIG_IP6_NF_TARGET_MASQUERADE=y -CONFIG_IP6_NF_TARGET_NPT=y -CONFIG_NF_TABLES_BRIDGE=y -CONFIG_NFT_BRIDGE_REJECT=y -CONFIG_BRIDGE_NF_EBTABLES=y -CONFIG_BRIDGE_EBT_BROUTE=y -CONFIG_BRIDGE_EBT_T_FILTER=y -CONFIG_BRIDGE_EBT_T_NAT=y -CONFIG_BRIDGE_EBT_802_3=y -CONFIG_BRIDGE_EBT_AMONG=y -CONFIG_BRIDGE_EBT_ARP=y -CONFIG_BRIDGE_EBT_IP=y -CONFIG_BRIDGE_EBT_IP6=y -CONFIG_BRIDGE_EBT_LIMIT=y -CONFIG_BRIDGE_EBT_MARK=y -CONFIG_BRIDGE_EBT_PKTTYPE=y -CONFIG_BRIDGE_EBT_STP=y -CONFIG_BRIDGE_EBT_VLAN=y -CONFIG_BRIDGE_EBT_ARPREPLY=y -CONFIG_BRIDGE_EBT_DNAT=y -CONFIG_BRIDGE_EBT_MARK_T=y -CONFIG_BRIDGE_EBT_REDIRECT=y -CONFIG_BRIDGE_EBT_SNAT=y -CONFIG_BRIDGE_EBT_LOG=y -CONFIG_BRIDGE_EBT_NFLOG=y - -# --- Block devices --- -CONFIG_BLK_DEV=y -CONFIG_BLK_DEV_INTEGRITY=y -CONFIG_BLK_DEV_THROTTLING=y -CONFIG_BLK_CGROUP_IOLATENCY=y -CONFIG_PARTITION_ADVANCED=y -CONFIG_LDM_PARTITION=y -CONFIG_CMDLINE_PARTITION=y -CONFIG_BLK_DEV_LOOP=y -CONFIG_BLK_DEV_DRBD=m -CONFIG_BLK_DEV_NBD=y -CONFIG_BLK_DEV_RAM=y -CONFIG_BLK_DEV_RAM_SIZE=65536 -CONFIG_ATA_OVER_ETH=m -CONFIG_BLK_DEV_NVME=y - -# --- SCSI --- -CONFIG_SCSI=y -# CONFIG_SCSI_PROC_FS is not set -CONFIG_BLK_DEV_SD=y -CONFIG_BLK_DEV_SR=y -CONFIG_CHR_DEV_SG=y -CONFIG_ISCSI_TCP=m -CONFIG_SCSI_HPSA=y -CONFIG_MEGARAID_SAS=y -CONFIG_SCSI_MPT3SAS=y -CONFIG_SCSI_MPI3MR=m -CONFIG_SCSI_SMARTPQI=m -CONFIG_VMWARE_PVSCSI=y -CONFIG_XEN_SCSI_FRONTEND=y -CONFIG_SCSI_VIRTIO=y - -# --- ATA / SATA --- -CONFIG_ATA=y -# CONFIG_ATA_VERBOSE_ERROR is not set -# CONFIG_SATA_PMP is not set -CONFIG_SATA_AHCI=y -CONFIG_ATA_PIIX=y -CONFIG_SATA_MV=y -CONFIG_SATA_NV=y -CONFIG_SATA_PROMISE=y -CONFIG_SATA_SIL=y -CONFIG_SATA_SIS=y -CONFIG_SATA_SVW=y -CONFIG_SATA_ULI=y -CONFIG_SATA_VIA=y -CONFIG_SATA_VITESSE=y -CONFIG_ATA_GENERIC=y - -# --- Device mapper / MD --- -CONFIG_MD=y -CONFIG_BLK_DEV_MD=y -CONFIG_MD_LINEAR=y -CONFIG_MD_RAID0=y -CONFIG_MD_RAID1=y -CONFIG_MD_RAID10=y -CONFIG_MD_RAID456=y -CONFIG_MD_MULTIPATH=y -CONFIG_BLK_DEV_DM=y -CONFIG_DM_CRYPT=y -CONFIG_DM_SNAPSHOT=y -CONFIG_DM_THIN_PROVISIONING=y -CONFIG_DM_MULTIPATH=m -CONFIG_DM_MULTIPATH_QL=m -CONFIG_DM_MULTIPATH_ST=m - -# --- Fusion --- -CONFIG_FUSION=y -CONFIG_FUSION_SPI=y - -# --- Network drivers (built-in for reliable boot) --- -CONFIG_NETDEVICES=y -# CONFIG_NET_VENDOR_3COM is not set -# CONFIG_NET_VENDOR_ADAPTEC is not set -# CONFIG_NET_VENDOR_AGERE is not set -# CONFIG_NET_VENDOR_ALACRITECH is not set -# CONFIG_NET_VENDOR_ALTEON is not set -# CONFIG_NET_VENDOR_AMD is not set -# CONFIG_NET_VENDOR_AQUANTIA is not set -# CONFIG_NET_VENDOR_ARC is not set -# CONFIG_NET_VENDOR_ATHEROS is not set -# CONFIG_NET_VENDOR_CADENCE is not set -# CONFIG_NET_VENDOR_CAVIUM is not set -# CONFIG_NET_VENDOR_CHELSIO is not set -# CONFIG_NET_VENDOR_CORTINA is not set -# CONFIG_NET_VENDOR_DEC is not set -# CONFIG_NET_VENDOR_DLINK is not set -# CONFIG_NET_VENDOR_EMULEX is not set -# CONFIG_NET_VENDOR_EZCHIP is not set -# CONFIG_NET_VENDOR_HUAWEI is not set -# CONFIG_NET_VENDOR_I825XX is not set -# CONFIG_NET_VENDOR_MARVELL is not set -# CONFIG_NET_VENDOR_MICREL is not set -# CONFIG_NET_VENDOR_MICROCHIP is not set -# CONFIG_NET_VENDOR_MYRI is not set -# CONFIG_NET_VENDOR_NI is not set -# CONFIG_NET_VENDOR_NATSEMI is not set -# CONFIG_NET_VENDOR_NETERION is not set -# CONFIG_NET_VENDOR_NVIDIA is not set -# CONFIG_NET_VENDOR_OKI is not set -# CONFIG_NET_VENDOR_PACKET_ENGINES is not set -# CONFIG_NET_VENDOR_QLOGIC is not set -# CONFIG_NET_VENDOR_BROCADE is not set -# CONFIG_NET_VENDOR_QUALCOMM is not set -# CONFIG_NET_VENDOR_RDC is not set -# CONFIG_NET_VENDOR_RENESAS is not set -# CONFIG_NET_VENDOR_ROCKER is not set -# CONFIG_NET_VENDOR_SAMSUNG is not set -# CONFIG_NET_VENDOR_SEEQ is not set -# CONFIG_NET_VENDOR_SILAN is not set -# CONFIG_NET_VENDOR_SIS is not set -# CONFIG_NET_VENDOR_SMSC is not set -# CONFIG_NET_VENDOR_SOCIONEXT is not set -# CONFIG_NET_VENDOR_STMICRO is not set -# CONFIG_NET_VENDOR_SUN is not set -# CONFIG_NET_VENDOR_SYNOPSYS is not set -# CONFIG_NET_VENDOR_TEHUTI is not set -# CONFIG_NET_VENDOR_TI is not set -# CONFIG_NET_VENDOR_VIA is not set -# CONFIG_NET_VENDOR_WIZNET is not set -CONFIG_E1000=y -CONFIG_E1000E=y -CONFIG_IGB=y -CONFIG_IGBVF=y -CONFIG_IXGBE=y -CONFIG_IXGBEVF=y -CONFIG_I40E=y -CONFIG_I40EVF=y -CONFIG_ICE=y -CONFIG_IGC=y -CONFIG_MLX4_EN=y -CONFIG_MLX5_CORE=y -CONFIG_MLX5_CORE_EN=y -CONFIG_BNXT=y -CONFIG_CNIC=m -CONFIG_TIGON3=y -CONFIG_BNX2X=y -CONFIG_R8169=y -CONFIG_8139CP=y -CONFIG_8139TOO=y -CONFIG_VMXNET3=y -CONFIG_HYPERV_NET=y -CONFIG_ENA_ETHERNET=m -CONFIG_GVE=m -CONFIG_NFP=m -CONFIG_IONIC=y -CONFIG_VIRTIO_NET=y - -# --- USB network --- -CONFIG_USB_CATC=m -CONFIG_USB_KAWETH=m -CONFIG_USB_PEGASUS=m -CONFIG_USB_RTL8150=y -CONFIG_USB_RTL8152=y -CONFIG_USB_LAN78XX=m -CONFIG_USB_USBNET=y -CONFIG_USB_NET_AX8817X=m -CONFIG_USB_NET_AX88179_178A=m -CONFIG_USB_NET_CDC_EEM=m -CONFIG_USB_NET_CDC_NCM=m -CONFIG_USB_NET_NET1080=m -CONFIG_USB_NET_CDC_SUBSET=m -CONFIG_USB_NET_ZAURUS=m - -# --- WAN --- -CONFIG_WAN=y -CONFIG_HDLC=m -CONFIG_HDLC_CISCO=m - -# --- PPP --- -CONFIG_PPP=m -CONFIG_PPP_BSDCOMP=m -CONFIG_PPP_DEFLATE=m -CONFIG_PPP_FILTER=y -CONFIG_PPP_MPPE=m -CONFIG_PPP_MULTILINK=y -CONFIG_PPPOE=m -CONFIG_PPTP=m -CONFIG_PPPOL2TP=m -CONFIG_PPP_ASYNC=m -CONFIG_PPP_SYNC_TTY=m - -# --- USB --- -CONFIG_USB_SUPPORT=y -CONFIG_USB=y -CONFIG_USB_XHCI_HCD=y -CONFIG_USB_EHCI_HCD=y -CONFIG_USB_OHCI_HCD=y -CONFIG_USB_UHCI_HCD=y -CONFIG_USB_XHCI_PCI=y -CONFIG_USB_UAS=y -CONFIG_USB_STORAGE=y - -# --- USB serial (BMC/console access) --- -CONFIG_USB_SERIAL=y -CONFIG_USB_SERIAL_CONSOLE=y -CONFIG_USB_SERIAL_GENERIC=y -CONFIG_USB_SERIAL_CH341=y -CONFIG_USB_SERIAL_CP210X=y -CONFIG_USB_SERIAL_FTDI_SIO=y -CONFIG_USB_SERIAL_PL2303=y -CONFIG_USB_SERIAL_TI=y -CONFIG_USB_SERIAL_OPTION=y - -# --- Input --- -CONFIG_INPUT_FF_MEMLESS=y -CONFIG_INPUT_SPARSEKMAP=y -CONFIG_INPUT_MOUSEDEV=y -CONFIG_INPUT_MOUSEDEV_PSAUX=y -CONFIG_INPUT_EVDEV=y -# CONFIG_INPUT_MOUSE is not set -CONFIG_INPUT_MISC=y -CONFIG_INPUT_PCSPKR=y -CONFIG_INPUT_ATLAS_BTNS=y -CONFIG_INPUT_UINPUT=y -CONFIG_SERIO_PCIPS2=y -CONFIG_SERIO_RAW=y -# CONFIG_LEGACY_PTYS is not set -# CONFIG_INPUT_JOYSTICK is not set -# CONFIG_INPUT_TOUCHSCREEN is not set - -# --- MMC --- -CONFIG_MMC=y -CONFIG_MMC_SDHCI=y -CONFIG_MMC_SDHCI_PCI=y -# CONFIG_MMC_RICOH_MMC is not set -CONFIG_MMC_SDHCI_ACPI=y -CONFIG_MMC_SDHCI_PLTFM=y - -# --- Filesystems --- -CONFIG_EXT4_FS=y -CONFIG_EXT4_FS_POSIX_ACL=y -CONFIG_EXT4_FS_SECURITY=y -CONFIG_XFS_FS=y -CONFIG_XFS_QUOTA=y -CONFIG_XFS_POSIX_ACL=y -CONFIG_BTRFS_FS=m -CONFIG_BTRFS_FS_POSIX_ACL=y -CONFIG_FS_DAX=y -CONFIG_FS_ENCRYPTION=y -CONFIG_FANOTIFY=y -CONFIG_QUOTA=y -CONFIG_QUOTA_NETLINK_INTERFACE=y -CONFIG_VFAT_FS=y -CONFIG_MSDOS_FS=y -CONFIG_FAT_DEFAULT_IOCHARSET="utf8" -CONFIG_NTFS_FS=m -CONFIG_TMPFS=y -CONFIG_TMPFS_POSIX_ACL=y -CONFIG_TMPFS_XATTR=y -CONFIG_PROC_FS=y -CONFIG_PROC_KCORE=y -CONFIG_SYSFS=y -CONFIG_EFIVAR_FS=y -CONFIG_OVERLAY_FS=y -CONFIG_SQUASHFS=y -CONFIG_SQUASHFS_XATTR=y -CONFIG_SQUASHFS_LZ4=y -CONFIG_SQUASHFS_LZO=y -CONFIG_SQUASHFS_XZ=y -CONFIG_FUSE_FS=y -CONFIG_CUSE=y -CONFIG_ISO9660_FS=y -CONFIG_JOLIET=y -CONFIG_ZISOFS=y -CONFIG_UDF_FS=y -CONFIG_FSCACHE=y -CONFIG_FSCACHE_STATS=y -CONFIG_CACHEFILES=y -CONFIG_NFS_FS=m -# CONFIG_NFS_V2 is not set -CONFIG_NFS_V4=m -CONFIG_NFS_V4_1=y -CONFIG_NFS_V4_2=y -CONFIG_NFS_FSCACHE=y -CONFIG_NFSD=m -CONFIG_NFSD_V4=y -CONFIG_CEPH_FS=m -CONFIG_CEPH_FSCACHE=y -CONFIG_CEPH_FS_POSIX_ACL=y -CONFIG_CIFS=y -# CONFIG_CIFS_ALLOW_INSECURE_LEGACY is not set -CONFIG_CIFS_XATTR=y -CONFIG_CIFS_DFS_UPCALL=y -CONFIG_CIFS_FSCACHE=y -CONFIG_9P_FS=y -CONFIG_9P_FSCACHE=y -CONFIG_9P_FS_POSIX_ACL=y -CONFIG_9P_FS_SECURITY=y - -# --- NLS --- -CONFIG_NLS_CODEPAGE_437=y -CONFIG_NLS_ASCII=y -CONFIG_NLS_ISO8859_1=y -CONFIG_NLS_UTF8=y - -# --- Virtio (built-in for reliable boot) --- -CONFIG_VIRTIO=y -CONFIG_VIRTIO_PCI=y -CONFIG_VIRTIO_BLK=y -CONFIG_VIRTIO_CONSOLE=y -CONFIG_VIRTIO_BALLOON=y -CONFIG_VIRTIO_INPUT=y -CONFIG_VIRTIO_MMIO=y -CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y -CONFIG_HW_RANDOM_VIRTIO=y -CONFIG_VHOST_NET=m -CONFIG_VHOST_VSOCK=m - -# --- Console / Serial --- -CONFIG_VT=y -CONFIG_VT_CONSOLE=y -CONFIG_SERIAL_8250=y -CONFIG_SERIAL_8250_CONSOLE=y -CONFIG_SERIAL_8250_NR_UARTS=32 -# CONFIG_SERIAL_8250_MID is not set -CONFIG_SERIAL_DEV_BUS=y -CONFIG_TTY_PRINTK=y -CONFIG_HW_CONSOLE=y -CONFIG_VGA_CONSOLE=y -CONFIG_DUMMY_CONSOLE=y - -# --- IPMI --- -CONFIG_IPMI_HANDLER=y -CONFIG_IPMI_DMI_DECODE=y -CONFIG_IPMI_DEVICE_INTERFACE=y -CONFIG_IPMI_SI=y -CONFIG_IPMI_POWEROFF=y - -# --- Hardware RNG --- -CONFIG_HW_RANDOM=y -CONFIG_HW_RANDOM_TIMERIOMEM=y - -# --- I2C --- -CONFIG_I2C_CHARDEV=y -CONFIG_I2C_MUX=y - -# --- TPM --- -CONFIG_TCG_TIS_I2C_ATMEL=m -CONFIG_TCG_TIS_I2C_INFINEON=m -CONFIG_TCG_TIS_I2C_NUVOTON=m -CONFIG_TCG_NSC=m -CONFIG_TCG_ATMEL=m -CONFIG_TCG_INFINEON=m -CONFIG_TCG_XEN=m -CONFIG_TCG_VTPM_PROXY=m -CONFIG_TCG_TIS_ST33ZP24_I2C=m - -# --- Platform / MFD --- -CONFIG_NVRAM=y -CONFIG_HPET=y -CONFIG_HANGCHECK_TIMER=y -CONFIG_LPC_ICH=y -CONFIG_LPC_SCH=y -CONFIG_MFD_INTEL_LPSS_ACPI=y -CONFIG_MFD_INTEL_LPSS_PCI=y -CONFIG_MFD_SM501=y -CONFIG_MFD_VX855=y -# CONFIG_THERMAL_HWMON is not set - -# --- RTC --- -CONFIG_RTC_CLASS=y - -# --- DMA --- -CONFIG_DMADEVICES=y - -# --- Security --- -CONFIG_SECCOMP=y -CONFIG_SECCOMP_FILTER=y -CONFIG_SECURITY=y -CONFIG_SECURITY_NETWORK=y -CONFIG_SECURITY_NETWORK_XFRM=y -CONFIG_SECURITY_PATH=y -CONFIG_SECURITY_DMESG_RESTRICT=y -CONFIG_KEYS=y -CONFIG_PERSISTENT_KEYRINGS=y -CONFIG_TRUSTED_KEYS=y -CONFIG_KEY_DH_OPERATIONS=y -CONFIG_HARDENED_USERCOPY=y -CONFIG_FORTIFY_SOURCE=y -CONFIG_STATIC_USERMODEHELPER=y -CONFIG_SECURITY_YAMA=y -CONFIG_INTEGRITY_SIGNATURE=y -CONFIG_INTEGRITY_ASYMMETRIC_KEYS=y -CONFIG_IMA=y -CONFIG_IMA_DEFAULT_HASH_SHA256=y -CONFIG_IMA_READ_POLICY=y -CONFIG_IMA_APPRAISE=y -CONFIG_EVM=y -CONFIG_LSM="yama,loadpin,safesetid,integrity" - -# --- Crypto --- -CONFIG_CRYPTO=y -CONFIG_CRYPTO_USER=y -CONFIG_CRYPTO_AES=y -CONFIG_CRYPTO_SHA256=y -CONFIG_CRYPTO_SHA512=y -CONFIG_CRYPTO_XTS=y -CONFIG_CRYPTO_ANUBIS=y -CONFIG_CRYPTO_BLOWFISH=y -CONFIG_CRYPTO_CAMELLIA=y -CONFIG_CRYPTO_DES=y -CONFIG_CRYPTO_FCRYPT=y -CONFIG_CRYPTO_KHAZAD=y -CONFIG_CRYPTO_SEED=y -CONFIG_CRYPTO_TEA=y -CONFIG_CRYPTO_TWOFISH=y -CONFIG_CRYPTO_ARC4=y -CONFIG_CRYPTO_KEYWRAP=y -CONFIG_CRYPTO_LRW=y -CONFIG_CRYPTO_PCBC=y -CONFIG_CRYPTO_CHACHA20POLY1305=y -CONFIG_CRYPTO_SEQIV=y -CONFIG_CRYPTO_ECHAINIV=y -CONFIG_CRYPTO_MICHAEL_MIC=y -CONFIG_CRYPTO_RMD160=y -CONFIG_CRYPTO_VMAC=y -CONFIG_CRYPTO_WP512=y -CONFIG_CRYPTO_XCBC=y -CONFIG_CRYPTO_CRC32=y -CONFIG_CRYPTO_LZO=y -CONFIG_CRYPTO_842=y -CONFIG_CRYPTO_LZ4=y -CONFIG_CRYPTO_LZ4HC=y -CONFIG_CRYPTO_ANSI_CPRNG=y -CONFIG_CRYPTO_USER_API_HASH=y -CONFIG_CRYPTO_USER_API_SKCIPHER=y -CONFIG_CRYPTO_USER_API_RNG=y -CONFIG_CRYPTO_USER_API_AEAD=y - -# --- x86_64 hardware crypto acceleration --- -CONFIG_CRYPTO_AES_NI_INTEL=y -CONFIG_CRYPTO_BLOWFISH_X86_64=y -CONFIG_CRYPTO_CAMELLIA_AESNI_AVX2_X86_64=y -CONFIG_CRYPTO_CAST5_AVX_X86_64=y -CONFIG_CRYPTO_CAST6_AVX_X86_64=y -CONFIG_CRYPTO_DES3_EDE_X86_64=y -CONFIG_CRYPTO_SERPENT_SSE2_X86_64=y -CONFIG_CRYPTO_SERPENT_AVX2_X86_64=y -CONFIG_CRYPTO_TWOFISH_AVX_X86_64=y -CONFIG_CRYPTO_CHACHA20_X86_64=y -CONFIG_CRYPTO_POLY1305_X86_64=y -CONFIG_CRYPTO_SHA1_SSSE3=y -CONFIG_CRYPTO_SHA256_SSSE3=y -CONFIG_CRYPTO_SHA512_SSSE3=y -CONFIG_CRYPTO_GHASH_CLMUL_NI_INTEL=y -CONFIG_CRYPTO_CRC32C_INTEL=y -CONFIG_CRYPTO_CRC32_PCLMUL=y -CONFIG_CRYPTO_DEV_PADLOCK=y -CONFIG_CRYPTO_DEV_PADLOCK_AES=y -CONFIG_CRYPTO_DEV_PADLOCK_SHA=y -CONFIG_CRYPTO_DEV_VIRTIO=m -CONFIG_PKCS7_MESSAGE_PARSER=y - -# --- BPF --- -CONFIG_BPF=y -CONFIG_BPF_SYSCALL=y -CONFIG_BPF_JIT=y -CONFIG_BPF_JIT_ALWAYS_ON=y - -# --- PCI --- -CONFIG_PCI=y -CONFIG_PCIEPORTBUS=y -CONFIG_HOTPLUG_PCI_PCIE=y -CONFIG_PCI_STUB=y -CONFIG_PCI_IOV=y -# CONFIG_VGA_ARB is not set -CONFIG_HOTPLUG_PCI=y -CONFIG_HOTPLUG_PCI_ACPI=y -CONFIG_HOTPLUG_PCI_SHPC=y -CONFIG_PCI_HYPERV_INTERFACE=m - -# --- IOMMU --- -CONFIG_AMD_IOMMU=y -CONFIG_INTEL_IOMMU=y -CONFIG_IRQ_REMAP=y - -# --- Xen --- -# CONFIG_XEN_BACKEND is not set -CONFIG_XEN_GNTDEV=y -CONFIG_XEN_GRANT_DEV_ALLOC=y -CONFIG_XEN_PVCALLS_FRONTEND=y -CONFIG_XEN_ACPI_PROCESSOR=y -# CONFIG_XEN_SYMS is not set - -# --- Intel platform --- -CONFIG_INTEL_IPS=y - -# --- DEVFREQ --- -CONFIG_PM_DEVFREQ=y -CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y - -# --- Reset --- -CONFIG_RESET_CONTROLLER=y - -# --- DAX --- -CONFIG_DEV_DAX=y -CONFIG_DEV_DAX_PMEM=m - -# --- Misc --- -CONFIG_FW_LOADER=y -CONFIG_PRINTK=y -CONFIG_PRINTK_TIME=y -CONFIG_MODULES=y -CONFIG_MODULE_UNLOAD=y -CONFIG_EARLY_PRINTK=y -CONFIG_PANIC_TIMEOUT=10 -CONFIG_PANIC_ON_OOPS=y -CONFIG_MAGIC_SYSRQ=y -CONFIG_UEVENT_HELPER=y -CONFIG_DEVTMPFS=y -CONFIG_DEVTMPFS_MOUNT=y -CONFIG_CONNECTOR=y -CONFIG_DMI_SYSFS=y -CONFIG_SYSFB_SIMPLEFB=y -CONFIG_RESET_ATTACK_MITIGATION=y -# CONFIG_PNP_DEBUG_MESSAGES is not set -CONFIG_HYPERV=y -CONFIG_HYPERV_UTILS=y -CONFIG_HYPERV_BALLOON=y - -# --- Module compression --- -CONFIG_MODULE_COMPRESS_ZSTD=y -CONFIG_MODULE_DECOMPRESS=y - -# --- EFI boot support --- -CONFIG_EFI=y -CONFIG_EFI_STUB=y -CONFIG_EFI_MIXED=y - -# --- Framebuffer/Console (required for EFI boot video output) --- -CONFIG_DRM=y -CONFIG_DRM_I915=y -CONFIG_DRM_SIMPLEDRM=y -CONFIG_FB=y -CONFIG_FB_EFI=y -CONFIG_FRAMEBUFFER_CONSOLE=y - -# --- Debugging / Diagnostics --- -CONFIG_FRAME_WARN=1024 -CONFIG_BUG_ON_DATA_CORRUPTION=y -CONFIG_HARDLOCKUP_DETECTOR=y -CONFIG_WQ_WATCHDOG=y -CONFIG_DEBUG_NOTIFIERS=y -CONFIG_RCU_CPU_STALL_TIMEOUT=60 -# CONFIG_RCU_TRACE is not set -CONFIG_IO_STRICT_DEVMEM=y - -# --- Disable unnecessary subsystems --- -# CONFIG_SOUND is not set -# CONFIG_WIRELESS is not set -# CONFIG_WLAN is not set -# CONFIG_BLUETOOTH is not set -# CONFIG_NFC is not set -# CONFIG_INFINIBAND is not set -# CONFIG_MEDIA_SUPPORT is not set diff --git a/kernel.configs/6.19.y.arm64 b/kernel.configs/6.19.y.arm64 deleted file mode 100644 index 795c91e..0000000 --- a/kernel.configs/6.19.y.arm64 +++ /dev/null @@ -1,515 +0,0 @@ -# CaptainOS kernel defconfig for arm64 (aarch64) -# Minimal config for bare-metal provisioning with container/network support. -# Based on 6.12.y config with targeted additions for CaptainOS use case, adapted for kernel 6.18. -# -# Generate a full .config from this: -# cp 6.18.y.arm64 .config && make ARCH=arm64 olddefconfig - -# --- Architecture --- -CONFIG_ARM64=y -CONFIG_SMP=y -CONFIG_NR_CPUS=128 -CONFIG_NUMA=y -CONFIG_ARM64_VA_BITS_48=y -CONFIG_SCHED_MC=y -CONFIG_SCHED_SMT=y -CONFIG_HZ_1000=y -CONFIG_XEN=y -CONFIG_COMPAT=y -CONFIG_RANDOMIZE_BASE=y -CONFIG_ARM64_ACPI_PARKING_PROTOCOL=y - -# --- General --- -CONFIG_LOCALVERSION="-captainos" -CONFIG_DEFAULT_HOSTNAME="captainos" -CONFIG_SYSVIPC=y -CONFIG_POSIX_MQUEUE=y -CONFIG_AUDIT=y -CONFIG_NO_HZ_IDLE=y -CONFIG_HIGH_RES_TIMERS=y -CONFIG_PREEMPT_VOLUNTARY=y -CONFIG_BSD_PROCESS_ACCT=y -CONFIG_KEXEC=y -CONFIG_KEXEC_FILE=y -CONFIG_CC_OPTIMIZE_FOR_SIZE=y -CONFIG_CGROUPS=y -CONFIG_CGROUP_FREEZER=y -CONFIG_CGROUP_PIDS=y -CONFIG_CGROUP_DEVICE=y -CONFIG_CGROUP_CPUACCT=y -CONFIG_CGROUP_PERF=y -CONFIG_CGROUP_BPF=y -CONFIG_CGROUP_MISC=y -CONFIG_CGROUP_HUGETLB=y -CONFIG_CGROUP_RDMA=y -CONFIG_CPUSETS=y -CONFIG_MEMCG=y -CONFIG_BLK_CGROUP=y -CONFIG_BLK_CGROUP_IOLATENCY=y -CONFIG_BLK_DEV_THROTTLING=y -CONFIG_CFS_BANDWIDTH=y -CONFIG_RT_GROUP_SCHED=y -CONFIG_CGROUP_SCHED=y -CONFIG_CGROUP_NET_PRIO=y -CONFIG_NAMESPACES=y -CONFIG_USER_NS=y -CONFIG_PID_NS=y -CONFIG_NET_NS=y -CONFIG_UTS_NS=y -CONFIG_IPC_NS=y -CONFIG_CHECKPOINT_RESTORE=y -# Memory management -CONFIG_TRANSPARENT_HUGEPAGE=y -CONFIG_KSM=y -CONFIG_MEMORY_HOTPLUG=y -CONFIG_MEMORY_HOTREMOVE=y -CONFIG_HUGETLBFS=y - -# --- Init / Initramfs --- -CONFIG_BLK_DEV_INITRD=y -CONFIG_INITRAMFS_SOURCE="" - -# --- Networking --- -CONFIG_NET=y -CONFIG_PACKET=y -CONFIG_UNIX=y -CONFIG_INET=y -CONFIG_IP_MULTICAST=y -CONFIG_IP_ADVANCED_ROUTER=y -CONFIG_IP_MULTIPLE_TABLES=y -CONFIG_IP_ROUTE_MULTIPATH=y -CONFIG_IP_PNP=y -CONFIG_IP_PNP_DHCP=y -CONFIG_IP_PNP_BOOTP=y -CONFIG_NET_IPIP=m -CONFIG_NET_IPGRE_DEMUX=m -CONFIG_NET_IPGRE=m -CONFIG_SYN_COOKIES=y -CONFIG_IPV6=y -CONFIG_BRIDGE=y -CONFIG_VLAN_8021Q=m -CONFIG_BONDING=m -CONFIG_TUN=y -CONFIG_VETH=y -CONFIG_MACVLAN=y -CONFIG_MACVTAP=y -CONFIG_IPVLAN=y -CONFIG_TAP=y -CONFIG_DUMMY=m -CONFIG_VXLAN=y -CONFIG_GENEVE=m -CONFIG_NLMON=y -# Traffic control / QoS (needed by CNI plugins) -CONFIG_NET_SCHED=y -CONFIG_NET_SCH_HTB=m -CONFIG_NET_SCH_PRIO=m -CONFIG_NET_SCH_SFQ=m -CONFIG_NET_SCH_TBF=m -CONFIG_NET_SCH_NETEM=m -CONFIG_NET_SCH_INGRESS=m -CONFIG_NET_CLS_BASIC=y -CONFIG_NET_CLS_FW=y -CONFIG_NET_CLS_U32=y -CONFIG_NET_CLS_CGROUP=y -CONFIG_NET_CLS_BPF=y -CONFIG_NET_CLS_MATCHALL=y -CONFIG_NET_CLS_ACT=y -CONFIG_NET_ACT_POLICE=y -CONFIG_NET_ACT_GACT=y -CONFIG_NET_ACT_MIRRED=y -CONFIG_NET_ACT_BPF=y -CONFIG_NET_EMATCH=y -CONFIG_NET_EMATCH_U32=y - -# --- Netfilter --- -CONFIG_NETFILTER=y -CONFIG_NETFILTER_ADVANCED=y -CONFIG_NF_CONNTRACK=y -CONFIG_NF_NAT=y -CONFIG_NF_TABLES=y -CONFIG_NF_TABLES_INET=y -CONFIG_NF_TABLES_IPV4=y -CONFIG_NF_TABLES_IPV6=y -CONFIG_NFT_NAT=y -CONFIG_NFT_MASQ=y -CONFIG_NFT_CT=y -CONFIG_NFT_COMPAT=y -CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y -CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y -CONFIG_NETFILTER_XT_MATCH_COMMENT=y -CONFIG_NETFILTER_XT_MATCH_MARK=y -CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y -CONFIG_IP_NF_IPTABLES=y -CONFIG_IP_NF_FILTER=y -CONFIG_IP_NF_NAT=y -CONFIG_IP6_NF_IPTABLES=y -CONFIG_IP6_NF_FILTER=y -CONFIG_IP6_NF_NAT=y -CONFIG_BRIDGE_NETFILTER=y -CONFIG_BRIDGE_NF_EBTABLES=y -CONFIG_BRIDGE_EBT_BROUTE=y -CONFIG_BRIDGE_EBT_T_FILTER=y -CONFIG_BRIDGE_EBT_T_NAT=y -CONFIG_BRIDGE_EBT_ARP=y -CONFIG_BRIDGE_EBT_IP=y -CONFIG_BRIDGE_EBT_IP6=y -CONFIG_BRIDGE_EBT_MARK=y -CONFIG_BRIDGE_EBT_MARK_T=y -CONFIG_BRIDGE_EBT_VLAN=y -# IPVS (container load balancing / kube-proxy) -CONFIG_IP_VS=y -CONFIG_IP_VS_IPV6=y -CONFIG_IP_VS_PROTO_TCP=y -CONFIG_IP_VS_PROTO_UDP=y -CONFIG_IP_VS_RR=y -CONFIG_IP_VS_WRR=y -CONFIG_IP_VS_SH=y -CONFIG_IP_VS_NFCT=y -# IP sets (used by iptables/nftables for efficient matching) -CONFIG_IP_SET=y -CONFIG_IP_SET_HASH_IP=y -CONFIG_IP_SET_HASH_IPPORT=y -CONFIG_IP_SET_HASH_IPPORTNET=y -CONFIG_IP_SET_HASH_NET=y -CONFIG_IP_SET_HASH_NETPORT=y -CONFIG_IP_SET_BITMAP_IP=y -CONFIG_IP_SET_BITMAP_PORT=y -CONFIG_IP_SET_LIST_SET=y -CONFIG_NETFILTER_XT_SET=y - -# --- Block devices --- -CONFIG_BLK_DEV=y -CONFIG_BLK_DEV_LOOP=y -CONFIG_BLK_DEV_NBD=y -CONFIG_BLK_DEV_RAM=y -CONFIG_BLK_DEV_RAM_SIZE=65536 -CONFIG_BLK_DEV_NVME=y -CONFIG_ATA=y -CONFIG_SATA_AHCI=y -CONFIG_SATA_AHCI_PLATFORM=y - -# --- SCSI --- -CONFIG_SCSI=y -CONFIG_BLK_DEV_SD=y -CONFIG_CHR_DEV_SG=y -CONFIG_MEGARAID_SAS=y -CONFIG_SCSI_MPT3SAS=y -CONFIG_SCSI_MPI3MR=m -CONFIG_SCSI_HPSA=y -CONFIG_SCSI_HISI_SAS=y -CONFIG_SCSI_HISI_SAS_PCI=y -CONFIG_XEN_SCSI_FRONTEND=y -CONFIG_SCSI_VIRTIO=y - -# --- Device mapper --- -CONFIG_MD=y -CONFIG_BLK_DEV_DM=y -CONFIG_DM_CRYPT=y -CONFIG_DM_THIN_PROVISIONING=y -CONFIG_DM_SNAPSHOT=y -CONFIG_DM_MULTIPATH=m -CONFIG_MD_RAID0=m -CONFIG_MD_RAID1=m - -# --- Network drivers (built-in for reliable boot) --- -CONFIG_NETDEVICES=y -CONFIG_VIRTIO_NET=y -CONFIG_E1000E=y -CONFIG_IGB=y -CONFIG_IXGBE=y -CONFIG_I40E=y -CONFIG_ICE=y -CONFIG_IGC=y -CONFIG_MLX4_EN=y -CONFIG_MLX5_CORE=y -CONFIG_MLX5_CORE_EN=y -CONFIG_BNXT=y -CONFIG_TIGON3=y -CONFIG_R8169=y -CONFIG_THUNDER_NIC_BGX=m -CONFIG_THUNDER_NIC_PF=m -CONFIG_THUNDER_NIC_VF=m -CONFIG_MACB=m -CONFIG_HNS3=m -CONFIG_NET_XGENE=y -CONFIG_MVNETA=y -CONFIG_MVPP2=y -CONFIG_STMMAC_ETH=m -CONFIG_ENA_ETHERNET=m -CONFIG_GVE=m -CONFIG_VMXNET3=y -CONFIG_AMD_XGBE=y -# Broadcom GENET (Raspberry Pi 4B built-in Ethernet) -CONFIG_BCMGENET=y -# PHY subsystem (required by GENET and other MAC drivers) -CONFIG_PHYLIB=y -CONFIG_MDIO_BUS=y -CONFIG_BROADCOM_PHY=y -CONFIG_BCM7XXX_PHY=y -CONFIG_MDIO_BCM_UNIMAC=y -CONFIG_MARVELL_PHY=y -CONFIG_MARVELL_10G_PHY=y -CONFIG_MICREL_PHY=y -CONFIG_ROCKCHIP_PHY=y -CONFIG_REALTEK_PHY=y -CONFIG_MESON_GXL_PHY=y -CONFIG_AMD_PHY=y - -# --- USB --- -CONFIG_USB_SUPPORT=y -CONFIG_USB=y -CONFIG_USB_XHCI_HCD=y -CONFIG_USB_EHCI_HCD=y -CONFIG_USB_OHCI_HCD=y -CONFIG_USB_XHCI_PCI=y -CONFIG_USB_UAS=y -CONFIG_USB_STORAGE=y -CONFIG_USB_DWC3=y -CONFIG_USB_DWC3_HOST=y -CONFIG_USB_DWC2=y -CONFIG_USB_ISP1760=y - -# --- MMC/SD (Raspberry Pi, Rockchip, and other SBCs) --- -CONFIG_MMC=y -CONFIG_MMC_BLOCK_MINORS=32 -CONFIG_MMC_SDHCI=y -CONFIG_MMC_SDHCI_PCI=y -CONFIG_MMC_SDHCI_ACPI=y -CONFIG_MMC_SDHCI_PLTFM=y -CONFIG_MMC_SDHCI_OF_ARASAN=y -CONFIG_MMC_SDHCI_OF_DWCMSHC=y -CONFIG_MMC_SDHCI_CADENCE=y -CONFIG_MMC_SDHCI_TEGRA=y -CONFIG_MMC_SDHCI_MSM=y -CONFIG_MMC_SDHCI_XENON=y -CONFIG_MMC_SDHCI_F_SDH30=y -CONFIG_MMC_ARMMMCI=y -CONFIG_MMC_DW=y -CONFIG_MMC_DW_ROCKCHIP=y -CONFIG_MMC_DW_K3=y -CONFIG_MMC_MESON_GX=y -CONFIG_MMC_SUNXI=m -CONFIG_MMC_SPI=y -CONFIG_MMC_HSQ=y - -# --- Filesystems --- -CONFIG_EXT4_FS=y -CONFIG_XFS_FS=m -CONFIG_VFAT_FS=y -CONFIG_TMPFS=y -CONFIG_TMPFS_POSIX_ACL=y -CONFIG_PROC_FS=y -CONFIG_SYSFS=y -CONFIG_OVERLAY_FS=y -CONFIG_SQUASHFS=y -CONFIG_SQUASHFS_XATTR=y -CONFIG_SQUASHFS_LZ4=y -CONFIG_SQUASHFS_LZO=y -CONFIG_SQUASHFS_XZ=y -CONFIG_NFS_FS=m -CONFIG_NFS_V4=m -CONFIG_NFS_V4_1=y -CONFIG_NFS_V4_2=y -CONFIG_FUSE_FS=y -CONFIG_CUSE=y -CONFIG_EFIVAR_FS=y -CONFIG_ISO9660_FS=y -CONFIG_JOLIET=y -CONFIG_UDF_FS=y -CONFIG_MSDOS_FS=y -CONFIG_FAT_DEFAULT_IOCHARSET="utf8" -CONFIG_NTFS_FS=m -CONFIG_TMPFS_XATTR=y -CONFIG_NLS_CODEPAGE_437=y -CONFIG_NLS_ASCII=y -CONFIG_NLS_ISO8859_1=y -CONFIG_NLS_UTF8=y - -# --- Virtio (built-in for reliable boot) --- -CONFIG_VIRTIO=y -CONFIG_VIRTIO_PCI=y -CONFIG_VIRTIO_BLK=y -CONFIG_VIRTIO_CONSOLE=y -CONFIG_VIRTIO_BALLOON=y -CONFIG_VIRTIO_INPUT=y -CONFIG_VIRTIO_MMIO=y -CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y -CONFIG_HW_RANDOM_VIRTIO=y - -# --- Console / Serial --- -CONFIG_SERIAL_AMBA_PL011=y -CONFIG_SERIAL_AMBA_PL011_CONSOLE=y -CONFIG_SERIAL_8250=y -CONFIG_SERIAL_8250_CONSOLE=y -CONFIG_VT=y -CONFIG_VT_CONSOLE=y -CONFIG_HW_CONSOLE=y -CONFIG_DUMMY_CONSOLE=y - -# --- Security --- -CONFIG_SECCOMP=y -CONFIG_SECCOMP_FILTER=y -CONFIG_SECURITY=y -CONFIG_KEYS=y - -# --- Crypto --- -CONFIG_CRYPTO=y -CONFIG_CRYPTO_AES=y -CONFIG_CRYPTO_SHA256=y -CONFIG_CRYPTO_SHA512=y -CONFIG_CRYPTO_XTS=y -CONFIG_ARM64_CRYPTO=y -CONFIG_CRYPTO_AES_ARM64_CE=y -CONFIG_CRYPTO_SHA2_ARM64_CE=y - -# --- BPF --- -CONFIG_BPF=y -CONFIG_BPF_SYSCALL=y -CONFIG_BPF_JIT=y -CONFIG_BPF_JIT_ALWAYS_ON=y -CONFIG_XDP_SOCKETS=y - -# --- ARM64 SoC/platform support --- -CONFIG_ARCH_BCM2835=y -CONFIG_ARCH_ROCKCHIP=y -CONFIG_ARCH_MESON=y -CONFIG_ARCH_SUNXI=y -CONFIG_ARCH_TEGRA=y -CONFIG_ARCH_MVEBU=y -CONFIG_ARCH_HISI=y -CONFIG_ARCH_QCOM=y -CONFIG_ARCH_EXYNOS=y -CONFIG_ARCH_VEXPRESS=y -CONFIG_ARCH_XGENE=y -CONFIG_ARCH_THUNDER=y -CONFIG_ARCH_THUNDER2=y -CONFIG_ARCH_SEATTLE=y -CONFIG_ARCH_SYNQUACER=y -CONFIG_ARCH_UNIPHIER=y -# ACPI / UEFI -CONFIG_ACPI=y -CONFIG_ACPI_DOCK=y -CONFIG_ACPI_IPMI=y -CONFIG_ACPI_APEI=y -CONFIG_ACPI_APEI_GHES=y -CONFIG_IPMI_HANDLER=m -CONFIG_IPMI_DMI_DECODE=y -CONFIG_IPMI_DEVICE_INTERFACE=m -CONFIG_IPMI_SI=m -CONFIG_PCI=y -CONFIG_PCIEPORTBUS=y -CONFIG_HOTPLUG_PCI=y -CONFIG_PCI_HOST_GENERIC=y -CONFIG_PCIE_ROCKCHIP_HOST=y -CONFIG_PCIE_ARMADA_8K=y -CONFIG_PCIE_TEGRA194_HOST=y -# Broadcom STB PCIe controller (Raspberry Pi 4B — needed for VL805 USB 3.0) -CONFIG_PCIE_BRCMSTB=y -CONFIG_PCIE_ALTERA=y -CONFIG_PCI_AARDVARK=y -CONFIG_PCI_TEGRA=y -CONFIG_PCI_XGENE=y -CONFIG_PCI_HOST_THUNDER_PEM=y -CONFIG_PCI_HOST_THUNDER_ECAM=y -CONFIG_PCIE_QCOM=y -CONFIG_PCIE_AL=y -CONFIG_PCI_MESON=y -CONFIG_PCI_HISI=y -CONFIG_PCIE_KIRIN=y -CONFIG_PCIE_HISI_STB=y -CONFIG_PCI_IOV=y -CONFIG_PCI_PASID=y -CONFIG_HOTPLUG_PCI_ACPI=y -# IOMMU -CONFIG_ARM_SMMU=y -CONFIG_ARM_SMMU_V3=y -CONFIG_FW_LOADER=y -# Raspberry Pi firmware mailbox (required for GENET MAC address, clocks, etc.) -CONFIG_RASPBERRYPI_FIRMWARE=y -CONFIG_RASPBERRYPI_POWER=y -CONFIG_ARM_SCMI_PROTOCOL=y -CONFIG_ARM_SCPI_PROTOCOL=y -CONFIG_OF=y -CONFIG_USE_OF=y -# RTC -CONFIG_RTC_CLASS=y -CONFIG_RTC_DRV_EFI=y -# DMA -CONFIG_DMADEVICES=y -CONFIG_PL330_DMA=y -CONFIG_PRINTK=y -CONFIG_PRINTK_TIME=y -CONFIG_MODULES=y -CONFIG_MODULE_UNLOAD=y -CONFIG_PANIC_TIMEOUT=10 -CONFIG_PANIC_ON_OOPS=y -CONFIG_MAGIC_SYSRQ=y -CONFIG_RD_GZIP=y -CONFIG_RD_XZ=y -CONFIG_RD_ZSTD=y -CONFIG_UEVENT_HELPER=y -CONFIG_DEVTMPFS=y -CONFIG_DEVTMPFS_MOUNT=y -CONFIG_CONNECTOR=y -CONFIG_DMI_SYSFS=y -CONFIG_EFI=y -CONFIG_EFI_STUB=y - -# --- Framebuffer/Console (required for EFI boot video output) --- -CONFIG_DRM=y -CONFIG_DRM_SIMPLEDRM=y -CONFIG_DRM_VIRTIO_GPU=y -CONFIG_FB=y -CONFIG_FB_EFI=y -CONFIG_FRAMEBUFFER_CONSOLE=y -CONFIG_SYSFB_SIMPLEFB=y -# Virtualization -CONFIG_VIRTUALIZATION=y -CONFIG_KVM=y -CONFIG_VFIO=y -CONFIG_VFIO_PCI=y -CONFIG_VHOST_NET=m -CONFIG_VHOST_VSOCK=m - -# --- Module compression --- -CONFIG_MODULE_COMPRESS_ZSTD=y -CONFIG_MODULE_DECOMPRESS=y - -# --- I2C (SBC board management, EEPROM, sensors) --- -CONFIG_I2C=y -CONFIG_I2C_CHARDEV=y -CONFIG_I2C_MV64XXX=y -CONFIG_I2C_RK3X=y -CONFIG_I2C_TEGRA=y -CONFIG_I2C_DESIGNWARE_PLATFORM=y -# SPI -CONFIG_SPI=y -CONFIG_SPI_ROCKCHIP=y -CONFIG_SPI_PL022=y -CONFIG_SPI_ORION=y -# GPIO -CONFIG_GPIOLIB=y -# Hardware watchdog -CONFIG_WATCHDOG=y -CONFIG_SOFTLOCKUP_DETECTOR=y -CONFIG_WQ_WATCHDOG=y -# Hardware RNG -CONFIG_HW_RANDOM=y -CONFIG_HW_RANDOM_TIMERIOMEM=y -# TPM -CONFIG_TCG_TIS=y -CONFIG_TCG_FTPM_TEE=y -CONFIG_TEE=y -CONFIG_OPTEE=y - -# --- Disable unnecessary subsystems --- -# CONFIG_SOUND is not set -# CONFIG_WIRELESS is not set -# CONFIG_WLAN is not set -# CONFIG_BLUETOOTH is not set -# CONFIG_NFC is not set -# CONFIG_INFINIBAND is not set -# CONFIG_MEDIA_SUPPORT is not set -CONFIG_INPUT_EVDEV=y -# CONFIG_INPUT_JOYSTICK is not set -# CONFIG_INPUT_TOUCHSCREEN is not set From 052cdca4c2544ada615c9f3290345e9eab226481 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 20:41:35 +0200 Subject: [PATCH 18/93] mkosi.finalize: drop kernel sound/media/net-wireless modules - mkosi.finalize: clean up logging a bit - mkosi.postinst: debugs Signed-off-by: Ricardo Pardini --- mkosi.finalize | 26 +++++++++++++------------- mkosi.postinst | 9 +++++++++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/mkosi.finalize b/mkosi.finalize index 32309de..f97b742 100755 --- a/mkosi.finalize +++ b/mkosi.finalize @@ -59,13 +59,11 @@ done # --------------------------------------------------------------------------- MODDIR="$BUILDROOT/usr/lib/modules" -echo "==> Listing all installed kernel modules: ${MODDIR}" -du -h -x -d 5 "$MODDIR" | sort -h || true - if [[ -d "$MODDIR" ]]; then echo "==> Trimming unnecessary kernel modules..." BEFORE=$(du -sb "$MODDIR" | awk '{print $1}') + # @TODO: this has to be templated, for flavors like fat or full we should keep most modules. # Module directory patterns to remove (relative to kernel/ inside the # modules tree). Each entry is passed to 'find -path'. EXCLUDE_PATTERNS=( @@ -85,6 +83,12 @@ if [[ -d "$MODDIR" ]]; then "*/kernel/virt" # Niche NIC driver "*/kernel/drivers/net/ethernet/netronome" + # Sound stuff + "*/kernel/sound" + # Media stuff + "*/kernel/drivers/media" + # Wireless net drivers + "*/kernel/drivers/net/wireless" ) for pattern in "${EXCLUDE_PATTERNS[@]}"; do @@ -105,11 +109,11 @@ if [[ -d "$MODDIR" ]]; then done fi -echo "==> Listing remaining kernel modules: ${BUILDROOT}" -du -h -x -d 5 "$MODDIR" | sort -h || true +echo "==> Listing 20 biggest remaining kernel modules du: ${MODDIR}" +du -h -x -d 5 "$MODDIR" | sort -h | tail -n20 || true echo "==> Removing /boot from the image to save space" -rm -rfv "${BUILDROOT}"/boot +rm -rf "${BUILDROOT:?}"/boot DTB_SRC_DIR=$(echo -n "${BUILDROOT}"/usr/lib/linux-image-*) if [[ -d "$DTB_SRC_DIR" ]]; then @@ -118,18 +122,14 @@ if [[ -d "$DTB_SRC_DIR" ]]; then # Move every .dtb* file in the DTB_SRC_DIR to "${BUILDROOT}/root/dtb" -- maintain the directory structure find "${DTB_SRC_DIR}" -type f -name "*.dtb*" -exec sh -c 'for f; do mkdir -p "${0}/$(dirname "${f#'"${DTB_SRC_DIR}"'/}")" && mv "$f" "${0}/${f#'"${DTB_SRC_DIR}"'/}" ; done' "${BUILDROOT}/root/dtb" {} + || true - echo "--> Leftover files in ${DTB_SRC_DIR}:" - find "${DTB_SRC_DIR}" -type f - # Now simply output the dtb directory to mkosi's OUTPUTDIR echo "==> Copying DTBs to mkosi output directory: ${OUTPUTDIR}/dtb" - mv -v "${BUILDROOT}/root/dtb" "${OUTPUTDIR}/dtb" + mv "${BUILDROOT}/root/dtb" "${OUTPUTDIR}/dtb" else echo "==> No DTB source directory found at ${DTB_SRC_DIR}; skipping DTB export" fi -# Show a 4-deep overview of the full rootfs contents -echo "==> Final contents of root filesystem (up to depth 4):" -du -h -x -d 4 "$BUILDROOT" | sort -h || true +echo "==> Final contents of root filesystem (up to depth 4, top 30):" +du -h -x -d 4 "$BUILDROOT" | sort -h | tail -n30 || true echo "==> CaptainOS finalize complete." diff --git a/mkosi.postinst b/mkosi.postinst index 6ce6f59..bafdb9b 100755 --- a/mkosi.postinst +++ b/mkosi.postinst @@ -5,6 +5,12 @@ # creating directories and dropping config files. set -euo pipefail +echo "==> CaptainOS post-install: dump env at $(hostname)..." +env + +echo "==> CaptainOS post-install: show mounts at $(hostname)..." +mount || true + echo "==> CaptainOS post-install: configuring services..." # Ensure CA certificate bundle is generated (dpkg triggers may not @@ -37,4 +43,7 @@ done # Set default target to multi-user (no graphical) systemctl set-default multi-user.target 2>/dev/null || true +echo "==> List installed packages..." +dpkg -l --no-pager || true + echo "==> CaptainOS post-install complete." From b980fffcc808836fd0aebb556adf0afd08ca6a92 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 20:49:02 +0200 Subject: [PATCH 19/93] gha: introduce FLAVOR_ID (nee KERNEL_VERSION) gha: fix Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3605828..3a77dfe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ permissions: packages: write env: - KERNEL_VERSION: 6.18.16 + DEFAULT_FLAVOR_ID: "trixie-full" jobs: # ------------------------------------------------------------------- @@ -93,13 +93,14 @@ jobs: fail-fast: false matrix: include: - - { arch: amd64, output_arch: x86_64, iso: true } - - { arch: arm64, output_arch: aarch64, iso: true } + - { arch: amd64, output_arch: x86_64, iso: true, FLAVOR_ID: "trixie-full" } + - { arch: arm64, output_arch: aarch64, iso: true, FLAVOR_ID: "trixie-full" } env: ARCH: ${{ matrix.arch }} KERNEL_MODE: skip MKOSI_MODE: docker ISO_MODE: docker + KERNEL_VERSION: ${{ matrix.FLAVOR_ID }} steps: - name: Checkout code uses: actions/checkout@v6 @@ -126,7 +127,7 @@ jobs: - name: Upload initramfs artifacts uses: actions/upload-artifact@v6 with: - name: initramfs-${{ matrix.arch }} + name: initramfs-${{ matrix.FLAVOR_ID }}-${{ matrix.arch }} path: out/ retention-days: 1 @@ -138,8 +139,8 @@ jobs: if: ${{ matrix.iso }} # only if matrix entry had iso: true uses: actions/upload-artifact@v6 with: - name: iso-${{ matrix.arch }} - path: out/captainos-${{ env.KERNEL_VERSION }}-${{ matrix.output_arch }}.iso + name: iso-${{ matrix.FLAVOR_ID }}-${{ matrix.arch }} + path: out/captainos-${{ matrix.FLAVOR_ID }}-${{ matrix.output_arch }}.iso retention-days: 1 # ------------------------------------------------------------------- @@ -182,25 +183,25 @@ jobs: - name: Download initramfs artifacts (amd64) uses: actions/download-artifact@v6 with: - name: initramfs-amd64 + name: initramfs-${{ env.DEFAULT_FLAVOR_ID }}-amd64 path: out - name: Download ISO artifact (amd64) uses: actions/download-artifact@v6 with: - name: iso-amd64 + name: iso-${{ env.DEFAULT_FLAVOR_ID }}-amd64 path: out - name: Download initramfs artifacts (arm64) uses: actions/download-artifact@v6 with: - name: initramfs-arm64 + name: initramfs-${{ env.DEFAULT_FLAVOR_ID }}-arm64 path: out - name: Download ISO artifact (arm64) uses: actions/download-artifact@v6 with: - name: iso-arm64 + name: iso-${{ env.DEFAULT_FLAVOR_ID }}-arm64 path: out - name: Install uv From 73d86b405da4a7a4b1ded9e74a89504b97b812c3 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 21:06:42 +0200 Subject: [PATCH 20/93] release: include cfg.kernel_version as suffix to all tags Signed-off-by: Ricardo Pardini --- captain/cli/_release.py | 1 + 1 file changed, 1 insertion(+) diff --git a/captain/cli/_release.py b/captain/cli/_release.py index 1511ab6..a7dc34a 100644 --- a/captain/cli/_release.py +++ b/captain/cli/_release.py @@ -195,6 +195,7 @@ def _cmd_release(cfg: Config, extra_args: list[str], args: object = None) -> Non exclude = getattr(args, "version_exclude", None) sha = _resolve_git_sha(args, cfg.project_dir) tag = oci.compute_version_tag(cfg.project_dir, sha, exclude=exclude) + tag = f"{tag}-{cfg.kernel_version}" if sub == "publish": target = getattr(args, "target", None) or cfg.arch From 90344373664c4dd71add0ac5a68462dda0bc732a Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 21:10:40 +0200 Subject: [PATCH 21/93] gha: bump external actions so GHA stops complaining about Node 24 Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a77dfe..3a7f9f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,7 +106,7 @@ jobs: uses: actions/checkout@v6 - name: Download tools artifacts - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v8 with: name: tools-${{ matrix.arch }} path: mkosi.output/tools/${{ matrix.arch }} @@ -149,7 +149,7 @@ jobs: - name: Log in to GHCR if: github.ref == 'refs/heads/main' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} @@ -181,25 +181,25 @@ jobs: run: cat .github/config.env >> "$GITHUB_ENV" - name: Download initramfs artifacts (amd64) - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v8 with: name: initramfs-${{ env.DEFAULT_FLAVOR_ID }}-amd64 path: out - name: Download ISO artifact (amd64) - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v8 with: name: iso-${{ env.DEFAULT_FLAVOR_ID }}-amd64 path: out - name: Download initramfs artifacts (arm64) - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v8 with: name: initramfs-${{ env.DEFAULT_FLAVOR_ID }}-arm64 path: out - name: Download ISO artifact (arm64) - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v8 with: name: iso-${{ env.DEFAULT_FLAVOR_ID }}-arm64 path: out @@ -208,7 +208,7 @@ jobs: uses: astral-sh/setup-uv@v7 - name: Log in to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} From 44431ecd1a2e1f1853cb8a8e2b74483cc6e17e0c Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 21:23:14 +0200 Subject: [PATCH 22/93] gha: pass DEFAULT_FLAVOR_ID as KERNEL_VERSION for publish-combined gha: pass DEFAULT_FLAVOR_ID as KERNEL_VERSION for publish-combined - retry Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 3 +++ captain/config.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a7f9f6..5ab165a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -166,6 +166,7 @@ jobs: # ------------------------------------------------------------------- publish-combined: if: github.ref == 'refs/heads/main' + name: "publish-combined" runs-on: ubuntu-latest needs: [ build-all ] env: @@ -215,4 +216,6 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Publish combined image to GHCR + env: + KERNEL_VERSION: "${{ env.DEFAULT_FLAVOR_ID }}" run: uv run ./build.py release publish diff --git a/captain/config.py b/captain/config.py index 523eaf2..4483138 100644 --- a/captain/config.py +++ b/captain/config.py @@ -13,7 +13,7 @@ log = logging.getLogger(__name__) -# Valid values for KERNEL_MODE and MKOSI_MODE. +# Valid values for ISO_MODE and MKOSI_MODE. VALID_MODES = ("docker", "native", "skip") # The single source of truth for the default kernel version. From 091995066dbf0287253b8ff60b7b1a7f36e3bdc2 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 22:00:37 +0200 Subject: [PATCH 23/93] captain/gha: KERNEL_VERSION (et al) is now FLAVOR_ID - `--flavor-id` Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 10 ++++------ captain/artifacts.py | 8 ++++---- captain/cli/__init__.py | 8 ++++---- captain/cli/_commands.py | 12 ++++++------ captain/cli/_main.py | 2 +- captain/cli/_parser.py | 28 ++++++++++++++-------------- captain/cli/_release.py | 12 ++++++------ captain/cli/_stages.py | 8 ++++---- captain/config.py | 16 ++++++++-------- captain/docker.py | 2 +- captain/iso.py | 2 +- captain/oci/_build.py | 14 +++++++------- captain/oci/_publish.py | 6 +++--- captain/qemu.py | 4 ++-- captain/util.py | 13 +------------ 15 files changed, 66 insertions(+), 79 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ab165a..5b28fee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,6 @@ jobs: arch: [ amd64, arm64 ] env: ARCH: ${{ matrix.arch }} - KERNEL_MODE: skip MKOSI_MODE: skip TOOLS_MODE: native steps: @@ -84,11 +83,11 @@ jobs: retention-days: 1 # ------------------------------------------------------------------- - # Build initramfs via mkosi (depends on kernel + tools) + # Build initramfs via mkosi (depends on tools) # ------------------------------------------------------------------- build-all: runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} - needs: [ download-tools ] # build-kernel + needs: [ download-tools ] strategy: fail-fast: false matrix: @@ -97,10 +96,9 @@ jobs: - { arch: arm64, output_arch: aarch64, iso: true, FLAVOR_ID: "trixie-full" } env: ARCH: ${{ matrix.arch }} - KERNEL_MODE: skip MKOSI_MODE: docker ISO_MODE: docker - KERNEL_VERSION: ${{ matrix.FLAVOR_ID }} + FLAVOR_ID: ${{ matrix.FLAVOR_ID }} steps: - name: Checkout code uses: actions/checkout@v6 @@ -217,5 +215,5 @@ jobs: - name: Publish combined image to GHCR env: - KERNEL_VERSION: "${{ env.DEFAULT_FLAVOR_ID }}" + FLAVOR_ID: "${{ env.DEFAULT_FLAVOR_ID }}" run: uv run ./build.py release publish diff --git a/captain/artifacts.py b/captain/artifacts.py index 3b919d3..82b07ab 100644 --- a/captain/artifacts.py +++ b/captain/artifacts.py @@ -38,7 +38,7 @@ def collect_kernel(cfg: Config) -> None: vmlinu_files = sorted(cfg.initramfs_output.glob("*.vmlinu*")) if vmlinu_files: vmlinuz_src = vmlinu_files[0] - vmlinuz_dst = out / f"vmlinuz-{cfg.kernel_version}-{cfg.arch_info.output_arch}" + vmlinuz_dst = out / f"vmlinuz-{cfg.flavor_id}-{cfg.arch_info.output_arch}" shutil.copy2(vmlinuz_src, vmlinuz_dst) log.info( "mkosi supplied kernel: %s (%s)", vmlinuz_dst, _human_size(vmlinuz_dst.stat().st_size) @@ -53,7 +53,7 @@ def collect_initramfs(cfg: Config) -> None: cpio_files = sorted(cfg.initramfs_output.glob("*.cpio*")) if cpio_files: initrd_src = cpio_files[0] - initrd_dst = out / f"initramfs-{cfg.kernel_version}-{cfg.arch_info.output_arch}" + initrd_dst = out / f"initramfs-{cfg.flavor_id}-{cfg.arch_info.output_arch}" shutil.copy2(initrd_src, initrd_dst) log.info("initramfs: %s (%s)", initrd_dst, _human_size(initrd_dst.stat().st_size)) else: @@ -67,7 +67,7 @@ def collect_dtbs(cfg): if dtb_dir.exists(): log.info("Found dtb directory in %s, copying to output...", dtb_dir) out = ensure_dir(cfg.output_dir) - target_dtb_dir = out / f"dtb-{cfg.kernel_version}-{cfg.arch_info.output_arch}" + target_dtb_dir = out / f"dtb-{cfg.flavor_id}-{cfg.arch_info.output_arch}" if target_dtb_dir.exists(): shutil.rmtree(target_dtb_dir) shutil.copytree(dtb_dir, target_dtb_dir) @@ -83,7 +83,7 @@ def collect_iso(cfg: Config) -> None: iso_files = sorted(iso_dir.glob("*.iso")) if iso_dir.is_dir() else [] if iso_files: iso_src = iso_files[0] - iso_dst = out / f"captainos-{cfg.kernel_version}-{cfg.arch_info.output_arch}.iso" + iso_dst = out / f"captainos-{cfg.flavor_id}-{cfg.arch_info.output_arch}.iso" shutil.copy2(iso_src, iso_dst) log.info("iso: %s (%s)", iso_dst, _human_size(iso_dst.stat().st_size)) diff --git a/captain/cli/__init__.py b/captain/cli/__init__.py index fa440c1..77481c1 100644 --- a/captain/cli/__init__.py +++ b/captain/cli/__init__.py @@ -5,12 +5,12 @@ CLI args > environment variables > defaults -The subcommand (``build``, ``kernel``, ``tools``, …) is extracted from +The subcommand (``build``, ``initramfs``, ``tools``, …) is extracted from ``sys.argv`` *before* parsing so that flags work in any position:: - ./build.py --arch=arm64 kernel # works - ./build.py kernel --arch=arm64 # also works - ARCH=arm64 ./build.py kernel # also works + ./build.py --arch=arm64 initramfs # works + ./build.py initramfs --arch=arm64 # also works + ARCH=arm64 ./build.py initramfs # also works """ from captain.cli._main import main diff --git a/captain/cli/_commands.py b/captain/cli/_commands.py index c131224..54e9df1 100644 --- a/captain/cli/_commands.py +++ b/captain/cli/_commands.py @@ -65,7 +65,7 @@ def _cmd_shell(cfg: Config, _extra_args: list[str]) -> None: def _cmd_clean(cfg: Config, _extra_args: list[str], args: object = None) -> None: - """Remove build artifacts for the selected kernel version, or all.""" + """Remove build artifacts for the selected flavor, or all.""" clean_all = getattr(args, "clean_all", False) if clean_all: @@ -75,8 +75,8 @@ def _cmd_clean(cfg: Config, _extra_args: list[str], args: object = None) -> None def _clean_version(cfg: Config) -> None: - """Remove build artifacts for a single kernel version.""" - kver = cfg.kernel_version + """Remove build artifacts for a single flavor.""" + kver = cfg.flavor_id log.info("Cleaning build artifacts for kernel %s (%s)...", kver, cfg.arch) mkosi_output = cfg.mkosi_output @@ -130,7 +130,7 @@ def _clean_version(cfg: Config) -> None: def _clean_all(cfg: Config) -> None: - """Remove all build artifacts (all kernel versions).""" + """Remove all build artifacts (all flavors).""" log.info("Cleaning ALL build artifacts...") mkosi_output = cfg.mkosi_output mkosi_cache = cfg.project_dir / "mkosi.cache" @@ -183,7 +183,7 @@ def _cmd_summary(cfg: Config, _extra_args: list[str]) -> None: case "docker": docker.build_builder(cfg) container_tree = f"/work/mkosi.output/tools/{cfg.arch}" - container_outdir = f"/work/mkosi.output/initramfs/{cfg.kernel_version}/{cfg.arch}" + container_outdir = f"/work/mkosi.output/initramfs/{cfg.flavor_id}/{cfg.arch}" docker.run_mkosi( cfg, f"--extra-tree={container_tree}", @@ -224,7 +224,7 @@ def _cmd_checksums(cfg: Config, _extra_args: list[str], args: object = None) -> # Default mode: produce checksums for the selected architecture. out = cfg.output_dir oarch = cfg.arch_info.output_arch - kver = cfg.kernel_version + kver = cfg.flavor_id arch_files = [ out / f"vmlinuz-{kver}-{oarch}", out / f"initramfs-{kver}-{oarch}", diff --git a/captain/cli/_main.py b/captain/cli/_main.py index b299d83..286c237 100644 --- a/captain/cli/_main.py +++ b/captain/cli/_main.py @@ -112,7 +112,7 @@ def main(project_dir: Path | None = None) -> None: case "docker": docker.build_builder(cfg) container_tree = f"/work/mkosi.output/tools/{cfg.arch}" - container_outdir = f"/work/mkosi.output/initramfs/{cfg.kernel_version}/{cfg.arch}" + container_outdir = f"/work/mkosi.output/initramfs/{cfg.flavor_id}/{cfg.arch}" docker.run_mkosi( cfg, f"--extra-tree={container_tree}", diff --git a/captain/cli/_parser.py b/captain/cli/_parser.py index 0b685de..46229c4 100644 --- a/captain/cli/_parser.py +++ b/captain/cli/_parser.py @@ -9,7 +9,7 @@ import configargparse -from captain.config import DEFAULT_KERNEL_VERSION +from captain.config import DEFAULT_FLAVOR_ID # --------------------------------------------------------------------------- # Known subcommands (order matters for help text) @@ -197,14 +197,14 @@ def _add_common_flags(parser: configargparse.ArgParser) -> None: ) -def _add_kernel_flags(parser: configargparse.ArgParser) -> None: - """--kernel-version""" +def _add_flavor_flags(parser: configargparse.ArgParser) -> None: + """--flavor-id""" g = parser.add_argument_group("kernel") g.add_argument( - "--kernel-version", - env_var="KERNEL_VERSION", + "--flavor-id", + env_var="FLAVOR_ID", metavar="VER", - default=DEFAULT_KERNEL_VERSION, + default=DEFAULT_FLAVOR_ID, help="kernel version to build", ) @@ -537,18 +537,18 @@ def _add_tink_flags(parser: configargparse.ArgParser) -> None: _COMMAND_FLAGS: dict[str, list[Callable[..., None]]] = { "build": [ _add_common_flags, - _add_kernel_flags, + _add_flavor_flags, _add_tools_flags, _add_mkosi_flags, _add_iso_flags, ], "tools": [_add_common_flags, _add_tools_flags], - "initramfs": [_add_common_flags, _add_kernel_flags, _add_mkosi_flags], - "iso": [_add_common_flags, _add_kernel_flags, _add_iso_flags], - "checksums": [_add_common_flags, _add_kernel_flags, _add_checksums_flags], - "release": [_add_common_flags, _add_kernel_flags, _add_release_flags], + "initramfs": [_add_common_flags, _add_flavor_flags, _add_mkosi_flags], + "iso": [_add_common_flags, _add_flavor_flags, _add_iso_flags], + "checksums": [_add_common_flags, _add_flavor_flags, _add_checksums_flags], + "release": [_add_common_flags, _add_flavor_flags, _add_release_flags], "shell": [_add_common_flags], - "clean": [_add_common_flags, _add_kernel_flags, _add_clean_flags], - "summary": [_add_common_flags, _add_kernel_flags, _add_summary_flags], - "qemu-test": [_add_common_flags, _add_kernel_flags, _add_qemu_flags, _add_tink_flags], + "clean": [_add_common_flags, _add_flavor_flags, _add_clean_flags], + "summary": [_add_common_flags, _add_flavor_flags, _add_summary_flags], + "qemu-test": [_add_common_flags, _add_flavor_flags, _add_qemu_flags, _add_tink_flags], } diff --git a/captain/cli/_release.py b/captain/cli/_release.py index a7dc34a..5b6fa49 100644 --- a/captain/cli/_release.py +++ b/captain/cli/_release.py @@ -15,7 +15,7 @@ from ._parser import ( _add_common_flags, - _add_kernel_flags, + _add_flavor_flags, _add_release_base_flags, _add_release_pull_output, _add_release_tag_version, @@ -30,13 +30,13 @@ _RELEASE_SUBCMD_INFO: dict[str, tuple[str, list]] = { "publish": ( "Publish artifacts as a multi-arch OCI image", - [_add_common_flags, _add_kernel_flags, _add_release_base_flags, _add_release_target_flag], + [_add_common_flags, _add_flavor_flags, _add_release_base_flags, _add_release_target_flag], ), "pull": ( "Pull and extract artifacts (amd64, arm64, or combined)", [ _add_common_flags, - _add_kernel_flags, + _add_flavor_flags, _add_release_base_flags, _add_release_target_flag, _add_release_pull_output, @@ -44,7 +44,7 @@ ), "tag": ( "Tag all artifact images with a version", - [_add_common_flags, _add_kernel_flags, _add_release_base_flags, _add_release_tag_version], + [_add_common_flags, _add_flavor_flags, _add_release_base_flags, _add_release_tag_version], ), } @@ -135,7 +135,7 @@ def _cmd_release(cfg: Config, extra_args: list[str], args: object = None) -> Non sha = _resolve_git_sha(args, cfg.project_dir) env_args: list[str] = [ "-e", - f"KERNEL_VERSION={cfg.kernel_version}", + f"FLAVOR_ID={cfg.flavor_id}", "-e", f"REGISTRY={registry}", "-e", @@ -195,7 +195,7 @@ def _cmd_release(cfg: Config, extra_args: list[str], args: object = None) -> Non exclude = getattr(args, "version_exclude", None) sha = _resolve_git_sha(args, cfg.project_dir) tag = oci.compute_version_tag(cfg.project_dir, sha, exclude=exclude) - tag = f"{tag}-{cfg.kernel_version}" + tag = f"{tag}-{cfg.flavor_id}" if sub == "publish": target = getattr(args, "target", None) or cfg.arch diff --git a/captain/cli/_stages.py b/captain/cli/_stages.py index b29726c..05b6c43 100644 --- a/captain/cli/_stages.py +++ b/captain/cli/_stages.py @@ -1,4 +1,4 @@ -"""Build stage orchestration — kernel, tools, mkosi, ISO.""" +"""Build stage orchestration — tools, mkosi, ISO.""" from __future__ import annotations @@ -78,7 +78,7 @@ def _build_mkosi_stage(cfg: Config, extra_args: list[str]) -> None: docker.build_builder(cfg) log.info("Building initrd with mkosi (docker)...") tools_tree = f"/work/mkosi.output/tools/{cfg.arch}" - output_dir = f"/work/mkosi.output/initramfs/{cfg.kernel_version}/{cfg.arch}" + output_dir = f"/work/mkosi.output/initramfs/{cfg.flavor_id}/{cfg.arch}" docker.run_mkosi( cfg, f"--extra-tree={tools_tree}", @@ -90,7 +90,7 @@ def _build_mkosi_stage(cfg: Config, extra_args: list[str]) -> None: docker.fix_docker_ownership( cfg, [ - f"/work/mkosi.output/initramfs/{cfg.kernel_version}/{cfg.arch}", + f"/work/mkosi.output/initramfs/{cfg.flavor_id}/{cfg.arch}", "/work/out", ], ) @@ -105,7 +105,7 @@ def _build_iso_stage(cfg: Config) -> None: return # --- idempotency -------------------------------------------------- - iso_path = cfg.iso_output / f"captainos-{cfg.kernel_version}-{cfg.arch_info.output_arch}.iso" + iso_path = cfg.iso_output / f"captainos-{cfg.flavor_id}-{cfg.arch_info.output_arch}.iso" if iso_path.is_file() and not cfg.force_iso: log.info("ISO already built: %s (use --force-iso to rebuild)", iso_path) return diff --git a/captain/config.py b/captain/config.py index 4483138..c87a2f3 100644 --- a/captain/config.py +++ b/captain/config.py @@ -16,9 +16,9 @@ # Valid values for ISO_MODE and MKOSI_MODE. VALID_MODES = ("docker", "native", "skip") -# The single source of truth for the default kernel version. -# Override at runtime via --kernel-version or KERNEL_VERSION env var. -DEFAULT_KERNEL_VERSION = "6.18.16" +# The single source of truth for the default flavor. +# Override at runtime via --flavor-id or FLAVOR_ID env var. +DEFAULT_FLAVOR_ID = "6.18.16" @dataclass(slots=True) @@ -31,7 +31,7 @@ class Config: # Target arch: str = "amd64" - kernel_version: str = DEFAULT_KERNEL_VERSION + flavor_id: str = DEFAULT_FLAVOR_ID # Docker builder_image: str = "captainos-builder" @@ -100,7 +100,7 @@ def from_args(cls, args: argparse.Namespace, project_dir: Path | None) -> Config project_dir=project_dir, output_dir=project_dir / "out", arch=getattr(args, "arch", "amd64"), - kernel_version=getattr(args, "kernel_version", DEFAULT_KERNEL_VERSION), + flavor_id=getattr(args, "flavor_id", DEFAULT_FLAVOR_ID), builder_image=getattr(args, "builder_image", "captainos-builder"), no_cache=getattr(args, "no_cache", False), tools_mode=getattr(args, "tools_mode", "docker"), @@ -126,7 +126,7 @@ def from_env(cls, project_dir: Path) -> Config: project_dir=project_dir, output_dir=project_dir / "out", arch=os.environ.get("ARCH", "amd64"), - kernel_version=os.environ.get("KERNEL_VERSION", DEFAULT_KERNEL_VERSION), + flavor_id=os.environ.get("FLAVOR_ID", DEFAULT_FLAVOR_ID), builder_image=os.environ.get("BUILDER_IMAGE", "captainos-builder"), no_cache=os.environ.get("NO_CACHE") == "1", tools_mode=os.environ.get("TOOLS_MODE", "docker"), @@ -157,12 +157,12 @@ def mkosi_output(self) -> Path: @property def initramfs_output(self) -> Path: """Per-version, per-arch directory for mkosi initramfs output.""" - return self.project_dir / "mkosi.output" / "initramfs" / self.kernel_version / self.arch + return self.project_dir / "mkosi.output" / "initramfs" / self.flavor_id / self.arch @property def iso_output(self) -> Path: """Per-version, per-arch directory for the built ISO image.""" - return self.project_dir / "mkosi.output" / "iso" / self.kernel_version / self.arch + return self.project_dir / "mkosi.output" / "iso" / self.flavor_id / self.arch @property def iso_staging(self) -> Path: diff --git a/captain/docker.py b/captain/docker.py index c56cdc3..3c09347 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -163,7 +163,7 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: "-e", f"ARCH={cfg.arch}", "-e", - f"KERNEL_VERSION={cfg.kernel_version}", + f"FLAVOR_ID={cfg.flavor_id}", "-e", f"FORCE_TOOLS={int(cfg.force_tools)}", "-e", diff --git a/captain/iso.py b/captain/iso.py index 8640e5c..fb57777 100644 --- a/captain/iso.py +++ b/captain/iso.py @@ -96,7 +96,7 @@ def build(cfg: Config) -> None: (grub_dir / "grub.cfg").write_text(_grub_cfg(cfg.arch)) iso_dir = ensure_dir(cfg.iso_output) - iso_path = iso_dir / f"captainos-{cfg.kernel_version}-{cfg.arch_info.output_arch}.iso" + iso_path = iso_dir / f"captainos-{cfg.flavor_id}-{cfg.arch_info.output_arch}.iso" log.info("Building ISO with grub-mkrescue (%s)...", grub_platform) grub_mkrescue = shutil.which("grub-mkrescue") diff --git a/captain/oci/_build.py b/captain/oci/_build.py index 23776ee..0fc2833 100644 --- a/captain/oci/_build.py +++ b/captain/oci/_build.py @@ -39,7 +39,7 @@ def _collect_arch_artifacts( project_dir: Path, out: Path, arch: str, - kernel_version: str, + flavor_id: str, ) -> list[Path]: """Collect and return the artifact files for a single architecture. @@ -48,10 +48,10 @@ def _collect_arch_artifacts( oarch = get_arch_info(arch).output_arch ## # Collect kernel @TODO probably fix or remove? - ## vmlinuz_dir = project_dir / "mkosi.output" / "kernel" / kernel_version / arch + ## vmlinuz_dir = project_dir / "mkosi.output" / "kernel" / flavor_id / arch ## vmlinuz_files = sorted(vmlinuz_dir.glob("vmlinuz-*")) if vmlinuz_dir.is_dir() else [] ## - ## vmlinuz_dst = out / f"vmlinuz-{kernel_version}-{oarch}" + ## vmlinuz_dst = out / f"vmlinuz-{flavor_id}-{oarch}" ## if vmlinuz_files: ## shutil.copy2(vmlinuz_files[0], vmlinuz_dst) ## log.info("kernel: %s", vmlinuz_dst) @@ -59,11 +59,11 @@ def _collect_arch_artifacts( ## log.warning("No kernel image found for %s", arch) arch_files = [ - out / f"vmlinuz-{kernel_version}-{oarch}", - out / f"initramfs-{kernel_version}-{oarch}", - out / f"captainos-{kernel_version}-{oarch}.iso", + out / f"vmlinuz-{flavor_id}-{oarch}", + out / f"initramfs-{flavor_id}-{oarch}", + out / f"captainos-{flavor_id}-{oarch}.iso", ] - checksums_path = out / f"sha256sums-{kernel_version}-{oarch}.txt" + checksums_path = out / f"sha256sums-{flavor_id}-{oarch}.txt" artifacts.collect_checksums(arch_files, checksums_path) push_files = [*arch_files, checksums_path] diff --git a/captain/oci/_publish.py b/captain/oci/_publish.py index b67ced6..e757eda 100644 --- a/captain/oci/_publish.py +++ b/captain/oci/_publish.py @@ -194,7 +194,7 @@ def publish( cfg.project_dir, out, arch, - cfg.kernel_version, + cfg.flavor_id, ) # Create deterministic layer tars (shared across manifest pushes). @@ -204,13 +204,13 @@ def publish( arch_layer_tars[arch] = [_deterministic_tar(f, out) for f in files] # A single layer for all DTBs, if any; those are highly compressible together. - dtb_dir_in = out / f"dtb-{cfg.kernel_version}-{get_arch_info(arch).output_arch}" + dtb_dir_in = out / f"dtb-{cfg.flavor_id}-{get_arch_info(arch).output_arch}" if not dtb_dir_in.is_dir(): log.warning("No dtbs directory found for %s: %s", arch, dtb_dir_in) else: log.info(f"Found DTB directory for {arch}: {dtb_dir_in}") all_dtb_files: list[Path] = sorted(dtb_dir_in.glob("**/*.dtb*")) - dtb_tar_path = out / f"dtbs-{cfg.kernel_version}-{arch}.tar" + dtb_tar_path = out / f"dtbs-{cfg.flavor_id}-{arch}.tar" with tarfile.open(dtb_tar_path, "w") as tar: for f in all_dtb_files: tar.add(f, arcname=f.relative_to(out)) diff --git a/captain/qemu.py b/captain/qemu.py index bbbd8d5..ae0da4a 100644 --- a/captain/qemu.py +++ b/captain/qemu.py @@ -65,8 +65,8 @@ def run_qemu(cfg: Config, args: argparse.Namespace | None = None) -> None: :mod:`configargparse`. When provided, Tinkerbell kernel cmdline parameters are drawn from it instead of the environment. """ - kernel = cfg.output_dir / f"vmlinuz-{cfg.kernel_version}-{cfg.arch_info.output_arch}" - initrd = cfg.output_dir / f"initramfs-{cfg.kernel_version}-{cfg.arch_info.output_arch}" + kernel = cfg.output_dir / f"vmlinuz-{cfg.flavor_id}-{cfg.arch_info.output_arch}" + initrd = cfg.output_dir / f"initramfs-{cfg.flavor_id}-{cfg.arch_info.output_arch}" missing: list[str] = [] if not kernel.is_file(): diff --git a/captain/util.py b/captain/util.py index 797f8fa..61736fa 100644 --- a/captain/util.py +++ b/captain/util.py @@ -142,17 +142,6 @@ def _missing(cmds: list[str]) -> list[str]: return [cmd for cmd in cmds if _shutil.which(cmd) is None] -def check_kernel_dependencies(arch: str) -> list[str]: - """Check host tools required for a native kernel build. - - Returns a list of missing command names (empty if all found). - """ - required = ["make", "gcc", "flex", "bison", "bc", "rsync", "strip", "zstd", "depmod"] - if arch in ("arm64", "aarch64"): - required += ["aarch64-linux-gnu-gcc", "aarch64-linux-gnu-strip"] - return _missing(required) - - def check_mkosi_dependencies() -> list[str]: """Check host tools required for a native mkosi image build. @@ -183,4 +172,4 @@ def check_dependencies(arch: str) -> list[str]: Returns a list of missing command names (empty if all found). """ - return check_kernel_dependencies(arch) + check_mkosi_dependencies() + return check_mkosi_dependencies() From bd9799ecc445aa705625687a13513f6e341c6c96 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 22:02:35 +0200 Subject: [PATCH 24/93] captain: cleanups / doc updates WiP (drop kernel build-related) Signed-off-by: Ricardo Pardini --- .gitignore | 1 - README.md | 5 +- captain/oci/_build.py | 11 --- docs/kernel-build.md | 177 +----------------------------------------- mkosi.conf | 6 -- 5 files changed, 3 insertions(+), 197 deletions(-) diff --git a/.gitignore b/.gitignore index 6781065..b09d454 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ mkosi.cache/ mkosi.tools/ mkosi.tools.manifest mkosi.builddir/ -kernel.configs/.config.resolved.* out/ # Editor diff --git a/README.md b/README.md index 00e2a5b..799ab84 100644 --- a/README.md +++ b/README.md @@ -55,10 +55,7 @@ build configuration: --no-cache rebuild builder image without Docker cache kernel: - --kernel-version VER kernel version to build (default: 6.18.16) - --kernel-src PATH path to local kernel source tree - --kernel-mode {docker,native,skip} kernel stage execution mode (default: docker) - --force-kernel force kernel rebuild even if outputs exist + --flavor-id VER kernel version to build (default: 6.18.16) tools: --tools-mode {docker,native,skip} tools stage execution mode (default: docker) diff --git a/captain/oci/_build.py b/captain/oci/_build.py index 0fc2833..0f39b69 100644 --- a/captain/oci/_build.py +++ b/captain/oci/_build.py @@ -47,17 +47,6 @@ def _collect_arch_artifacts( """ oarch = get_arch_info(arch).output_arch - ## # Collect kernel @TODO probably fix or remove? - ## vmlinuz_dir = project_dir / "mkosi.output" / "kernel" / flavor_id / arch - ## vmlinuz_files = sorted(vmlinuz_dir.glob("vmlinuz-*")) if vmlinuz_dir.is_dir() else [] - ## - ## vmlinuz_dst = out / f"vmlinuz-{flavor_id}-{oarch}" - ## if vmlinuz_files: - ## shutil.copy2(vmlinuz_files[0], vmlinuz_dst) - ## log.info("kernel: %s", vmlinuz_dst) - ## else: - ## log.warning("No kernel image found for %s", arch) - arch_files = [ out / f"vmlinuz-{flavor_id}-{oarch}", out / f"initramfs-{flavor_id}-{oarch}", diff --git a/docs/kernel-build.md b/docs/kernel-build.md index 0130ef3..0f5fb21 100644 --- a/docs/kernel-build.md +++ b/docs/kernel-build.md @@ -1,178 +1,5 @@ # Kernel Build Process -CaptainOS builds a custom Linux kernel from upstream source with -project-specific defconfigs. The build is orchestrated by the `captain.kernel` -module and driven through the CLI (`./build.py kernel`). +CaptainOS uses standard or custom Debian-packaged kernels. -## Quick Start - -```bash -# Build the kernel (defaults to Docker mode, amd64, kernel 6.18.16) -./build.py kernel - -# Build for arm64 -./build.py kernel --arch=arm64 - -# Use a local kernel source tree instead of downloading -./build.py kernel --kernel-src=/path/to/linux - -# Force a rebuild even if outputs already exist -./build.py kernel --force-kernel - -# Build natively (no Docker) -./build.py kernel --kernel-mode=native -``` - -Every flag also has an environment variable form (e.g. `ARCH`, `KERNEL_VERSION`, -`KERNEL_MODE`, `KERNEL_SRC`, `FORCE_KERNEL`). - -## Execution Modes - -The `--kernel-mode` flag controls how the kernel is built: - -| Mode | Description | -|----------|------------------------------------------------------------------------| -| `docker` | (default) Builds inside the `captainos-builder` Docker container. | -| `native` | Builds directly on the host; requires kernel build tools to be installed. | -| `skip` | Skips the kernel stage entirely. | - -In Docker mode, the builder container is based on Debian Trixie and includes -all kernel build dependencies (gcc, make, flex, bison, bc, libelf, libssl, -dwarves/pahole, etc.) plus cross-compilation toolchains for arm64. Inside -the container, the mode is forced to `native` so Docker is never invoked -recursively. - -## Build Pipeline - -The `kernel.build()` function runs four stages sequentially: - -### 1. Download - -`kernel.download_kernel()` fetches the upstream tarball from -`cdn.kernel.org`: - -``` -https://cdn.kernel.org/pub/linux/kernel/v{major}.x/linux-{version}.tar.xz -``` - -The tarball is extracted into `/var/tmp/kernel-build/linux-{version}/`. -If the extracted source directory already exists, it is reused. - -When `--kernel-src` is set, this step is skipped and the local source tree -is used as-is. - -### 2. Configure - -`kernel.configure_kernel()` applies the project defconfig for the target -architecture: - -1. Copies `kernel.configs/{major}.{minor}.y.{arch}` into the source tree as `.config`. -2. Runs `make olddefconfig` to resolve any new symbols against defaults. -3. Saves the fully resolved config to `kernel.configs/.config.resolved.{branch}.{arch}` for - debugging. - -If no defconfig file exists for the target kernel version and arch, the build -exits with an error listing the available kernel branches. - -For `x86_64` builds, the `COMMAND_LINE_SIZE` is patched from 2048 to 4096 -in `arch/x86/include/asm/setup.h` because Tinkerbell passes large kernel -command lines. - -Cross-compilation is set up automatically via the `ARCH` and -`CROSS_COMPILE` environment variables (e.g. `CROSS_COMPILE=aarch64-linux-gnu-` -for arm64). - -### 3. Compile - -`kernel.build_kernel()` runs the parallel make: - -```bash -make -j$(nproc) {image_target} modules -``` - -The image target is architecture-dependent: - -| Architecture | `ARCH` | Image target | Output path | -|-------------|-----------|-------------|-------------------------------| -| amd64 | `x86_64` | `bzImage` | `arch/x86/boot/bzImage` | -| arm64 | `arm64` | `Image` | `arch/arm64/boot/Image` | - -After compilation, `make -s kernelrelease` is invoked to determine the -exact built kernel version string (e.g. `6.18.16-captainos`). - -### 4. Install - -`kernel.install_kernel()` places the built artifacts into the output tree: - -1. **Module installation** — `make INSTALL_MOD_PATH=... modules_install` - installs modules to `mkosi.output/kernel/{version}/{arch}/modules/`. -2. **Strip** — Debug symbols are stripped from every `.ko` file with - `strip --strip-unneeded`. -3. **Compress** — Modules are compressed with `zstd --rm -q -19` producing - `.ko.zst` files. The defconfig enables `CONFIG_MODULE_COMPRESS_ZSTD` - and `CONFIG_MODULE_DECOMPRESS` so the kernel loads compressed modules at - runtime. -4. **Clean up** — The `build` and `source` symlinks are removed from the - modules directory. -5. **Merged-usr layout** — Modules are relocated from `lib/modules/` to - `usr/lib/modules/` to follow the merged-usr filesystem convention. -6. **depmod** — `depmod -a` regenerates module dependency metadata for - the compressed `.ko.zst` files. -7. **Kernel image** — The kernel image (`vmlinuz-{version}`) is copied to - `mkosi.output/kernel/{kernel_version}/{arch}/`. It is kept separate from - the extra-tree so it is **not** included in the initramfs CPIO — iPXE loads - the kernel independently. - -## Output Layout - -After a successful build, the kernel stage produces: - -``` -mkosi.output/ -├── tools/{arch}/ # tools only (containerd, runc, etc.) -│ ├── usr/local/bin/ -│ └── opt/cni/bin/ -└── kernel/{kernel_version}/{arch}/ - ├── vmlinuz-{version} # kernel image (bzImage or Image) - └── modules/ # passed as --extra-tree to mkosi - └── usr/lib/modules/{version}/ - ├── kernel/... # compressed .ko.zst module files - ├── modules.dep - ├── modules.dep.bin - └── ... -``` - -The tools and modules subtrees are both passed to mkosi via -separate `--extra-tree=` flags and merged into the initramfs. The vmlinuz -image is collected into `out/` by `artifacts.collect_kernel()` as -`out/vmlinuz-{kernel_version}-{arch}`. - -## Idempotency - -The CLI checks for existing outputs before starting: - -- If both `mkosi.output/kernel/{kernel_version}/{arch}/modules/usr/lib/modules/` and - `mkosi.output/kernel/{kernel_version}/{arch}/vmlinuz-*` exist, the build is skipped. -- Use `--force-kernel` (or `FORCE_KERNEL=1`) to force a rebuild. -- If modules exist but the vmlinuz is missing, the kernel is rebuilt - automatically. - -## Defconfigs - -Architecture-specific defconfigs live in the `kernel.configs/` directory, -named by stable branch: `{major}.{minor}.y.{arch}`. This allows -multiple kernel versions to coexist — each stable branch (e.g. 6.18.y, -6.19.y) has its own config per architecture. - -- `kernel.configs/6.18.y.amd64` — x86_64 config adapted for kernel 6.18. -- `kernel.configs/6.18.y.arm64` — arm64 config adapted for kernel 6.18. -- `kernel.configs/6.19.y.amd64` — x86_64 config adapted for kernel 6.19. -- `kernel.configs/6.19.y.arm64` — arm64 config adapted for kernel 6.19. - -The default kernel version is defined as `DEFAULT_KERNEL_VERSION` in -`captain/config.py` and can be overridden via `--kernel-version` or -the `KERNEL_VERSION` environment variable. - -Both configs include support for bare-metal provisioning, container -runtimes (cgroups v2, namespaces, overlayfs), and broad hardware/network -driver coverage. The local version suffix is set to `-captainos`. +The default flavor, `trixie-full`, uses `linux-image-generic` default Debian kernel. diff --git a/mkosi.conf b/mkosi.conf index fceb838..50d24b3 100644 --- a/mkosi.conf +++ b/mkosi.conf @@ -26,12 +26,6 @@ WithDocs=no # rootfs → tmpfs via switch_root before exec'ing systemd. This makes # pivot_root(2) work for container runtimes (runc). -# Pre-built kernel modules and tools are injected via separate --extra-tree= -# flags on the CLI: -# --extra-tree=mkosi.output/tools/{arch}/ (tools) -# This keeps mkosi.conf architecture-neutral so both amd64 and arm64 builds -# can coexist under mkosi.output/. - Packages= linux-image-generic tiny-initramfs # a tiny initrd builder to account for the linux-image dependency From ddb17eab20e199a25315523675ce42d271b014b8 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Fri, 3 Apr 2026 22:27:50 +0200 Subject: [PATCH 25/93] captain: DEFAULT_FLAVOR_ID = "trixie-full" Signed-off-by: Ricardo Pardini --- captain/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/captain/config.py b/captain/config.py index c87a2f3..63781cd 100644 --- a/captain/config.py +++ b/captain/config.py @@ -18,7 +18,7 @@ # The single source of truth for the default flavor. # Override at runtime via --flavor-id or FLAVOR_ID env var. -DEFAULT_FLAVOR_ID = "6.18.16" +DEFAULT_FLAVOR_ID = "trixie-full" @dataclass(slots=True) From 048fc874e0a90f7f8c62e8cb88d159e9b19d183a Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Sat, 4 Apr 2026 23:37:50 +0200 Subject: [PATCH 26/93] flavors: introduce flavors but logging sucks and no dataclass better, logging still sucks for Full -> Common much better in-package this sucks less cosmetics some static and template rendering and hardcoded cleanup kinda-works kinda works Signed-off-by: Ricardo Pardini --- .gitignore | 11 ++ captain/cli/_main.py | 13 +- captain/config.py | 7 + captain/docker.py | 1 + captain/flavor.py | 166 ++++++++++++++++++ captain/flavors/common_armbian/__init__.py | 36 ++++ .../etc/apt/sources.list.d/armbian-next.list | 2 + .../usr/share/keyrings/armbian-next.gpg | Bin 0 -> 1754 bytes captain/flavors/common_debian/__init__.py | 39 ++++ .../flavors/common_debian/mkosi.conf.j2 | 3 +- .../mkosi.extra}/etc/acpi/events/powerbtn | 0 .../mkosi.extra}/etc/acpi/powerbtn.sh | 0 .../etc/cni/net.d/10-bridge.conflist | 0 .../mkosi.extra}/etc/containerd/config.toml | 0 .../mkosi.extra}/etc/locale.conf | 0 .../common_debian/mkosi.extra}/etc/machine-id | 0 .../mkosi.extra}/etc/modules-load.d/ipmi.conf | 0 .../common_debian/mkosi.extra}/etc/motd | 0 .../mkosi.extra}/etc/nerdctl/nerdctl.toml | 0 .../common_debian/mkosi.extra}/etc/os-release | 0 .../mkosi.extra}/etc/rsyslog.conf | 0 .../etc/sysctl.d/99-ip-forward.conf | 0 .../etc/sysctl.d/99-quiet-audit.conf | 0 .../etc/systemd/network/80-dhcp.network | 0 .../etc/systemd/resolved.conf.d/no-stub.conf | 0 .../systemd/system/captainos-banner.service | 0 .../system/captainos-static-network.service | 0 .../etc/systemd/system/containerd.service | 0 .../etc/systemd/system/default.target | 0 .../etc/systemd/system/initrd-cleanup.service | 0 .../systemd/system/initrd-parse-etc.service | 0 .../systemd/system/initrd-switch-root.service | 0 .../systemd/system/initrd-switch-root.target | 0 .../system/initrd-udevadm-cleanup-db.service | 0 .../etc/systemd/system/initrd.target | 0 .../system/rsyslog-hostname-reload.path | 0 .../system/rsyslog-hostname-reload.service | 0 .../etc/systemd/system/rsyslog.service | 0 .../serial-getty@.service.d/autologin.conf | 0 .../systemd/system/systemd-firstboot.service | 0 .../systemd/system/tink-agent-setup.service | 0 .../etc/systemd/system/tink-agent.service | 0 .../mkosi.extra}/etc/systemd/timesyncd.conf | 0 .../common_debian/mkosi.extra}/etc/timezone | 0 .../flavors/common_debian/mkosi.extra}/init | 0 .../common_debian/mkosi.extra}/root/.bashrc | 0 .../common_debian/mkosi.extra}/root/.profile | 0 .../usr/local/bin/captainos-banner | 0 .../usr/local/bin/captainos-static-network | 0 .../mkosi.extra}/usr/local/bin/rsyslog-start | 0 .../usr/local/bin/tink-agent-setup | 0 .../usr/local/bin/tink-agent-start | 0 .../common_debian/mkosi.finalize.sh.j2 | 0 .../common_debian/mkosi.postinst.sh.j2 | 0 captain/flavors/trixie_full/__init__.py | 32 ++++ captain/flavors/trixie_rockchip64/__init__.py | 32 ++++ pyproject.toml | 8 +- 57 files changed, 346 insertions(+), 4 deletions(-) create mode 100644 captain/flavor.py create mode 100644 captain/flavors/common_armbian/__init__.py create mode 100644 captain/flavors/common_armbian/mkosi.sandbox/etc/apt/sources.list.d/armbian-next.list create mode 100644 captain/flavors/common_armbian/mkosi.sandbox/usr/share/keyrings/armbian-next.gpg create mode 100644 captain/flavors/common_debian/__init__.py rename mkosi.conf => captain/flavors/common_debian/mkosi.conf.j2 (95%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/acpi/events/powerbtn (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/acpi/powerbtn.sh (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/cni/net.d/10-bridge.conflist (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/containerd/config.toml (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/locale.conf (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/machine-id (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/modules-load.d/ipmi.conf (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/motd (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/nerdctl/nerdctl.toml (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/os-release (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/rsyslog.conf (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/sysctl.d/99-ip-forward.conf (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/sysctl.d/99-quiet-audit.conf (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/network/80-dhcp.network (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/resolved.conf.d/no-stub.conf (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/captainos-banner.service (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/captainos-static-network.service (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/containerd.service (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/default.target (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/initrd-cleanup.service (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/initrd-parse-etc.service (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/initrd-switch-root.service (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/initrd-switch-root.target (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/initrd-udevadm-cleanup-db.service (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/initrd.target (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/rsyslog-hostname-reload.path (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/rsyslog-hostname-reload.service (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/rsyslog.service (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/serial-getty@.service.d/autologin.conf (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/systemd-firstboot.service (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/tink-agent-setup.service (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/system/tink-agent.service (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/systemd/timesyncd.conf (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/etc/timezone (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/init (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/root/.bashrc (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/root/.profile (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/usr/local/bin/captainos-banner (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/usr/local/bin/captainos-static-network (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/usr/local/bin/rsyslog-start (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/usr/local/bin/tink-agent-setup (100%) rename {mkosi.extra => captain/flavors/common_debian/mkosi.extra}/usr/local/bin/tink-agent-start (100%) rename mkosi.finalize => captain/flavors/common_debian/mkosi.finalize.sh.j2 (100%) rename mkosi.postinst => captain/flavors/common_debian/mkosi.postinst.sh.j2 (100%) create mode 100644 captain/flavors/trixie_full/__init__.py create mode 100644 captain/flavors/trixie_rockchip64/__init__.py diff --git a/.gitignore b/.gitignore index b09d454..9af6934 100644 --- a/.gitignore +++ b/.gitignore @@ -6,11 +6,19 @@ mkosi.tools.manifest mkosi.builddir/ out/ +# Generated artifacts +/mkosi.conf +/mkosi.finalize +/mkosi.postinst +/mkosi.extra +/mkosi.sandbox + # Editor *.swp *.swo *~ .vscode/ +.idea/ # OS .DS_Store @@ -28,3 +36,6 @@ dist/ .mypy_cache/ .pyright/ .pytest_cache/ + +# Ignore uv.lock for now; we should always use the latest version of packages etc +uv.lock diff --git a/captain/cli/_main.py b/captain/cli/_main.py index 286c237..f21a3ad 100644 --- a/captain/cli/_main.py +++ b/captain/cli/_main.py @@ -19,10 +19,12 @@ import sys from pathlib import Path +import captain.flavor from captain import docker from captain.config import Config from captain.util import run +from ..flavor import BaseFlavor from ._commands import ( _cmd_build, _cmd_checksums, @@ -82,7 +84,10 @@ def main(project_dir: Path | None = None) -> None: cfg = Config.from_args(args, project_dir) cfg.mkosi_args = mkosi_args - # 7. Dispatch. + # 7. Instantiate the flavor (from cfg.flavor_id) + flavor: BaseFlavor = captain.flavor.create_and_setup_flavor_for_id(cfg.flavor_id, cfg) + + # 8. Dispatch. dispatch: dict[str, object] = { "build": _cmd_build, "tools": _cmd_tools, @@ -96,8 +101,14 @@ def main(project_dir: Path | None = None) -> None: "qemu-test": _cmd_qemu_test, } + log.debug("Dispatching command '%s', extra args: %s", command, extra) handler = dispatch.get(command) if handler is not None: + # If building, or doing initramfs (mkosi), + # generate the flavor first so the files are in place. + if command in ("build", "initramfs"): + flavor.generate() + if command in ("qemu-test", "checksums", "release", "clean"): handler(cfg, extra, args=args) # type: ignore[operator] else: diff --git a/captain/config.py b/captain/config.py index 63781cd..1baeff4 100644 --- a/captain/config.py +++ b/captain/config.py @@ -96,6 +96,10 @@ def from_args(cls, args: argparse.Namespace, project_dir: Path | None) -> Config if project_dir is None: raise ValueError("project_dir must be provided to Config.from_args") + log.debug( + "Creating Config from args: %s (env flavor: %s)", args, os.environ.get("FLAVOR_ID") + ) + return cls( project_dir=project_dir, output_dir=project_dir / "out", @@ -122,6 +126,9 @@ def from_env(cls, project_dir: Path) -> Config: for any non-CLI callers (e.g. tests, scripts) that need a ``Config`` without going through argparse. """ + + log.debug("Creating Config from env: %s", os.environ) + return cls( project_dir=project_dir, output_dir=project_dir / "out", diff --git a/captain/docker.py b/captain/docker.py index 3c09347..496a0c9 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -188,6 +188,7 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: docker_args += ["-v", f"{cfg.project_dir}/mkosi.output:/work/mkosi.output"] docker_args += ["-v", f"{cfg.project_dir}/mkosi.extra:/work/mkosi.extra"] + docker_args += ["-v", f"{cfg.project_dir}/mkosi.sandbox:/work/mkosi.sandbox"] docker_args += ["-v", f"{cfg.project_dir}/out:/work/out"] docker_args += ["-v", f"{cfg.project_dir}/mkosi.conf:/work/mkosi.conf"] diff --git a/captain/flavor.py b/captain/flavor.py new file mode 100644 index 0000000..e56e472 --- /dev/null +++ b/captain/flavor.py @@ -0,0 +1,166 @@ +"""Flavor-specific configuration.""" + +from __future__ import annotations + +import logging +import shutil +from pathlib import Path +from typing import Protocol, runtime_checkable + +import jinja2 + +from captain.config import Config + +log = logging.getLogger(__name__) + + +@runtime_checkable +class BaseFlavor(Protocol): + cfg: Config + id: str + name: str + description: str + flavor_dir: Path + supported_architectures: frozenset[str] + template_map: dict[str, list[Path]] + static_map: dict[str, Path] + + def setup(self, cfg: Config, flavor_dir: Path) -> None: + if cfg is None: + raise ValueError("cfg (Config) cannot be None") + self.cfg = cfg + + if flavor_dir is None: + raise ValueError("flavor_dir (Path) cannot be None") + if not flavor_dir.is_dir(): + raise ValueError(f"flavor_dir {flavor_dir} does not exist or is not a directory") + self.flavor_dir = flavor_dir + + self.template_map = {} + self.static_map = {} + log.debug("Called BaseFlavor.setup()...") + pass + + def generate(self): + log.debug("Called BaseFlavor.generate()...") + # Before generating, cleanup known targets. @TODO make dir disposable instead + log.debug("Cleaning up old generated files in %s", self.cfg.project_dir) + shutil.rmtree(self.cfg.project_dir / "mkosi.conf", ignore_errors=True) + shutil.rmtree(self.cfg.project_dir / "mkosi.postinst", ignore_errors=True) + shutil.rmtree(self.cfg.project_dir / "mkosi.finalize", ignore_errors=True) + shutil.rmtree(self.cfg.project_dir / "mkosi.extra", ignore_errors=True) + shutil.rmtree(self.cfg.project_dir / "mkosi.sandbox", ignore_errors=True) + + self.copy_static_files(self.cfg.project_dir) + self.render_templates(self.cfg.project_dir) # For compatibility + pass + + def specific_flavor_dir(self, flavor_id: str) -> Path: + flavor_id_underscore = flavor_id.replace("-", "_") + flavor_dir = self.cfg.project_dir / "captain" / "flavors" / flavor_id_underscore + + if not flavor_dir.is_dir(): + log.error( + "Specific Flavor dir '%s' not found. Expected to find directory %s", + flavor_id, + flavor_dir, + ) + raise SystemExit(1) + return flavor_dir + + def render_templates(self, output_dir: Path): + log.debug("Called BaseFlavor.render_templates() with output_dir: %s", output_dir) + # Use jinja2 to render all templates in self.template_map, writing output to output_dir + # The keys of self.template_map are the relative output paths (e.g. "mkosi.conf"), and the + # values are lists of Path objects pointing to Jinja2 template files. + # If more than one template is provided for a given output path, they should be rendered + # in order and concatenated together to produce the final output file. + for relative_output_path, template_paths in self.template_map.items(): + log.debug( + "Rendering templates for output path '%s': %s", + relative_output_path, + template_paths, + ) + rendered_content = "" + for template_path in template_paths: + log.debug("Rendering template %s", template_path) + # Here you would load the template file, render it with the appropriate context + # (e.g. using Jinja2), and append the rendered content to rendered_content. + # For example: + template = jinja2.Environment( + loader=jinja2.FileSystemLoader(template_path.parent), + undefined=jinja2.StrictUndefined, + ).get_template(template_path.name) + rendered_content += template.render(cfg=self.cfg, flavor=self) + + output_file_path = output_dir / relative_output_path + log.debug("Writing rendered content to %s", output_file_path) + output_file_path.parent.mkdir(parents=True, exist_ok=True) + output_file_path.write_text(rendered_content) + + # Make output_file executable @TODO: we will need a way to tell + output_file_path.chmod(output_file_path.stat().st_mode | 0o111) + + def copy_static_files(self, project_dir): + # Do a plain copy of all files in self.static_map to project_dir / relative_path, where + # relative_path is the key in self.static_map + for relative_path, source_path in self.static_map.items(): + destination_path = project_dir / relative_path + log.debug("Copying static file from '%s' to '%s'", source_path, destination_path) + destination_path.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(source_path, destination_path) + + +def create_and_setup_flavor_for_id(flavor_id: str, cfg: Config) -> BaseFlavor: + log.debug("Creating and setting up flavor for id '%s'", flavor_id) + flavor_id_underscore = flavor_id.replace("-", "_") + flavor_dir = cfg.project_dir / "captain" / "flavors" / flavor_id_underscore + + if not flavor_dir.is_dir(): + log.error( + "Flavor '%s' not found. Expected to find directory %s", + flavor_id, + flavor_dir, + ) + raise SystemExit(1) + + wanted_module = f"captain.flavors.{flavor_id_underscore}" + log.debug("Attempting to import flavor module %s from directory %s", wanted_module, flavor_dir) + + try: + module = __import__(wanted_module, fromlist=["create_flavor"]) + except ImportError as e: + log.error( + "Failed to import flavor module %s from directory %s: %s", + wanted_module, + flavor_dir, + e, + ) + raise e + + # Validate API explicitly + if not hasattr(module, "create_flavor"): + log.error("Flavor module %s does not define create_flavor()", wanted_module) + raise SystemExit(1) + + log.debug("Executing %s.create_flavor()", wanted_module) + flavor: BaseFlavor = module.create_flavor() + + if not isinstance(flavor, BaseFlavor): + log.error( + "create_flavor() in %s did not return BaseFlavor (got %r)", + wanted_module, + type(flavor), + ) + raise SystemExit(1) + + log.debug("Calling setup() on flavor %s with config: %s", flavor, cfg) + flavor.setup(cfg, flavor_dir) + + log.debug( + "Flavor is setup; description: %s; supported_architectures: %s", + flavor.description, + flavor.supported_architectures, + ) + + return flavor diff --git a/captain/flavors/common_armbian/__init__.py b/captain/flavors/common_armbian/__init__.py new file mode 100644 index 0000000..70ecb4b --- /dev/null +++ b/captain/flavors/common_armbian/__init__.py @@ -0,0 +1,36 @@ +import logging +from dataclasses import dataclass +from pathlib import Path + +from captain.config import Config +from captain.flavors.common_debian import DebianCommonFlavor + +log: logging.Logger = logging.getLogger(__name__) + + +@dataclass +class ArmbianCommonFlavor(DebianCommonFlavor): + id = "common-armbian" + name = "Armbian Common" + description = "Base flavor for Armbian-based distros" + + def setup(self, cfg: Config, flavor_dir: Path) -> None: + super().setup(cfg, flavor_dir) + log.warning( + "ArmbianCommonFlavor setting up; mkosi arch: %s; flavor_dir: %s", + cfg.arch_info.mkosi_arch, + flavor_dir, + ) + this_flavor_dir = self.specific_flavor_dir("common-armbian") + + # Now, lets enumerate and add all the static files this flavor's mkosi.sandbox directory + # and add them to self.static_map with the key being the relative path from the flavor dir + extra_dir = this_flavor_dir / "mkosi.sandbox" + if extra_dir.exists() and extra_dir.is_dir(): + for extra_file in extra_dir.rglob("*"): + if extra_file.is_file(): + relative_path = extra_file.relative_to(this_flavor_dir) + self.static_map[str(relative_path)] = extra_file + + def extra_mkosi_conf_distribution(self) -> str: + return "# frak this" diff --git a/captain/flavors/common_armbian/mkosi.sandbox/etc/apt/sources.list.d/armbian-next.list b/captain/flavors/common_armbian/mkosi.sandbox/etc/apt/sources.list.d/armbian-next.list new file mode 100644 index 0000000..0b1c08d --- /dev/null +++ b/captain/flavors/common_armbian/mkosi.sandbox/etc/apt/sources.list.d/armbian-next.list @@ -0,0 +1,2 @@ +deb [signed-by=/usr/share/keyrings/armbian-next.gpg] https://apt-test.next.armbian.com armbian main +deb [signed-by=/usr/share/keyrings/armbian-next.gpg] https://apt-test.next.armbian.com armbian-trixie main diff --git a/captain/flavors/common_armbian/mkosi.sandbox/usr/share/keyrings/armbian-next.gpg b/captain/flavors/common_armbian/mkosi.sandbox/usr/share/keyrings/armbian-next.gpg new file mode 100644 index 0000000000000000000000000000000000000000..1e1b9eed3caf617998e259f7f21768d155fecbb0 GIT binary patch literal 1754 zcmV<01||8K0gVJ@>H>fP3;@&B51Qms9@TKnz|j@>$(2);alOF%T1onuG$Q|cT|&sL zytEPkHj@sBw`EG~AeUtd{7P_3lsa6^@vHM`tvz8SBq(yHY`8JMfdy*=CZ!@~gtAt4 zhXq_NI3V4PCFTZPNjZ<>(Xej#m#phSYpLI~2=G98k^CPkL~jn2ZjxzY@wK&$hQFYP zn#pD|Wrwi#){>nrnqriz*E`_bDwL5G=OHW;c$5&oHOiYHGei80cb6Mtq*ueFwi&pH z-3SSgw4DwjI$=a+v0Lq~)~)>TR19Su=H>M8Kjn%)isxQdN;xRzXme|{h5Akh;Fr8% zO8?p~wsPBwJ{mqXa%>aZ1)z^F^?F^(pr9>4<{)8D{w=fq>UmFd^X?d{JGp0xr#TP+ zt0B`-%xl4DX*9l$b;PS6P(I2G`K}MOxStd-$Y~9-jeh0vjwrv+=DWhB*G9E9!52f~ zc}BRa&s0nP*y0SW*(79j+B z*%x>`0J&w-BhJFtUtk7DWgC_S0%hs~fC3u>1q%rX2Lc8a3JC}c0t6NU0|5da0Rk6* z0162Z*I!@;M`at9kLU~lpa!JXupu(oB;p17i_4kq>g+%>&%iA%+H1heJN00MWi3Ss zA-Z1Cp>Qwp|$Z`SFWN}adzt`p{Jez`<*`q*vU3Hjux|bxdDv?W$FTe0So}Dfs>{9 z81Jhq@I*F{Fjz)F`81JAm&fS(FlraNYR}3>M(v&bbsb=2g#)saeNHfhbx8_T8&)U{ ze&`&@B4q~$)JOvvroKd*c|(c6o5=yov1)7QE(@dz{9N14n-@0uo`R(+{7j4b`s?MOve?TpG7U_ z?gXHO4_N%1?U_KFLi=(Q(q`FRS9-68(ncBQv{p6Jjh5^{)zrxQ2hMxY{rm2)g_j!d z_s$+KGUCYHl@0Du;8yXiF=|3xvY_Sl(o-;ZFDBUMKfNaZ5U`1_Pb?L3mSvl|R<%tF zry!8H*$cM?UIb^+0`ob%`y7f(3m>PG)>vRm%V18XHfrJ4uIqrzQuaT9$@gJ5vaAxB z@&q1@Y5ah4AN}6=!?6=!%9P|5raw>@wkogOrY2hb1H0K=B9LIyVeC|Z`raZHc z-mqT>Ub%fF@NaSmX_rS@tws%^Z^Ej>`EwhkOYR;19dpu)>On9xr4ke$w9u~MrtnIV z>Do7}!8@am6)$QdKlERVQes5%u&L20cRUweR@-51D*2gsggEBcC-tukEX_$^ig*0E zqSjy{Ub1%AhFeIp-Fq8@?8rqsfCY*3=Nm;N;s5vl9Mpjq>n(@A)R_zVLs;qft^v`7 zbR^E!-^Uk?$8Xe^n^ZoqNN-O wljP_nfrIzce3ANkg4UQr0(u`g*2QUsVCVNj$KBe>D})lt*S68!hE0DbpL>r+Gynhq literal 0 HcmV?d00001 diff --git a/captain/flavors/common_debian/__init__.py b/captain/flavors/common_debian/__init__.py new file mode 100644 index 0000000..4200e4d --- /dev/null +++ b/captain/flavors/common_debian/__init__.py @@ -0,0 +1,39 @@ +import logging +from dataclasses import dataclass +from pathlib import Path + +from captain.config import Config +from captain.flavor import BaseFlavor + +log: logging.Logger = logging.getLogger(__name__) + + +@dataclass +class DebianCommonFlavor(BaseFlavor): + id = "common-debian" + name = "Debian Common" + description = "Base flavor for Debian-based distros" + + def setup(self, cfg: Config, flavor_dir: Path) -> None: + super().setup(cfg, flavor_dir) + log.warning( + "DebianCommonFlavor setting up; mkosi arch: %s; flavor_dir: %s", + cfg.arch_info.mkosi_arch, + flavor_dir, + ) + this_flavor_dir = self.specific_flavor_dir("common-debian") + self.template_map["mkosi.conf"] = [this_flavor_dir / "mkosi.conf.j2"] + self.template_map["mkosi.postinst"] = [this_flavor_dir / "mkosi.postinst.sh.j2"] + self.template_map["mkosi.finalize"] = [this_flavor_dir / "mkosi.finalize.sh.j2"] + + # Now, lets enumerate and add all the static files this flavor's mkosi.extra directory + # and add them to self.static_map with the key being the relative path from the flavor dir + extra_dir = this_flavor_dir / "mkosi.extra" + if extra_dir.exists() and extra_dir.is_dir(): + for extra_file in extra_dir.rglob("*"): + if extra_file.is_file(): + relative_path = extra_file.relative_to(this_flavor_dir) + self.static_map[str(relative_path)] = extra_file + + def extra_mkosi_conf_distribution(self) -> str: + return "" diff --git a/mkosi.conf b/captain/flavors/common_debian/mkosi.conf.j2 similarity index 95% rename from mkosi.conf rename to captain/flavors/common_debian/mkosi.conf.j2 index 50d24b3..dae28b3 100644 --- a/mkosi.conf +++ b/captain/flavors/common_debian/mkosi.conf.j2 @@ -1,6 +1,7 @@ [Distribution] Distribution=debian Release=trixie +{{ flavor.extra_mkosi_conf_distribution() }} [Output] Format=cpio @@ -27,7 +28,7 @@ WithDocs=no # pivot_root(2) work for container runtimes (runc). Packages= - linux-image-generic + {{ ' '.join(flavor.kernel_packages()) }} tiny-initramfs # a tiny initrd builder to account for the linux-image dependency # systemd and core systemd diff --git a/mkosi.extra/etc/acpi/events/powerbtn b/captain/flavors/common_debian/mkosi.extra/etc/acpi/events/powerbtn similarity index 100% rename from mkosi.extra/etc/acpi/events/powerbtn rename to captain/flavors/common_debian/mkosi.extra/etc/acpi/events/powerbtn diff --git a/mkosi.extra/etc/acpi/powerbtn.sh b/captain/flavors/common_debian/mkosi.extra/etc/acpi/powerbtn.sh similarity index 100% rename from mkosi.extra/etc/acpi/powerbtn.sh rename to captain/flavors/common_debian/mkosi.extra/etc/acpi/powerbtn.sh diff --git a/mkosi.extra/etc/cni/net.d/10-bridge.conflist b/captain/flavors/common_debian/mkosi.extra/etc/cni/net.d/10-bridge.conflist similarity index 100% rename from mkosi.extra/etc/cni/net.d/10-bridge.conflist rename to captain/flavors/common_debian/mkosi.extra/etc/cni/net.d/10-bridge.conflist diff --git a/mkosi.extra/etc/containerd/config.toml b/captain/flavors/common_debian/mkosi.extra/etc/containerd/config.toml similarity index 100% rename from mkosi.extra/etc/containerd/config.toml rename to captain/flavors/common_debian/mkosi.extra/etc/containerd/config.toml diff --git a/mkosi.extra/etc/locale.conf b/captain/flavors/common_debian/mkosi.extra/etc/locale.conf similarity index 100% rename from mkosi.extra/etc/locale.conf rename to captain/flavors/common_debian/mkosi.extra/etc/locale.conf diff --git a/mkosi.extra/etc/machine-id b/captain/flavors/common_debian/mkosi.extra/etc/machine-id similarity index 100% rename from mkosi.extra/etc/machine-id rename to captain/flavors/common_debian/mkosi.extra/etc/machine-id diff --git a/mkosi.extra/etc/modules-load.d/ipmi.conf b/captain/flavors/common_debian/mkosi.extra/etc/modules-load.d/ipmi.conf similarity index 100% rename from mkosi.extra/etc/modules-load.d/ipmi.conf rename to captain/flavors/common_debian/mkosi.extra/etc/modules-load.d/ipmi.conf diff --git a/mkosi.extra/etc/motd b/captain/flavors/common_debian/mkosi.extra/etc/motd similarity index 100% rename from mkosi.extra/etc/motd rename to captain/flavors/common_debian/mkosi.extra/etc/motd diff --git a/mkosi.extra/etc/nerdctl/nerdctl.toml b/captain/flavors/common_debian/mkosi.extra/etc/nerdctl/nerdctl.toml similarity index 100% rename from mkosi.extra/etc/nerdctl/nerdctl.toml rename to captain/flavors/common_debian/mkosi.extra/etc/nerdctl/nerdctl.toml diff --git a/mkosi.extra/etc/os-release b/captain/flavors/common_debian/mkosi.extra/etc/os-release similarity index 100% rename from mkosi.extra/etc/os-release rename to captain/flavors/common_debian/mkosi.extra/etc/os-release diff --git a/mkosi.extra/etc/rsyslog.conf b/captain/flavors/common_debian/mkosi.extra/etc/rsyslog.conf similarity index 100% rename from mkosi.extra/etc/rsyslog.conf rename to captain/flavors/common_debian/mkosi.extra/etc/rsyslog.conf diff --git a/mkosi.extra/etc/sysctl.d/99-ip-forward.conf b/captain/flavors/common_debian/mkosi.extra/etc/sysctl.d/99-ip-forward.conf similarity index 100% rename from mkosi.extra/etc/sysctl.d/99-ip-forward.conf rename to captain/flavors/common_debian/mkosi.extra/etc/sysctl.d/99-ip-forward.conf diff --git a/mkosi.extra/etc/sysctl.d/99-quiet-audit.conf b/captain/flavors/common_debian/mkosi.extra/etc/sysctl.d/99-quiet-audit.conf similarity index 100% rename from mkosi.extra/etc/sysctl.d/99-quiet-audit.conf rename to captain/flavors/common_debian/mkosi.extra/etc/sysctl.d/99-quiet-audit.conf diff --git a/mkosi.extra/etc/systemd/network/80-dhcp.network b/captain/flavors/common_debian/mkosi.extra/etc/systemd/network/80-dhcp.network similarity index 100% rename from mkosi.extra/etc/systemd/network/80-dhcp.network rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/network/80-dhcp.network diff --git a/mkosi.extra/etc/systemd/resolved.conf.d/no-stub.conf b/captain/flavors/common_debian/mkosi.extra/etc/systemd/resolved.conf.d/no-stub.conf similarity index 100% rename from mkosi.extra/etc/systemd/resolved.conf.d/no-stub.conf rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/resolved.conf.d/no-stub.conf diff --git a/mkosi.extra/etc/systemd/system/captainos-banner.service b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/captainos-banner.service similarity index 100% rename from mkosi.extra/etc/systemd/system/captainos-banner.service rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/captainos-banner.service diff --git a/mkosi.extra/etc/systemd/system/captainos-static-network.service b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/captainos-static-network.service similarity index 100% rename from mkosi.extra/etc/systemd/system/captainos-static-network.service rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/captainos-static-network.service diff --git a/mkosi.extra/etc/systemd/system/containerd.service b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/containerd.service similarity index 100% rename from mkosi.extra/etc/systemd/system/containerd.service rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/containerd.service diff --git a/mkosi.extra/etc/systemd/system/default.target b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/default.target similarity index 100% rename from mkosi.extra/etc/systemd/system/default.target rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/default.target diff --git a/mkosi.extra/etc/systemd/system/initrd-cleanup.service b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/initrd-cleanup.service similarity index 100% rename from mkosi.extra/etc/systemd/system/initrd-cleanup.service rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/initrd-cleanup.service diff --git a/mkosi.extra/etc/systemd/system/initrd-parse-etc.service b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/initrd-parse-etc.service similarity index 100% rename from mkosi.extra/etc/systemd/system/initrd-parse-etc.service rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/initrd-parse-etc.service diff --git a/mkosi.extra/etc/systemd/system/initrd-switch-root.service b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/initrd-switch-root.service similarity index 100% rename from mkosi.extra/etc/systemd/system/initrd-switch-root.service rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/initrd-switch-root.service diff --git a/mkosi.extra/etc/systemd/system/initrd-switch-root.target b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/initrd-switch-root.target similarity index 100% rename from mkosi.extra/etc/systemd/system/initrd-switch-root.target rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/initrd-switch-root.target diff --git a/mkosi.extra/etc/systemd/system/initrd-udevadm-cleanup-db.service b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/initrd-udevadm-cleanup-db.service similarity index 100% rename from mkosi.extra/etc/systemd/system/initrd-udevadm-cleanup-db.service rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/initrd-udevadm-cleanup-db.service diff --git a/mkosi.extra/etc/systemd/system/initrd.target b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/initrd.target similarity index 100% rename from mkosi.extra/etc/systemd/system/initrd.target rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/initrd.target diff --git a/mkosi.extra/etc/systemd/system/rsyslog-hostname-reload.path b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/rsyslog-hostname-reload.path similarity index 100% rename from mkosi.extra/etc/systemd/system/rsyslog-hostname-reload.path rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/rsyslog-hostname-reload.path diff --git a/mkosi.extra/etc/systemd/system/rsyslog-hostname-reload.service b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/rsyslog-hostname-reload.service similarity index 100% rename from mkosi.extra/etc/systemd/system/rsyslog-hostname-reload.service rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/rsyslog-hostname-reload.service diff --git a/mkosi.extra/etc/systemd/system/rsyslog.service b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/rsyslog.service similarity index 100% rename from mkosi.extra/etc/systemd/system/rsyslog.service rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/rsyslog.service diff --git a/mkosi.extra/etc/systemd/system/serial-getty@.service.d/autologin.conf b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/serial-getty@.service.d/autologin.conf similarity index 100% rename from mkosi.extra/etc/systemd/system/serial-getty@.service.d/autologin.conf rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/serial-getty@.service.d/autologin.conf diff --git a/mkosi.extra/etc/systemd/system/systemd-firstboot.service b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/systemd-firstboot.service similarity index 100% rename from mkosi.extra/etc/systemd/system/systemd-firstboot.service rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/systemd-firstboot.service diff --git a/mkosi.extra/etc/systemd/system/tink-agent-setup.service b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/tink-agent-setup.service similarity index 100% rename from mkosi.extra/etc/systemd/system/tink-agent-setup.service rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/tink-agent-setup.service diff --git a/mkosi.extra/etc/systemd/system/tink-agent.service b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/tink-agent.service similarity index 100% rename from mkosi.extra/etc/systemd/system/tink-agent.service rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/system/tink-agent.service diff --git a/mkosi.extra/etc/systemd/timesyncd.conf b/captain/flavors/common_debian/mkosi.extra/etc/systemd/timesyncd.conf similarity index 100% rename from mkosi.extra/etc/systemd/timesyncd.conf rename to captain/flavors/common_debian/mkosi.extra/etc/systemd/timesyncd.conf diff --git a/mkosi.extra/etc/timezone b/captain/flavors/common_debian/mkosi.extra/etc/timezone similarity index 100% rename from mkosi.extra/etc/timezone rename to captain/flavors/common_debian/mkosi.extra/etc/timezone diff --git a/mkosi.extra/init b/captain/flavors/common_debian/mkosi.extra/init similarity index 100% rename from mkosi.extra/init rename to captain/flavors/common_debian/mkosi.extra/init diff --git a/mkosi.extra/root/.bashrc b/captain/flavors/common_debian/mkosi.extra/root/.bashrc similarity index 100% rename from mkosi.extra/root/.bashrc rename to captain/flavors/common_debian/mkosi.extra/root/.bashrc diff --git a/mkosi.extra/root/.profile b/captain/flavors/common_debian/mkosi.extra/root/.profile similarity index 100% rename from mkosi.extra/root/.profile rename to captain/flavors/common_debian/mkosi.extra/root/.profile diff --git a/mkosi.extra/usr/local/bin/captainos-banner b/captain/flavors/common_debian/mkosi.extra/usr/local/bin/captainos-banner similarity index 100% rename from mkosi.extra/usr/local/bin/captainos-banner rename to captain/flavors/common_debian/mkosi.extra/usr/local/bin/captainos-banner diff --git a/mkosi.extra/usr/local/bin/captainos-static-network b/captain/flavors/common_debian/mkosi.extra/usr/local/bin/captainos-static-network similarity index 100% rename from mkosi.extra/usr/local/bin/captainos-static-network rename to captain/flavors/common_debian/mkosi.extra/usr/local/bin/captainos-static-network diff --git a/mkosi.extra/usr/local/bin/rsyslog-start b/captain/flavors/common_debian/mkosi.extra/usr/local/bin/rsyslog-start similarity index 100% rename from mkosi.extra/usr/local/bin/rsyslog-start rename to captain/flavors/common_debian/mkosi.extra/usr/local/bin/rsyslog-start diff --git a/mkosi.extra/usr/local/bin/tink-agent-setup b/captain/flavors/common_debian/mkosi.extra/usr/local/bin/tink-agent-setup similarity index 100% rename from mkosi.extra/usr/local/bin/tink-agent-setup rename to captain/flavors/common_debian/mkosi.extra/usr/local/bin/tink-agent-setup diff --git a/mkosi.extra/usr/local/bin/tink-agent-start b/captain/flavors/common_debian/mkosi.extra/usr/local/bin/tink-agent-start similarity index 100% rename from mkosi.extra/usr/local/bin/tink-agent-start rename to captain/flavors/common_debian/mkosi.extra/usr/local/bin/tink-agent-start diff --git a/mkosi.finalize b/captain/flavors/common_debian/mkosi.finalize.sh.j2 similarity index 100% rename from mkosi.finalize rename to captain/flavors/common_debian/mkosi.finalize.sh.j2 diff --git a/mkosi.postinst b/captain/flavors/common_debian/mkosi.postinst.sh.j2 similarity index 100% rename from mkosi.postinst rename to captain/flavors/common_debian/mkosi.postinst.sh.j2 diff --git a/captain/flavors/trixie_full/__init__.py b/captain/flavors/trixie_full/__init__.py new file mode 100644 index 0000000..c3bcbf3 --- /dev/null +++ b/captain/flavors/trixie_full/__init__.py @@ -0,0 +1,32 @@ +import logging +from dataclasses import dataclass +from pathlib import Path + +from captain.config import Config +from captain.flavor import BaseFlavor +from captain.flavors.common_debian import DebianCommonFlavor + +log: logging.Logger = logging.getLogger(__name__) + + +def create_flavor() -> BaseFlavor: + return TrixieFullFlavor() + + +@dataclass +class TrixieFullFlavor(DebianCommonFlavor): + id = "trixie-full" + name = "Trixie Full" + description = "Debian Trixie based with linux-image-generic standard Debian kernel" + supported_architectures = frozenset(["amd64", "arm64"]) + + def setup(self, cfg: Config, flavor_dir: Path) -> None: + super().setup(cfg, flavor_dir) + log.warning( + "TrixieFullFlavor setting up; mkosi arch: %s; flavor_dir: %s", + cfg.arch_info.mkosi_arch, + flavor_dir, + ) + + def kernel_packages(self) -> set[str]: + return {"linux-image-generic"} diff --git a/captain/flavors/trixie_rockchip64/__init__.py b/captain/flavors/trixie_rockchip64/__init__.py new file mode 100644 index 0000000..da08158 --- /dev/null +++ b/captain/flavors/trixie_rockchip64/__init__.py @@ -0,0 +1,32 @@ +import logging +from dataclasses import dataclass +from pathlib import Path + +from captain.config import Config +from captain.flavor import BaseFlavor +from captain.flavors.common_armbian import ArmbianCommonFlavor + +log: logging.Logger = logging.getLogger(__name__) + + +def create_flavor() -> BaseFlavor: + return TrixieRockchip64Flavor() + + +@dataclass +class TrixieRockchip64Flavor(ArmbianCommonFlavor): + id = "trixie-rockchip64" + name = "Trixie for Rockchip 64-bit ARM machines" + description = "Debian Trixie based with Armbian's rockchip64-edge kernel" + supported_architectures = frozenset(["arm64"]) # does NOT support amd64 + + def setup(self, cfg: Config, flavor_dir: Path) -> None: + super().setup(cfg, flavor_dir) + log.warning( + "TrixieRockchip64Flavor setting up; mkosi arch: %s; flavor_dir: %s", + cfg.arch_info.mkosi_arch, + flavor_dir, + ) + + def kernel_packages(self) -> set[str]: + return {"linux-image-edge-rockchip64"} diff --git a/pyproject.toml b/pyproject.toml index 17aa984..c518bca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,10 +2,14 @@ version = "0.0.1" name = "captain" requires-python = ">=3.13" -dependencies = ["configargparse>=1.7", "rich>=14.3.3"] +dependencies = [ + "configargparse>=1.7", + "rich>=14.3.3", + "jinja2>=3.1.6" +] [project.optional-dependencies] -dev = ["ruff>=0.9", "pyright>=1.1"] +dev = ["ruff>=0.15.9", "pyright>=1.1"] [tool.ruff] target-version = "py310" From b8bb259f0618f55c539d527c83199185e2e28da5 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Sun, 5 Apr 2026 13:35:16 +0200 Subject: [PATCH 27/93] gha: add trixie-rockchip64 flavor Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b28fee..d9c07ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,6 +94,7 @@ jobs: include: - { arch: amd64, output_arch: x86_64, iso: true, FLAVOR_ID: "trixie-full" } - { arch: arm64, output_arch: aarch64, iso: true, FLAVOR_ID: "trixie-full" } + - { arch: arm64, output_arch: aarch64, iso: false, FLAVOR_ID: "trixie-rockchip64" } env: ARCH: ${{ matrix.arch }} MKOSI_MODE: docker From 3b920e776debc8c8391d7f13fbe849365ca0a2fb Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Sun, 5 Apr 2026 13:46:16 +0200 Subject: [PATCH 28/93] stages: show what tools_mode is running Signed-off-by: Ricardo Pardini --- captain/cli/_stages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/captain/cli/_stages.py b/captain/cli/_stages.py index 05b6c43..3c39954 100644 --- a/captain/cli/_stages.py +++ b/captain/cli/_stages.py @@ -21,13 +21,13 @@ def _build_tools_stage(cfg: Config) -> None: # --- native ------------------------------------------------------- if cfg.tools_mode == "native": - log.info("Downloading tools (nerdctl, containerd, etc.)...") + log.info("Downloading tools (nerdctl, containerd, etc.) native...") tools.download_all(cfg) return # --- docker ------------------------------------------------------- docker.build_builder(cfg) - log.info("Downloading tools (nerdctl, containerd, etc.)...") + log.info("Downloading tools (nerdctl, containerd, etc.) docker...") docker.run_in_builder( cfg, "--entrypoint", From 5da98742608956147883d0e24006703a9fde02a1 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Sun, 5 Apr 2026 13:53:18 +0200 Subject: [PATCH 29/93] gha: pass --arch to build.py build Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9c07ed..4753b32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,7 +121,7 @@ jobs: uses: astral-sh/setup-uv@v7 - name: Build initramfs - run: uv run ./build.py build # full build, incl initramfs and iso + run: uv run ./build.py --arch=${{ matrix.arch }} # full build, incl initramfs and iso - name: Upload initramfs artifacts uses: actions/upload-artifact@v6 From a239f45ad41f84c0acf01c3b96dc4d4f984d8569 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Sun, 5 Apr 2026 14:03:39 +0200 Subject: [PATCH 30/93] common_debian: add 01nopty with `Dpkg::Use-Pty "0";` to mkosi sandbox tree Signed-off-by: Ricardo Pardini --- captain/flavors/common_debian/__init__.py | 9 +++++++++ .../mkosi.sandbox/etc/apt/apt.conf.d/01nopty | 1 + 2 files changed, 10 insertions(+) create mode 100644 captain/flavors/common_debian/mkosi.sandbox/etc/apt/apt.conf.d/01nopty diff --git a/captain/flavors/common_debian/__init__.py b/captain/flavors/common_debian/__init__.py index 4200e4d..7803bec 100644 --- a/captain/flavors/common_debian/__init__.py +++ b/captain/flavors/common_debian/__init__.py @@ -35,5 +35,14 @@ def setup(self, cfg: Config, flavor_dir: Path) -> None: relative_path = extra_file.relative_to(this_flavor_dir) self.static_map[str(relative_path)] = extra_file + # Now, lets enumerate and add all the static files this flavor's mkosi.sandbox directory + # and add them to self.static_map with the key being the relative path from the flavor dir + extra_dir = this_flavor_dir / "mkosi.sandbox" + if extra_dir.exists() and extra_dir.is_dir(): + for extra_file in extra_dir.rglob("*"): + if extra_file.is_file(): + relative_path = extra_file.relative_to(this_flavor_dir) + self.static_map[str(relative_path)] = extra_file + def extra_mkosi_conf_distribution(self) -> str: return "" diff --git a/captain/flavors/common_debian/mkosi.sandbox/etc/apt/apt.conf.d/01nopty b/captain/flavors/common_debian/mkosi.sandbox/etc/apt/apt.conf.d/01nopty new file mode 100644 index 0000000..ba2d790 --- /dev/null +++ b/captain/flavors/common_debian/mkosi.sandbox/etc/apt/apt.conf.d/01nopty @@ -0,0 +1 @@ +Dpkg::Use-Pty "0"; From 9fe9ed9978b06dd629d3629734c121fceb2fdefb Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 11:57:39 +0200 Subject: [PATCH 31/93] captain: add support for mkosi skeleton tree - gha: still trying to fix dpkg/apt output in gha - this went nowhere, but hindsight is 20/20 Signed-off-by: Ricardo Pardini --- .gitignore | 1 + captain/docker.py | 1 + captain/flavor.py | 1 + captain/flavors/common_debian/__init__.py | 9 +++++++++ captain/flavors/common_debian/mkosi.conf.j2 | 1 + .../mkosi.sandbox/etc/apt/apt.conf.d/01nopty | 3 +++ .../mkosi.skeleton/etc/apt/apt.conf.d/01nopty | 4 ++++ 7 files changed, 20 insertions(+) create mode 100644 captain/flavors/common_debian/mkosi.skeleton/etc/apt/apt.conf.d/01nopty diff --git a/.gitignore b/.gitignore index 9af6934..4098d42 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ out/ /mkosi.postinst /mkosi.extra /mkosi.sandbox +/mkosi.skeleton # Editor *.swp diff --git a/captain/docker.py b/captain/docker.py index 496a0c9..2761ab1 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -189,6 +189,7 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: docker_args += ["-v", f"{cfg.project_dir}/mkosi.output:/work/mkosi.output"] docker_args += ["-v", f"{cfg.project_dir}/mkosi.extra:/work/mkosi.extra"] docker_args += ["-v", f"{cfg.project_dir}/mkosi.sandbox:/work/mkosi.sandbox"] + docker_args += ["-v", f"{cfg.project_dir}/mkosi.skeleton:/work/mkosi.skeleton"] docker_args += ["-v", f"{cfg.project_dir}/out:/work/out"] docker_args += ["-v", f"{cfg.project_dir}/mkosi.conf:/work/mkosi.conf"] diff --git a/captain/flavor.py b/captain/flavor.py index e56e472..f4e75df 100644 --- a/captain/flavor.py +++ b/captain/flavor.py @@ -50,6 +50,7 @@ def generate(self): shutil.rmtree(self.cfg.project_dir / "mkosi.finalize", ignore_errors=True) shutil.rmtree(self.cfg.project_dir / "mkosi.extra", ignore_errors=True) shutil.rmtree(self.cfg.project_dir / "mkosi.sandbox", ignore_errors=True) + shutil.rmtree(self.cfg.project_dir / "mkosi.skeleton", ignore_errors=True) self.copy_static_files(self.cfg.project_dir) self.render_templates(self.cfg.project_dir) # For compatibility diff --git a/captain/flavors/common_debian/__init__.py b/captain/flavors/common_debian/__init__.py index 7803bec..292cabb 100644 --- a/captain/flavors/common_debian/__init__.py +++ b/captain/flavors/common_debian/__init__.py @@ -44,5 +44,14 @@ def setup(self, cfg: Config, flavor_dir: Path) -> None: relative_path = extra_file.relative_to(this_flavor_dir) self.static_map[str(relative_path)] = extra_file + # Now, lets enumerate and add all the static files this flavor's mkosi.skeleton directory + # and add them to self.static_map with the key being the relative path from the flavor dir + extra_dir = this_flavor_dir / "mkosi.skeleton" + if extra_dir.exists() and extra_dir.is_dir(): + for extra_file in extra_dir.rglob("*"): + if extra_file.is_file(): + relative_path = extra_file.relative_to(this_flavor_dir) + self.static_map[str(relative_path)] = extra_file + def extra_mkosi_conf_distribution(self) -> str: return "" diff --git a/captain/flavors/common_debian/mkosi.conf.j2 b/captain/flavors/common_debian/mkosi.conf.j2 index dae28b3..98a307e 100644 --- a/captain/flavors/common_debian/mkosi.conf.j2 +++ b/captain/flavors/common_debian/mkosi.conf.j2 @@ -10,6 +10,7 @@ CompressLevel=19 OutputDirectory=mkosi.output [Build] +Environment=TERM=dumb ToolsTree=yes Incremental=yes CacheDirectory=mkosi.cache diff --git a/captain/flavors/common_debian/mkosi.sandbox/etc/apt/apt.conf.d/01nopty b/captain/flavors/common_debian/mkosi.sandbox/etc/apt/apt.conf.d/01nopty index ba2d790..551c0a6 100644 --- a/captain/flavors/common_debian/mkosi.sandbox/etc/apt/apt.conf.d/01nopty +++ b/captain/flavors/common_debian/mkosi.sandbox/etc/apt/apt.conf.d/01nopty @@ -1 +1,4 @@ Dpkg::Use-Pty "0"; +APT::Quiet "2"; +Dpkg::Progress-Fancy "false"; +APT::Color "true"; diff --git a/captain/flavors/common_debian/mkosi.skeleton/etc/apt/apt.conf.d/01nopty b/captain/flavors/common_debian/mkosi.skeleton/etc/apt/apt.conf.d/01nopty new file mode 100644 index 0000000..551c0a6 --- /dev/null +++ b/captain/flavors/common_debian/mkosi.skeleton/etc/apt/apt.conf.d/01nopty @@ -0,0 +1,4 @@ +Dpkg::Use-Pty "0"; +APT::Quiet "2"; +Dpkg::Progress-Fancy "false"; +APT::Color "true"; From 85442643890585517d30dc5b2d611a569ccdd06a Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 11:59:02 +0200 Subject: [PATCH 32/93] common_debian: common bash header with logging, sprinkle some dust on postinst and finalize Signed-off-by: Ricardo Pardini --- captain/flavors/common_debian/__init__.py | 10 ++- captain/flavors/common_debian/bash.header.sh | 19 +++++ .../common_debian/mkosi.finalize.sh.j2 | 50 ++++++------ .../common_debian/mkosi.postinst.sh.j2 | 76 +++++++++++++------ 4 files changed, 104 insertions(+), 51 deletions(-) create mode 100644 captain/flavors/common_debian/bash.header.sh diff --git a/captain/flavors/common_debian/__init__.py b/captain/flavors/common_debian/__init__.py index 292cabb..f9c22d2 100644 --- a/captain/flavors/common_debian/__init__.py +++ b/captain/flavors/common_debian/__init__.py @@ -23,8 +23,14 @@ def setup(self, cfg: Config, flavor_dir: Path) -> None: ) this_flavor_dir = self.specific_flavor_dir("common-debian") self.template_map["mkosi.conf"] = [this_flavor_dir / "mkosi.conf.j2"] - self.template_map["mkosi.postinst"] = [this_flavor_dir / "mkosi.postinst.sh.j2"] - self.template_map["mkosi.finalize"] = [this_flavor_dir / "mkosi.finalize.sh.j2"] + self.template_map["mkosi.postinst"] = [ + this_flavor_dir / "bash.header.sh", + this_flavor_dir / "mkosi.postinst.sh.j2", + ] + self.template_map["mkosi.finalize"] = [ + this_flavor_dir / "bash.header.sh", + this_flavor_dir / "mkosi.finalize.sh.j2", + ] # Now, lets enumerate and add all the static files this flavor's mkosi.extra directory # and add them to self.static_map with the key being the relative path from the flavor dir diff --git a/captain/flavors/common_debian/bash.header.sh b/captain/flavors/common_debian/bash.header.sh new file mode 100644 index 0000000..afde1fa --- /dev/null +++ b/captain/flavors/common_debian/bash.header.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -euo pipefail + +# logger utility, output ANSI-colored messages to stderr; first argument is level (debug/info/warn/error), all other arguments are the message. +declare -A log_colors=(["debug"]="0;36" ["info"]="0;32" ["notice"]="1;32" ["warn"]="1;33" ["warning"]="1;33" ["error"]="1;31") +declare -A log_emoji=(["debug"]="🐛" ["info"]="🌿" ["notice"]="🌱" ["warn"]="🚸" ["warning"]="🚸" ["error"]="🚨") +function log() { + declare level="${1}" + shift + [[ "${level}" == "debug" && "${DEBUG}" != "yes" ]] && return # Skip debugs unless DEBUG=yes is set in the environment + # Normal output + declare color="\033[${log_colors[${level}]}m" + declare emoji="${log_emoji[${level}]}" + declare ansi_reset="\033[0m" + level=$(printf "%-5s" "${level}") # pad to 5 characters before printing + echo -e "${emoji} ${ansi_reset}[${color}${level}${ansi_reset}] ${color}${*}${ansi_reset}" >&2 +} + diff --git a/captain/flavors/common_debian/mkosi.finalize.sh.j2 b/captain/flavors/common_debian/mkosi.finalize.sh.j2 index f97b742..c0c67d4 100755 --- a/captain/flavors/common_debian/mkosi.finalize.sh.j2 +++ b/captain/flavors/common_debian/mkosi.finalize.sh.j2 @@ -3,10 +3,10 @@ # The root filesystem is writable here. set -euo pipefail -echo "==> Showing full contents of environment variables for debugging:" +log info "Showing full contents of environment variables for debugging:" env -echo "==> CaptainOS finalize: unlocking root account..." +log info "CaptainOS finalize: unlocking root account..." # Unlock root with empty password # Replace the locked root entry (! or * in password field) with empty hash @@ -16,38 +16,38 @@ if [[ -f "$BUILDROOT/etc/shadow" ]]; then fi # Ensure /etc/initrd-release does not exist -rm -f "$BUILDROOT/etc/initrd-release" -echo " /etc/initrd-release removed" +log info "Ensure no /etc/initrd-release exists..." +rm -fv "$BUILDROOT/etc/initrd-release" # Remove systemd-firstboot completely -rm -f "$BUILDROOT/usr/lib/systemd/system/systemd-firstboot.service" -rm -f "$BUILDROOT/usr/lib/systemd/system/sysinit.target.wants/systemd-firstboot.service" -rm -f "$BUILDROOT/usr/bin/systemd-firstboot" -echo " systemd-firstboot removed" +log info "Completely remove systemd-firstboot..." +rm -fv "$BUILDROOT/usr/lib/systemd/system/systemd-firstboot.service" +rm -fv "$BUILDROOT/usr/lib/systemd/system/sysinit.target.wants/systemd-firstboot.service" +rm -fv "$BUILDROOT/usr/bin/systemd-firstboot" # Point /etc/resolv.conf at the non-stub resolv.conf written by # systemd-resolved. This file contains upstream (DHCP-provided) # nameservers instead of 127.0.0.53. tink-agent bind-mounts the host's # /etc/resolv.conf into action containers so they need real, routable # nameservers (the stub address is unreachable from container namespaces). -ln -sf /run/systemd/resolve/resolv.conf "$BUILDROOT/etc/resolv.conf" -echo " /etc/resolv.conf -> /run/systemd/resolve/resolv.conf" +log info "Handle resolv.conf..." +ln -sfv /run/systemd/resolve/resolv.conf "$BUILDROOT/etc/resolv.conf" # Ensure /init (switch_root script) is executable +log info "Ensure /init is executable..." if [[ -f "$BUILDROOT/init" ]]; then - chmod +x "$BUILDROOT/init" - echo " /init made executable" + chmod -v +x "$BUILDROOT/init" fi # Ensure tool binaries are executable. # GitHub Actions artifact upload/download and some archive tools strip # the execute bit. Re-apply +x to every tool directory so containerd, # runc, nerdctl, and CNI plugins can actually run. +log info "Ensure tool binaries are executable..." for dir in usr/local/bin opt/cni/bin; do target="$BUILDROOT/$dir" if [[ -d "$target" ]]; then - find "$target" -type f -exec chmod +x {} + - echo " +x restored on $dir/*" + find "$target" -type f -exec chmod -v +x {} + fi done @@ -60,7 +60,7 @@ done MODDIR="$BUILDROOT/usr/lib/modules" if [[ -d "$MODDIR" ]]; then - echo "==> Trimming unnecessary kernel modules..." + log info "Trimming unnecessary kernel modules..." BEFORE=$(du -sb "$MODDIR" | awk '{print $1}') # @TODO: this has to be templated, for flavors like fat or full we should keep most modules. @@ -92,14 +92,14 @@ if [[ -d "$MODDIR" ]]; then ) for pattern in "${EXCLUDE_PATTERNS[@]}"; do - echo "--> Removing modules matching pattern: '${pattern}'" - find "$MODDIR" -path "$pattern" -type d -exec rm -rfv {} + || true + log info " Removing modules matching pattern: '${pattern}'" + find "$MODDIR" -path "$pattern" -type d -exec rm -rf {} + || true done AFTER=$(du -sb "$MODDIR" | awk '{print $1}') SAVED=$(( (BEFORE - AFTER) / 1024 / 1024 )) [[ $SAVED -lt 0 ]] && SAVED=0 - echo " Removed ${SAVED}MB of unnecessary kernel modules" + log info " Removed ${SAVED}MB of unnecessary kernel modules" # Regenerate module dependency files after removal for all kernel versions for kdir in "$MODDIR"/*; do @@ -109,27 +109,27 @@ if [[ -d "$MODDIR" ]]; then done fi -echo "==> Listing 20 biggest remaining kernel modules du: ${MODDIR}" +log info "Listing 20 biggest remaining kernel modules du: ${MODDIR}" du -h -x -d 5 "$MODDIR" | sort -h | tail -n20 || true -echo "==> Removing /boot from the image to save space" +log info "Removing /boot from the image to save space" rm -rf "${BUILDROOT:?}"/boot DTB_SRC_DIR=$(echo -n "${BUILDROOT}"/usr/lib/linux-image-*) if [[ -d "$DTB_SRC_DIR" ]]; then - echo "==> Exporting kernel DTBs; DTB_DIR: ${DTB_SRC_DIR}" + log info "Exporting kernel DTBs; DTB_DIR: ${DTB_SRC_DIR}" mkdir -pv "${BUILDROOT}/root/dtb" # Move every .dtb* file in the DTB_SRC_DIR to "${BUILDROOT}/root/dtb" -- maintain the directory structure find "${DTB_SRC_DIR}" -type f -name "*.dtb*" -exec sh -c 'for f; do mkdir -p "${0}/$(dirname "${f#'"${DTB_SRC_DIR}"'/}")" && mv "$f" "${0}/${f#'"${DTB_SRC_DIR}"'/}" ; done' "${BUILDROOT}/root/dtb" {} + || true # Now simply output the dtb directory to mkosi's OUTPUTDIR - echo "==> Copying DTBs to mkosi output directory: ${OUTPUTDIR}/dtb" + log info "Copying DTBs to mkosi output directory: ${OUTPUTDIR}/dtb" mv "${BUILDROOT}/root/dtb" "${OUTPUTDIR}/dtb" else - echo "==> No DTB source directory found at ${DTB_SRC_DIR}; skipping DTB export" + log info "No DTB source directory found at ${DTB_SRC_DIR}; skipping DTB export" fi -echo "==> Final contents of root filesystem (up to depth 4, top 30):" +log info "Final contents of root filesystem (up to depth 4, top 30):" du -h -x -d 4 "$BUILDROOT" | sort -h | tail -n30 || true -echo "==> CaptainOS finalize complete." +log info "CaptainOS finalize complete." diff --git a/captain/flavors/common_debian/mkosi.postinst.sh.j2 b/captain/flavors/common_debian/mkosi.postinst.sh.j2 index bafdb9b..e090e19 100755 --- a/captain/flavors/common_debian/mkosi.postinst.sh.j2 +++ b/captain/flavors/common_debian/mkosi.postinst.sh.j2 @@ -5,45 +5,73 @@ # creating directories and dropping config files. set -euo pipefail -echo "==> CaptainOS post-install: dump env at $(hostname)..." +log info "CaptainOS post-install: dump env at $(hostname)..." env -echo "==> CaptainOS post-install: show mounts at $(hostname)..." +log info "CaptainOS post-install: show mounts at $(hostname)..." mount || true -echo "==> CaptainOS post-install: configuring services..." - # Ensure CA certificate bundle is generated (dpkg triggers may not # fire reliably in mkosi's chroot). +log info "Generating fresh CA certificate bundle..." update-ca-certificates --fresh 2>/dev/null || true +# @TODO: this is the wrong place to do any systemd enablement/disablement/masking: mkosi will +# after this still apply systemd "presets" which will change the game completely. +# This is better done in a finalize.chroot script which would run after that. +# alternatively: adopt/introduce systemd presets, but that's a can of worms. + +log info "Listing all systemd units..." +systemctl list-unit-files --no-pager || true + +log info "CaptainOS post-install: configuring services..." # Enable core services -for unit in systemd-networkd systemd-resolved systemd-timesyncd containerd captainos-banner \ - captainos-static-network tink-agent-setup tink-agent rsyslog rsyslog-hostname-reload.path; do - systemctl enable "$unit" 2>/dev/null || true +declare -a units_to_enable=( + systemd-networkd systemd-resolved systemd-timesyncd containerd captainos-banner + systemd-time-wait-sync # wait for time sync + captainos-static-network tink-agent-setup tink-agent rsyslog rsyslog-hostname-reload.path +) +declare unit +for unit in "${units_to_enable[@]}"; do + log info "Enabling systemd unit ${unit}..." + if systemctl enable "${unit}"; then + log info " ${unit} enabled successfully." + else + log warning " Failed to enable ${unit}, but continuing anyway..." + fi done # Root password is set via mkosi.conf RootPassword= setting # Disable unnecessary systemd units to speed up boot -for unit in \ - apt-daily.timer \ - apt-daily-upgrade.timer \ - e2scrub_all.timer \ - e2scrub_reap.service \ - fstrim.timer \ - logrotate.timer \ - man-db.timer \ - remote-fs.target \ - systemd-firstboot.service; do - systemctl disable "$unit" 2>/dev/null || true - systemctl mask "$unit" 2>/dev/null || true +declare -a units_to_disable=( + apt-daily.timer + apt-daily-upgrade.timer + e2scrub_all.timer + e2scrub_reap.service + fstrim.timer + logrotate.timer + man-db.timer + remote-fs.target + systemd-firstboot.service +) +for unit in "${units_to_disable[@]}"; do + log info "Disabling and masking systemd unit ${unit}..." + if systemctl disable "${unit}"; then + log info " ${unit} disabled successfully." + else + log warning " Failed to disable ${unit}, but continuing anyway..." + fi + + if systemctl mask "${unit}"; then + log info " ${unit} masked successfully." + else + log warning " Failed to mask ${unit}, but continuing anyway..." + fi done # Set default target to multi-user (no graphical) -systemctl set-default multi-user.target 2>/dev/null || true - -echo "==> List installed packages..." -dpkg -l --no-pager || true +log info "Setting default systemd target to multi-user.target (no graphical)..." +systemctl set-default multi-user.target || true -echo "==> CaptainOS post-install complete." +log info "CaptainOS post-install complete." From 78960db3e8a8dfb3cf93e8ec603b5fcae804d07c Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 11:59:18 +0200 Subject: [PATCH 33/93] mkosi.conf: output a JSON manifest Signed-off-by: Ricardo Pardini --- captain/flavors/common_debian/mkosi.conf.j2 | 1 + 1 file changed, 1 insertion(+) diff --git a/captain/flavors/common_debian/mkosi.conf.j2 b/captain/flavors/common_debian/mkosi.conf.j2 index 98a307e..e2be9f6 100644 --- a/captain/flavors/common_debian/mkosi.conf.j2 +++ b/captain/flavors/common_debian/mkosi.conf.j2 @@ -4,6 +4,7 @@ Release=trixie {{ flavor.extra_mkosi_conf_distribution() }} [Output] +ManifestFormat=json Format=cpio CompressOutput=zstd CompressLevel=19 From 29ee475e0b2a039b05374cf737ce51136683e9f6 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 11:59:43 +0200 Subject: [PATCH 34/93] mkosi.conf: force tools tree to be Debian Trixie - mkosi defaults to 'testing' Signed-off-by: Ricardo Pardini --- captain/flavors/common_debian/mkosi.conf.j2 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/captain/flavors/common_debian/mkosi.conf.j2 b/captain/flavors/common_debian/mkosi.conf.j2 index e2be9f6..fc6068b 100644 --- a/captain/flavors/common_debian/mkosi.conf.j2 +++ b/captain/flavors/common_debian/mkosi.conf.j2 @@ -13,6 +13,8 @@ OutputDirectory=mkosi.output [Build] Environment=TERM=dumb ToolsTree=yes +ToolsTreeDistribution=debian +ToolsTreeRelease=trixie Incremental=yes CacheDirectory=mkosi.cache From e68455485b2811fb0d9c242c11245663e062dcd5 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 12:00:02 +0200 Subject: [PATCH 35/93] mkosi.conf: use sandbox tree also for tools tree Signed-off-by: Ricardo Pardini --- captain/flavors/common_debian/mkosi.conf.j2 | 1 + 1 file changed, 1 insertion(+) diff --git a/captain/flavors/common_debian/mkosi.conf.j2 b/captain/flavors/common_debian/mkosi.conf.j2 index fc6068b..5383634 100644 --- a/captain/flavors/common_debian/mkosi.conf.j2 +++ b/captain/flavors/common_debian/mkosi.conf.j2 @@ -15,6 +15,7 @@ Environment=TERM=dumb ToolsTree=yes ToolsTreeDistribution=debian ToolsTreeRelease=trixie +ToolsTreeSandboxTrees=mkosi.sandbox Incremental=yes CacheDirectory=mkosi.cache From 1a660fdbc5e1df031ec171bea0a51a943b97afa8 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 12:04:47 +0200 Subject: [PATCH 36/93] flavor: ensure flavor supports requested architecture Signed-off-by: Ricardo Pardini --- captain/flavor.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/captain/flavor.py b/captain/flavor.py index f4e75df..c5fa538 100644 --- a/captain/flavor.py +++ b/captain/flavor.py @@ -158,6 +158,22 @@ def create_and_setup_flavor_for_id(flavor_id: str, cfg: Config) -> BaseFlavor: log.debug("Calling setup() on flavor %s with config: %s", flavor, cfg) flavor.setup(cfg, flavor_dir) + # Ensure the current arch is supported by the flavor + if cfg.arch_info.arch not in flavor.supported_architectures: + log.error( + "Flavor '%s' does not support architecture '%s'. Supported architectures: %s", + flavor.id, + cfg.arch_info.arch, + flavor.supported_architectures, + ) + raise SystemExit(1) + else: + log.debug( + "Flavor '%s' supports architecture '%s'", + flavor.id, + cfg.arch_info.arch, + ) + log.debug( "Flavor is setup; description: %s; supported_architectures: %s", flavor.description, From f5f124487c309e4c610514fa4e7b9b9a61425686 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 12:08:11 +0200 Subject: [PATCH 37/93] flavors: cleanup a bit Signed-off-by: Ricardo Pardini --- captain/flavors/common_armbian/__init__.py | 8 -------- captain/flavors/common_debian/__init__.py | 9 +-------- captain/flavors/common_debian/mkosi.conf.j2 | 1 - captain/flavors/trixie_full/__init__.py | 10 ---------- captain/flavors/trixie_rockchip64/__init__.py | 10 ---------- 5 files changed, 1 insertion(+), 37 deletions(-) diff --git a/captain/flavors/common_armbian/__init__.py b/captain/flavors/common_armbian/__init__.py index 70ecb4b..e79bdfd 100644 --- a/captain/flavors/common_armbian/__init__.py +++ b/captain/flavors/common_armbian/__init__.py @@ -16,11 +16,6 @@ class ArmbianCommonFlavor(DebianCommonFlavor): def setup(self, cfg: Config, flavor_dir: Path) -> None: super().setup(cfg, flavor_dir) - log.warning( - "ArmbianCommonFlavor setting up; mkosi arch: %s; flavor_dir: %s", - cfg.arch_info.mkosi_arch, - flavor_dir, - ) this_flavor_dir = self.specific_flavor_dir("common-armbian") # Now, lets enumerate and add all the static files this flavor's mkosi.sandbox directory @@ -31,6 +26,3 @@ def setup(self, cfg: Config, flavor_dir: Path) -> None: if extra_file.is_file(): relative_path = extra_file.relative_to(this_flavor_dir) self.static_map[str(relative_path)] = extra_file - - def extra_mkosi_conf_distribution(self) -> str: - return "# frak this" diff --git a/captain/flavors/common_debian/__init__.py b/captain/flavors/common_debian/__init__.py index f9c22d2..60c84dc 100644 --- a/captain/flavors/common_debian/__init__.py +++ b/captain/flavors/common_debian/__init__.py @@ -16,11 +16,7 @@ class DebianCommonFlavor(BaseFlavor): def setup(self, cfg: Config, flavor_dir: Path) -> None: super().setup(cfg, flavor_dir) - log.warning( - "DebianCommonFlavor setting up; mkosi arch: %s; flavor_dir: %s", - cfg.arch_info.mkosi_arch, - flavor_dir, - ) + this_flavor_dir = self.specific_flavor_dir("common-debian") self.template_map["mkosi.conf"] = [this_flavor_dir / "mkosi.conf.j2"] self.template_map["mkosi.postinst"] = [ @@ -58,6 +54,3 @@ def setup(self, cfg: Config, flavor_dir: Path) -> None: if extra_file.is_file(): relative_path = extra_file.relative_to(this_flavor_dir) self.static_map[str(relative_path)] = extra_file - - def extra_mkosi_conf_distribution(self) -> str: - return "" diff --git a/captain/flavors/common_debian/mkosi.conf.j2 b/captain/flavors/common_debian/mkosi.conf.j2 index 5383634..5913c5a 100644 --- a/captain/flavors/common_debian/mkosi.conf.j2 +++ b/captain/flavors/common_debian/mkosi.conf.j2 @@ -1,7 +1,6 @@ [Distribution] Distribution=debian Release=trixie -{{ flavor.extra_mkosi_conf_distribution() }} [Output] ManifestFormat=json diff --git a/captain/flavors/trixie_full/__init__.py b/captain/flavors/trixie_full/__init__.py index c3bcbf3..409ed1e 100644 --- a/captain/flavors/trixie_full/__init__.py +++ b/captain/flavors/trixie_full/__init__.py @@ -1,8 +1,6 @@ import logging from dataclasses import dataclass -from pathlib import Path -from captain.config import Config from captain.flavor import BaseFlavor from captain.flavors.common_debian import DebianCommonFlavor @@ -20,13 +18,5 @@ class TrixieFullFlavor(DebianCommonFlavor): description = "Debian Trixie based with linux-image-generic standard Debian kernel" supported_architectures = frozenset(["amd64", "arm64"]) - def setup(self, cfg: Config, flavor_dir: Path) -> None: - super().setup(cfg, flavor_dir) - log.warning( - "TrixieFullFlavor setting up; mkosi arch: %s; flavor_dir: %s", - cfg.arch_info.mkosi_arch, - flavor_dir, - ) - def kernel_packages(self) -> set[str]: return {"linux-image-generic"} diff --git a/captain/flavors/trixie_rockchip64/__init__.py b/captain/flavors/trixie_rockchip64/__init__.py index da08158..c49b5ab 100644 --- a/captain/flavors/trixie_rockchip64/__init__.py +++ b/captain/flavors/trixie_rockchip64/__init__.py @@ -1,8 +1,6 @@ import logging from dataclasses import dataclass -from pathlib import Path -from captain.config import Config from captain.flavor import BaseFlavor from captain.flavors.common_armbian import ArmbianCommonFlavor @@ -20,13 +18,5 @@ class TrixieRockchip64Flavor(ArmbianCommonFlavor): description = "Debian Trixie based with Armbian's rockchip64-edge kernel" supported_architectures = frozenset(["arm64"]) # does NOT support amd64 - def setup(self, cfg: Config, flavor_dir: Path) -> None: - super().setup(cfg, flavor_dir) - log.warning( - "TrixieRockchip64Flavor setting up; mkosi arch: %s; flavor_dir: %s", - cfg.arch_info.mkosi_arch, - flavor_dir, - ) - def kernel_packages(self) -> set[str]: return {"linux-image-edge-rockchip64"} From 8fcc91f874b39d76974f510588f03c7ae47a765a Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 12:10:06 +0200 Subject: [PATCH 38/93] flavors/gha: introduce meson64 flavor Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 1 + captain/flavors/trixie_meson64/__init__.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 captain/flavors/trixie_meson64/__init__.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4753b32..23315b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,6 +95,7 @@ jobs: - { arch: amd64, output_arch: x86_64, iso: true, FLAVOR_ID: "trixie-full" } - { arch: arm64, output_arch: aarch64, iso: true, FLAVOR_ID: "trixie-full" } - { arch: arm64, output_arch: aarch64, iso: false, FLAVOR_ID: "trixie-rockchip64" } + - { arch: arm64, output_arch: aarch64, iso: false, FLAVOR_ID: "trixie-meson64" } env: ARCH: ${{ matrix.arch }} MKOSI_MODE: docker diff --git a/captain/flavors/trixie_meson64/__init__.py b/captain/flavors/trixie_meson64/__init__.py new file mode 100644 index 0000000..f3dd09e --- /dev/null +++ b/captain/flavors/trixie_meson64/__init__.py @@ -0,0 +1,22 @@ +import logging +from dataclasses import dataclass + +from captain.flavor import BaseFlavor +from captain.flavors.common_armbian import ArmbianCommonFlavor + +log: logging.Logger = logging.getLogger(__name__) + + +def create_flavor() -> BaseFlavor: + return TrixieMeson64Flavor() + + +@dataclass +class TrixieMeson64Flavor(ArmbianCommonFlavor): + id = "trixie-meson64" + name = "Trixie for Meson (Amlogic) 64-bit ARM machines" + description = "Debian Trixie based with Armbian's meson64-edge kernel" + supported_architectures = frozenset(["arm64"]) # does NOT support amd64 + + def kernel_packages(self) -> set[str]: + return {"linux-image-edge-meson64"} From 253f18c37937d30f1d87649f48fb6611d395d6ba Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 12:23:03 +0200 Subject: [PATCH 39/93] docker: never interactive, never a terminal Signed-off-by: Ricardo Pardini --- captain/docker.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/captain/docker.py b/captain/docker.py index 2761ab1..5779f32 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -6,7 +6,6 @@ import logging import os import platform -import sys from pathlib import Path from captain.config import Config @@ -108,9 +107,6 @@ def run_in_release(cfg: Config, *extra_args: str) -> None: "--rm", # Buildah needs mount/remount capabilities for layer operations. "--privileged", - # interactive if running in a terminal - *(["-i"] if sys.stdout.isatty() and sys.stdin.isatty() else []), - "-t", # terminal "-v", f"{cfg.project_dir}:/work", "-w", @@ -153,9 +149,6 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: "run", "--rm", "--privileged", - # interactive if running in a terminal - *(["-i"] if sys.stdout.isatty() and sys.stdin.isatty() else []), - "-t", # terminal "-w", "/work", "-e", From a8f71ee976bb1713a66f6cf8fa9877abb5f403e5 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 12:40:32 +0200 Subject: [PATCH 40/93] cli/flavor: pass Flavor down to build and initramfs cmds; introduce BaseFlavor::has_iso() - so only the fully UEFI/ACPI flavor gets ISOs by default - they don't make sense for DeviceTree flavors Signed-off-by: Ricardo Pardini --- captain/cli/_commands.py | 14 ++++++++++---- captain/cli/_main.py | 6 +++--- captain/flavor.py | 3 +++ captain/flavors/trixie_full/__init__.py | 4 ++++ 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/captain/cli/_commands.py b/captain/cli/_commands.py index 54e9df1..6d31918 100644 --- a/captain/cli/_commands.py +++ b/captain/cli/_commands.py @@ -9,6 +9,7 @@ from captain import artifacts, docker, qemu from captain.config import Config +from captain.flavor import BaseFlavor from captain.util import run from ._stages import ( @@ -26,7 +27,7 @@ def _cmd_tools(cfg: Config, _extra_args: list[str]) -> None: log.info("Tools stage complete!") -def _cmd_initramfs(cfg: Config, extra_args: list[str]) -> None: +def _cmd_initramfs(cfg: Config, flavor: BaseFlavor, extra_args: list[str]) -> None: """Build only the initramfs via mkosi, then collect artifacts.""" _build_mkosi_stage(cfg, extra_args) artifacts.collect_initramfs(cfg) @@ -42,11 +43,16 @@ def _cmd_iso(cfg: Config, _extra_args: list[str]) -> None: log.info("ISO build complete!") -def _cmd_build(cfg: Config, extra_args: list[str]) -> None: +def _cmd_build(cfg: Config, flavor: BaseFlavor, extra_args: list[str]) -> None: """Full build: tools → initramfs → iso → artifacts.""" _build_tools_stage(cfg) - _cmd_initramfs(cfg, extra_args) # delegate, so it also collects - _build_iso_stage(cfg) # TODO also conditional... / and/or include dtb's for arm64 + _cmd_initramfs(cfg, flavor, extra_args) # delegate, so it also collects + + if flavor.has_iso(): + _build_iso_stage(cfg) + else: + log.info("Flavor '%s' does not have an ISO stage; skipping.", flavor.id) + artifacts.collect(cfg) log.info("Build complete!") diff --git a/captain/cli/_main.py b/captain/cli/_main.py index f21a3ad..08702fa 100644 --- a/captain/cli/_main.py +++ b/captain/cli/_main.py @@ -22,9 +22,9 @@ import captain.flavor from captain import docker from captain.config import Config +from captain.flavor import BaseFlavor from captain.util import run -from ..flavor import BaseFlavor from ._commands import ( _cmd_build, _cmd_checksums, @@ -108,8 +108,8 @@ def main(project_dir: Path | None = None) -> None: # generate the flavor first so the files are in place. if command in ("build", "initramfs"): flavor.generate() - - if command in ("qemu-test", "checksums", "release", "clean"): + handler(cfg, flavor, extra) # type: ignore[operator] + elif command in ("qemu-test", "checksums", "release", "clean"): handler(cfg, extra, args=args) # type: ignore[operator] else: handler(cfg, extra) # type: ignore[operator] diff --git a/captain/flavor.py b/captain/flavor.py index c5fa538..c79ee2d 100644 --- a/captain/flavor.py +++ b/captain/flavor.py @@ -111,6 +111,9 @@ def copy_static_files(self, project_dir): destination_path.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(source_path, destination_path) + def has_iso(self) -> bool: + return False + def create_and_setup_flavor_for_id(flavor_id: str, cfg: Config) -> BaseFlavor: log.debug("Creating and setting up flavor for id '%s'", flavor_id) diff --git a/captain/flavors/trixie_full/__init__.py b/captain/flavors/trixie_full/__init__.py index 409ed1e..dd28880 100644 --- a/captain/flavors/trixie_full/__init__.py +++ b/captain/flavors/trixie_full/__init__.py @@ -20,3 +20,7 @@ class TrixieFullFlavor(DebianCommonFlavor): def kernel_packages(self) -> set[str]: return {"linux-image-generic"} + + # This flavor can produce working ISO images (generic UEFI/ACPI) + def has_iso(self) -> bool: + return True From 4d1bee88a3d0ff942c8aa2369328427d233da44f Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 16:04:33 +0200 Subject: [PATCH 41/93] gha/docker: set and pass down FORCE_COLOR=1 Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 1 + captain/docker.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23315b1..428a71e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ permissions: env: DEFAULT_FLAVOR_ID: "trixie-full" + FORCE_COLOR: "1" jobs: # ------------------------------------------------------------------- diff --git a/captain/docker.py b/captain/docker.py index 5779f32..a51607f 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -124,6 +124,8 @@ def run_in_release(cfg: Config, *extra_args: str) -> None: "-e", f"TERM={os.environ.get('TERM', 'xterm-256color')}", "-e", + "FORCE_COLOR=1", + "-e", f"COLUMNS={os.environ.get('COLUMNS', '200')}", "-e", f"GITHUB_ACTIONS={os.environ.get('GITHUB_ACTIONS', '')}", @@ -172,6 +174,8 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: "-e", f"TERM={os.environ.get('TERM', 'xterm-256color')}", "-e", + "FORCE_COLOR=1", + "-e", f"COLUMNS={os.environ.get('COLUMNS', '200')}", "-e", f"GITHUB_ACTIONS={os.environ.get('GITHUB_ACTIONS', '')}", From f61defb5a79c92bb3c9ad0404ab3b4b3c2ea861f Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 16:38:11 +0200 Subject: [PATCH 42/93] captain: don't shorten out logging record name Signed-off-by: Ricardo Pardini --- captain/__init__.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/captain/__init__.py b/captain/__init__.py index 03cc81e..4cdd54e 100644 --- a/captain/__init__.py +++ b/captain/__init__.py @@ -29,11 +29,10 @@ class _StageFormatter(logging.Formatter): def format(self, record: logging.LogRecord) -> str: name = record.name - stage = name.split(".", 1)[1] if name.startswith("captain.") else name - record.__dict__["stage"] = stage + record.__dict__["stage"] = name if os.environ.get("CAPTAIN_IN_DOCKER", "") == "docker": - # Running on host: show stage names in green for visual clarity. - record.__dict__["stage"] = f"[bold][blue]in-docker[/bold]: [/blue]{stage}" + # Running on host: show stage names in blue for visual clarity. + record.__dict__["stage"] = f"[bold][blue]in-docker[/bold]: [/blue]{name}" return super().format(record) @@ -46,7 +45,7 @@ def format(self, record: logging.LogRecord) -> str: show_time=False, show_level=True, show_path=True, - markup=True, + markup=True, # interprets [braket]stuff[/bracket] in log messages, beware rich_tracebacks=True, tracebacks_show_locals=True, ) From 36504b23e6eaa4bdd9619f23c5923a1a29b0921a Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 17:15:32 +0200 Subject: [PATCH 43/93] buildah: take env BUILDAH_INSECURE=1 for `--tls-verify=false` - so one can use an HTTP local `registry:2` for testing pushes Signed-off-by: Ricardo Pardini --- captain/buildah.py | 13 ++++++++++++- captain/docker.py | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/captain/buildah.py b/captain/buildah.py index cbc3110..e595069 100644 --- a/captain/buildah.py +++ b/captain/buildah.py @@ -13,6 +13,7 @@ from __future__ import annotations import logging +import os from pathlib import Path from captain.util import run @@ -119,7 +120,17 @@ def manifest_push( dest: str, ) -> None: log.info("buildah manifest push → %s", dest) - run(["buildah", "manifest", "push", "--all", manifest, f"docker://{dest}"]) + run( + [ + "buildah", + "manifest", + "push", + *(("--tls-verify=false",) if os.environ.get("BUILDAH_INSECURE") == "1" else ()), + "--all", + manifest, + f"docker://{dest}", + ] + ) def rmi( diff --git a/captain/docker.py b/captain/docker.py index a51607f..4a021d5 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -122,6 +122,8 @@ def run_in_release(cfg: Config, *extra_args: str) -> None: "-e", "BUILDAH_ISOLATION=chroot", "-e", + f"BUILDAH_INSECURE={os.environ.get('BUILDAH_INSECURE', '')}", + "-e", f"TERM={os.environ.get('TERM', 'xterm-256color')}", "-e", "FORCE_COLOR=1", From 49c77fd5b3cb455b63b6c492d0a2693a0b7e2ed1 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 17:15:52 +0200 Subject: [PATCH 44/93] release: pass flavor down to release, avoid releasing iso for !has_iso Signed-off-by: Ricardo Pardini --- captain/cli/_main.py | 6 ++++-- captain/cli/_release.py | 6 +++++- captain/oci/_build.py | 6 +++++- captain/oci/_publish.py | 7 +++---- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/captain/cli/_main.py b/captain/cli/_main.py index 08702fa..a2aecfc 100644 --- a/captain/cli/_main.py +++ b/captain/cli/_main.py @@ -106,10 +106,12 @@ def main(project_dir: Path | None = None) -> None: if handler is not None: # If building, or doing initramfs (mkosi), # generate the flavor first so the files are in place. - if command in ("build", "initramfs"): + if command in ("build", "initramfs", "publish"): flavor.generate() handler(cfg, flavor, extra) # type: ignore[operator] - elif command in ("qemu-test", "checksums", "release", "clean"): + elif command in ("release"): + handler(cfg, flavor, extra, args=args) # type: ignore[operator] + elif command in ("qemu-test", "checksums", "clean"): handler(cfg, extra, args=args) # type: ignore[operator] else: handler(cfg, extra) # type: ignore[operator] diff --git a/captain/cli/_release.py b/captain/cli/_release.py index 5b6fa49..4ed0c7d 100644 --- a/captain/cli/_release.py +++ b/captain/cli/_release.py @@ -11,6 +11,7 @@ from captain import docker, oci from captain.config import Config +from captain.flavor import BaseFlavor from captain.util import check_release_dependencies from ._parser import ( @@ -85,7 +86,9 @@ def _resolve_git_sha(args: object, project_dir: Path) -> str: return result.stdout.strip() -def _cmd_release(cfg: Config, extra_args: list[str], args: object = None) -> None: +def _cmd_release( + cfg: Config, flavor: BaseFlavor, extra_args: list[str], args: object = None +) -> None: """OCI artifact operations: publish, pull, tag.""" # Peel the release subcommand from extra_args. @@ -202,6 +205,7 @@ def _cmd_release(cfg: Config, extra_args: list[str], args: object = None) -> Non force = getattr(args, "force", False) oci.publish( cfg, + flavor, target=target, registry=registry, repository=repository, diff --git a/captain/oci/_build.py b/captain/oci/_build.py index 0f39b69..ac846d6 100644 --- a/captain/oci/_build.py +++ b/captain/oci/_build.py @@ -40,6 +40,7 @@ def _collect_arch_artifacts( out: Path, arch: str, flavor_id: str, + has_iso: bool, ) -> list[Path]: """Collect and return the artifact files for a single architecture. @@ -50,8 +51,11 @@ def _collect_arch_artifacts( arch_files = [ out / f"vmlinuz-{flavor_id}-{oarch}", out / f"initramfs-{flavor_id}-{oarch}", - out / f"captainos-{flavor_id}-{oarch}.iso", ] + + if has_iso: + arch_files += [out / f"captainos-{flavor_id}-{oarch}.iso"] + checksums_path = out / f"sha256sums-{flavor_id}-{oarch}.txt" artifacts.collect_checksums(arch_files, checksums_path) diff --git a/captain/oci/_publish.py b/captain/oci/_publish.py index e757eda..eebf520 100644 --- a/captain/oci/_publish.py +++ b/captain/oci/_publish.py @@ -11,6 +11,7 @@ from captain import buildah, skopeo from captain.config import Config +from captain.flavor import BaseFlavor from captain.util import ensure_dir, get_arch_info from ._build import _build_platform_image, _collect_arch_artifacts, _deterministic_tar @@ -152,6 +153,7 @@ def _publish_combined( def publish( cfg: Config, + flavor: BaseFlavor, *, target: str, registry: str, @@ -191,10 +193,7 @@ def publish( arch_files: dict[str, list[Path]] = {} for arch in arches: arch_files[arch] = _collect_arch_artifacts( - cfg.project_dir, - out, - arch, - cfg.flavor_id, + cfg.project_dir, out, arch, cfg.flavor_id, has_iso=flavor.has_iso() ) # Create deterministic layer tars (shared across manifest pushes). From 67f3ae6c16ab6e853cf7cea143681971c17e26f4 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 20:23:08 +0200 Subject: [PATCH 45/93] cli: new cli under `click_cli` - add to pyproject.toml, thus `uv run captain ...` Signed-off-by: Ricardo Pardini --- captain/click_cli/__init__.py | 20 +++ captain/click_cli/_build.py | 169 ++++++++++++++++++ captain/click_cli/_builder.py | 130 ++++++++++++++ captain/click_cli/_main.py | 119 +++++++++++++ captain/click_cli/_release_publish.py | 243 ++++++++++++++++++++++++++ click_cli.py | 32 ++++ pyproject.toml | 8 + 7 files changed, 721 insertions(+) create mode 100644 captain/click_cli/__init__.py create mode 100644 captain/click_cli/_build.py create mode 100644 captain/click_cli/_builder.py create mode 100644 captain/click_cli/_main.py create mode 100644 captain/click_cli/_release_publish.py create mode 100644 click_cli.py diff --git a/captain/click_cli/__init__.py b/captain/click_cli/__init__.py new file mode 100644 index 0000000..b8b35d1 --- /dev/null +++ b/captain/click_cli/__init__.py @@ -0,0 +1,20 @@ +"""Click-based CLI for CaptainOS. + +Provides the ``captain`` console script with subcommands: + +- ``builder`` — build the Docker builder image, optionally push it +- ``build`` — full build pipeline (tools → initramfs → iso → artifacts) +- ``release-publish`` — publish artifacts as OCI images via buildah + +Shell completion is available for bash and zsh:: + + # bash + eval "$(_CAPTAIN_COMPLETE=bash_source captain)" + + # zsh + eval "$(_CAPTAIN_COMPLETE=zsh_source captain)" +""" + +from captain.click_cli._main import main + +__all__ = ["main"] diff --git a/captain/click_cli/_build.py b/captain/click_cli/_build.py new file mode 100644 index 0000000..8d23edc --- /dev/null +++ b/captain/click_cli/_build.py @@ -0,0 +1,169 @@ +"""``captain build`` — full build pipeline (tools → initramfs → iso → artifacts).""" + +from __future__ import annotations + +import logging + +import click + +import captain.flavor +from captain.click_cli._main import cli, common_options, resolve_project_dir +from captain.config import Config + +log = logging.getLogger(__name__) + + +@cli.command( + "build", + short_help="Run the full build pipeline via mkosi.", +) +@common_options +@click.option( + "--builder-image", + envvar="BUILDER_IMAGE", + default="captainos-builder", + show_default=True, + help="Docker builder image name.", +) +@click.option( + "--no-cache", + envvar="NO_CACHE", + is_flag=True, + default=False, + help="Rebuild the builder image without Docker layer cache.", +) +@click.option( + "--mkosi-mode", + envvar="MKOSI_MODE", + default="docker", + show_default=True, + type=click.Choice(["docker", "native", "skip"], case_sensitive=False), + metavar="MODE", + help="Mkosi stage execution mode (docker, native, skip).", +) +@click.option( + "--tools-mode", + envvar="TOOLS_MODE", + default="docker", + show_default=True, + type=click.Choice(["docker", "native", "skip"], case_sensitive=False), + metavar="MODE", + help="Tools download stage execution mode (docker, native, skip).", +) +@click.option( + "--iso-mode", + envvar="ISO_MODE", + default="docker", + show_default=True, + type=click.Choice(["docker", "native", "skip"], case_sensitive=False), + metavar="MODE", + help="ISO build stage execution mode (docker, native, skip).", +) +@click.option( + "--force", + "force_mkosi", + is_flag=True, + default=False, + help="Pass --force through to mkosi.", +) +@click.option( + "--force-tools", + envvar="FORCE_TOOLS", + is_flag=True, + default=False, + help="Re-download tools even if outputs already exist.", +) +@click.option( + "--force-iso", + envvar="FORCE_ISO", + is_flag=True, + default=False, + help="Force ISO rebuild even if outputs already exist.", +) +def build_cmd( + *, + arch: str, + flavor_id: str, + project_dir: str | None, + verbose: bool, + builder_image: str, + no_cache: bool, + mkosi_mode: str, + tools_mode: str, + iso_mode: str, + force_mkosi: bool, + force_tools: bool, + force_iso: bool, +) -> None: + """Run the full CaptainOS build pipeline. + + Stages executed in order: tools → initramfs (mkosi) → ISO → artifact + collection. Each stage can be independently set to run inside Docker, + natively on the host, or skipped entirely. + + \b + Examples + -------- + captain build + captain build --arch arm64 + captain build --flavor-id trixie-meson64 --arch arm64 + captain build --mkosi-mode native --tools-mode native + captain build --force --force-tools + """ + _configure_logging(verbose) + + proj = resolve_project_dir(project_dir) + + cfg = Config( + project_dir=proj, + output_dir=proj / "out", + arch=arch, + flavor_id=flavor_id, + builder_image=builder_image, + no_cache=no_cache, + tools_mode=tools_mode, + mkosi_mode=mkosi_mode, + iso_mode=iso_mode, + force_tools=force_tools, + force_iso=force_iso, + ) + + if force_mkosi: + cfg.mkosi_args = ["--force"] + + # Instantiate and generate the flavor. + flavor = captain.flavor.create_and_setup_flavor_for_id(cfg.flavor_id, cfg) + flavor.generate() + + # Import build commands (reuse existing stage orchestration). + from captain import artifacts + from captain.cli._stages import ( + _build_iso_stage, + _build_mkosi_stage, + _build_tools_stage, + ) + + # Tools stage. + _build_tools_stage(cfg) + + # Initramfs (mkosi) stage + artifact collection. + _build_mkosi_stage(cfg, list(cfg.mkosi_args)) + artifacts.collect_initramfs(cfg) + artifacts.collect_kernel(cfg) + artifacts.collect_dtbs(cfg) + log.info("Initramfs build complete.") + + # ISO stage (if the flavor supports it). + if flavor.has_iso(): + _build_iso_stage(cfg) + else: + log.info("Flavor '%s' does not produce an ISO — skipping.", flavor.id) + + # Final artifact collection. + artifacts.collect(cfg) + log.info("Build complete!") + + +def _configure_logging(verbose: bool) -> None: + level = logging.DEBUG if verbose else logging.INFO + logging.getLogger("captain").setLevel(level) diff --git a/captain/click_cli/_builder.py b/captain/click_cli/_builder.py new file mode 100644 index 0000000..db167b6 --- /dev/null +++ b/captain/click_cli/_builder.py @@ -0,0 +1,130 @@ +"""``captain builder`` — build (and optionally push) the Docker builder image.""" + +from __future__ import annotations + +import logging + +import click + +from captain.click_cli._main import cli, common_options, resolve_project_dir +from captain.config import Config +from captain.docker import build_builder + +log = logging.getLogger(__name__) + + +@cli.command( + "builder", + short_help="Build the Docker builder image, optionally push it.", +) +@common_options +@click.option( + "--builder-image", + envvar="BUILDER_IMAGE", + default="captainos-builder", + show_default=True, + help="Docker builder image name.", +) +@click.option( + "--no-cache", + envvar="NO_CACHE", + is_flag=True, + default=False, + help="Rebuild the builder image without Docker layer cache.", +) +@click.option( + "--push", + is_flag=True, + default=False, + help="Push the built image to a registry after building.", +) +@click.option( + "--registry", + envvar="REGISTRY", + default=None, + help="Registry hostname (e.g. ghcr.io). Required when --push is set.", +) +@click.option( + "--registry-path", + envvar="REGISTRY_PATH", + default=None, + help="Repository path within the registry (e.g. tinkerbell/captain/builder).", +) +@click.option( + "--tag", + "push_tag", + default=None, + help="Tag to apply when pushing (default: Dockerfile content hash).", +) +def builder_cmd( + *, + arch: str, + flavor_id: str, + project_dir: str | None, + verbose: bool, + builder_image: str, + no_cache: bool, + push: bool, + registry: str | None, + registry_path: str | None, + push_tag: str | None, +) -> None: + """Build the Docker builder image used by other build stages. + + By default the image is built locally only. Pass --push together + with --registry and --registry-path to also push the image to a + remote container registry. + + \b + Examples + -------- + captain builder + captain builder --no-cache + captain builder --push --registry ghcr.io --registry-path tinkerbell/captain/builder + captain builder --push --registry ghcr.io --registry-path tinkerbell/captain/builder --tag lt + """ + _configure_logging(verbose) + + proj = resolve_project_dir(project_dir) + + cfg = Config( + project_dir=proj, + output_dir=proj / "out", + arch=arch, + flavor_id=flavor_id, + builder_image=builder_image, + no_cache=no_cache, + ) + + # 1. Build the image. + build_builder(cfg) + log.info("Builder image '%s' is ready.", cfg.builder_image) + + # 2. Optionally push. + if push: + if not registry or not registry_path: + raise click.UsageError( + "--registry and --registry-path are required when --push is set." + ) + import hashlib + + from captain.util import run + + if push_tag is None: + # Use the Dockerfile content hash (same logic as docker.py) + dockerfile = cfg.project_dir / "Dockerfile" + push_tag = hashlib.sha256(dockerfile.read_bytes()).hexdigest() + + full_ref = f"{registry}/{registry_path}:{push_tag}" + log.info("Pushing builder image → %s", full_ref) + + # Tag and push. + run(["docker", "tag", cfg.builder_image, full_ref]) + run(["docker", "push", full_ref]) + log.info("Push complete: %s", full_ref) + + +def _configure_logging(verbose: bool) -> None: + """Set the captain logger level based on the --verbose flag.""" + level = logging.DEBUG if verbose else logging.INFO + logging.getLogger("captain").setLevel(level) diff --git a/captain/click_cli/_main.py b/captain/click_cli/_main.py new file mode 100644 index 0000000..fd2c330 --- /dev/null +++ b/captain/click_cli/_main.py @@ -0,0 +1,119 @@ +"""Click CLI — main group with shared options and subcommand registration.""" + +from __future__ import annotations + +import functools +import logging +import sys +from pathlib import Path +from typing import Any + +import click + +from captain.config import DEFAULT_FLAVOR_ID + +log = logging.getLogger(__name__) + + +# --------------------------------------------------------------------------- +# Shared option decorators +# --------------------------------------------------------------------------- + + +def common_options(fn: Any) -> Any: + """Decorate a click command with options shared by every subcommand.""" + + @click.option( + "--arch", + envvar="ARCH", + default="amd64", + show_default=True, + type=click.Choice(["amd64", "arm64"], case_sensitive=False), + metavar="ARCH", + help="Target architecture (amd64, arm64).", + ) + @click.option( + "--flavor-id", + envvar="FLAVOR_ID", + default=DEFAULT_FLAVOR_ID, + show_default=True, + help="Flavor (kernel/board config) to build.", + ) + @click.option( + "--project-dir", + envvar="CAPTAIN_PROJECT_DIR", + default=None, + type=click.Path(exists=True, file_okay=False, resolve_path=True), + help="Project root directory (auto-detected when omitted).", + ) + @click.option( + "-v", + "--verbose", + is_flag=True, + default=False, + help="Enable debug-level logging.", + ) + @functools.wraps(fn) + def wrapper(**kwargs: Any) -> Any: + return fn(**kwargs) + + return wrapper + + +# --------------------------------------------------------------------------- +# Resolve project directory +# --------------------------------------------------------------------------- + + +def resolve_project_dir(project_dir: str | None) -> Path: + """Return an absolute ``Path`` for the project root.""" + if project_dir is not None: + return Path(project_dir) + # Walk upward from this file until we find pyproject.toml. + candidate = Path(__file__).resolve().parent.parent.parent + if (candidate / "pyproject.toml").is_file(): + return candidate + click.echo("Error: cannot auto-detect project directory. Pass --project-dir.", err=True) + sys.exit(1) + + +# --------------------------------------------------------------------------- +# Top-level Click group +# --------------------------------------------------------------------------- + +CONTEXT_SETTINGS = dict( + help_option_names=["-h", "--help"], + max_content_width=120, +) + + +@click.group( + context_settings=CONTEXT_SETTINGS, + invoke_without_command=True, + help=( + "CaptainOS build system.\n\n" + "Run 'captain COMMAND --help' for details on each subcommand.\n\n" + "Shell completion (bash/zsh):\n\n" + ' eval "$(_CAPTAIN_COMPLETE=bash_source captain)" # bash\n\n' + ' eval "$(_CAPTAIN_COMPLETE=zsh_source captain)" # zsh' + ), +) +@click.version_option(package_name="captain") +@click.pass_context +def cli(ctx: click.Context) -> None: + """CaptainOS build system — click CLI.""" + if ctx.invoked_subcommand is None: + click.echo(ctx.get_help()) + + +# --------------------------------------------------------------------------- +# Register subcommands (imported lazily to avoid circular imports) +# --------------------------------------------------------------------------- + + +def main() -> None: + """Console-script entry point.""" + # Import subcommand modules to register them on the group. + from captain.click_cli import _build, _builder, _release_publish # noqa: F401 + + cli() diff --git a/captain/click_cli/_release_publish.py b/captain/click_cli/_release_publish.py new file mode 100644 index 0000000..3a85fb9 --- /dev/null +++ b/captain/click_cli/_release_publish.py @@ -0,0 +1,243 @@ +"""``captain release-publish`` — publish artifacts as OCI images via buildah.""" + +from __future__ import annotations + +import logging +import subprocess +from pathlib import Path + +import click + +import captain.flavor +from captain import oci +from captain.click_cli._main import cli, common_options, resolve_project_dir +from captain.config import Config +from captain.util import check_release_dependencies + +log = logging.getLogger(__name__) + + +@cli.command( + "release-publish", + short_help="Publish build artifacts as a multi-arch OCI image.", +) +@common_options +@click.option( + "--builder-image", + envvar="BUILDER_IMAGE", + default="captainos-builder", + show_default=True, + help="Docker builder image name.", +) +@click.option( + "--no-cache", + envvar="NO_CACHE", + is_flag=True, + default=False, + help="Rebuild images without Docker layer cache.", +) +@click.option( + "--release-mode", + envvar="RELEASE_MODE", + default="native", + show_default=True, + type=click.Choice(["docker", "native", "skip"], case_sensitive=False), + metavar="MODE", + help="Release stage execution mode (docker, native, skip).", +) +@click.option( + "--registry", + envvar="REGISTRY", + default="ghcr.io", + show_default=True, + help="OCI registry hostname.", +) +@click.option( + "--repository", + envvar="GITHUB_REPOSITORY", + default="tinkerbell/captain", + show_default=True, + help="Repository path (owner/name).", +) +@click.option( + "--oci-artifact-name", + envvar="OCI_ARTIFACT_NAME", + default="artifacts", + show_default=True, + help="OCI artifact image name.", +) +@click.option( + "--target", + envvar="TARGET", + default=None, + type=click.Choice(["amd64", "arm64", "combined"], case_sensitive=False), + metavar="TARGET", + help="Artifact target: amd64, arm64, or combined (default: value of --arch).", +) +@click.option( + "--git-sha", + envvar="GITHUB_SHA", + default=None, + help="Git commit SHA (default: auto-detected via git rev-parse HEAD).", +) +@click.option( + "--version-exclude", + envvar="VERSION_EXCLUDE", + default=None, + help="Tag to exclude from git-describe version lookup.", +) +@click.option( + "--force", + "force", + is_flag=True, + default=False, + help="Publish even if the image already exists in the registry.", +) +def release_publish_cmd( + *, + arch: str, + flavor_id: str, + project_dir: str | None, + verbose: bool, + builder_image: str, + no_cache: bool, + release_mode: str, + registry: str, + repository: str, + oci_artifact_name: str, + target: str | None, + git_sha: str | None, + version_exclude: str | None, + force: bool, +) -> None: + """Publish build artifacts as a multi-arch OCI image to a registry. + + Uses buildah to construct OCI images from the build artifacts (kernel, + initramfs, ISO, DTBs) and pushes them to the specified registry. + + Each artifact file becomes its own layer. Deterministic tar generation + ensures byte-identical layers across runs so that registries can + deduplicate blobs. + + \b + Examples + -------- + captain release-publish + captain release-publish --arch arm64 --target arm64 + captain release-publish --target combined --force + captain release-publish --registry ghcr.io --repository tinkerbell/captain + """ + _configure_logging(verbose) + + proj = resolve_project_dir(project_dir) + + if target is None: + target = arch + + cfg = Config( + project_dir=proj, + output_dir=proj / "out", + arch=arch, + flavor_id=flavor_id, + builder_image=builder_image, + no_cache=no_cache, + release_mode=release_mode, + ) + + # --- skip mode -------------------------------------------------------- + if cfg.release_mode == "skip": + log.info("RELEASE_MODE=skip — nothing to do.") + return + + # --- docker mode ------------------------------------------------------ + if cfg.release_mode == "docker": + from captain import docker + + docker.build_release_image(cfg) + sha = _resolve_git_sha(git_sha, proj) + + env_args: list[str] = [ + "-e", + f"FLAVOR_ID={cfg.flavor_id}", + "-e", + f"REGISTRY={registry}", + "-e", + f"GITHUB_REPOSITORY={repository}", + "-e", + f"OCI_ARTIFACT_NAME={oci_artifact_name}", + "-e", + f"GITHUB_SHA={sha}", + "-e", + f"TARGET={target}", + ] + if version_exclude: + env_args += ["-e", f"VERSION_EXCLUDE={version_exclude}"] + if force: + env_args += ["-e", "FORCE=true"] + + inner_cmd = ["/work/build.py", "release", "publish"] + + try: + docker.run_in_release( + cfg, + *env_args, + "--entrypoint", + "/usr/bin/uv", + docker.RELEASE_IMAGE, + *(["--verbose"] if log.isEnabledFor(logging.DEBUG) else []), + "run", + *inner_cmd, + ) + except subprocess.CalledProcessError as exc: + raise SystemExit(exc.returncode) from None + docker.fix_docker_ownership(cfg, ["/work/out"]) + return + + # --- native mode ------------------------------------------------------ + missing = check_release_dependencies() + if missing: + log.error("Missing release tools: %s", ", ".join(missing)) + log.error("Install them or set --release-mode=docker.") + raise SystemExit(1) + + sha = _resolve_git_sha(git_sha, proj) + tag = oci.compute_version_tag(proj, sha, exclude=version_exclude) + tag = f"{tag}-{cfg.flavor_id}" + + flavor = captain.flavor.create_and_setup_flavor_for_id(cfg.flavor_id, cfg) + + oci.publish( + cfg, + flavor, + target=target, + registry=registry, + repository=repository, + artifact_name=oci_artifact_name, + tag=tag, + sha=sha, + force=force, + ) + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _resolve_git_sha(sha: str | None, project_dir: Path) -> str: + """Return the provided SHA or auto-detect via ``git rev-parse HEAD``.""" + if sha: + return sha + result = subprocess.run( + ["git", "rev-parse", "HEAD"], + capture_output=True, + text=True, + check=True, + cwd=project_dir, + ) + return result.stdout.strip() + + +def _configure_logging(verbose: bool) -> None: + level = logging.DEBUG if verbose else logging.INFO + logging.getLogger("captain").setLevel(level) diff --git a/click_cli.py b/click_cli.py new file mode 100644 index 0000000..e0147bc --- /dev/null +++ b/click_cli.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +"""CaptainOS build system — click-based CLI entry point. + +Requires: Python >= 3.13, click, Rich, Docker (unless all stages use native/skip). +Use Astral's ``uv`` to run:: + + uv run click_cli.py --help + uv run click_cli.py builder + uv run click_cli.py build --arch arm64 + uv run click_cli.py release-publish --target combined + +Or, after ``uv pip install -e .``:: + + captain --help +""" + +import sys + +if sys.version_info < (3, 13): + print("ERROR: Python >= 3.13 is required.", file=sys.stderr) + sys.exit(1) + +try: + from captain.click_cli import main +except ImportError as exc: + print(f"ERROR: {exc}", file=sys.stderr) + uv_url = "https://docs.astral.sh/uv/getting-started/installation/" + print(f"Missing dependencies, use uv to run. See {uv_url}", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index c518bca..e98295b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,21 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + [project] version = "0.0.1" name = "captain" requires-python = ">=3.13" dependencies = [ + "click>=8.1", "configargparse>=1.7", "rich>=14.3.3", "jinja2>=3.1.6" ] +[project.scripts] +captain = "captain.click_cli:main" + [project.optional-dependencies] dev = ["ruff>=0.15.9", "pyright>=1.1"] From 7c85989dd76629a602f7f66a5a8f29dbebad018c Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 6 Apr 2026 20:45:06 +0200 Subject: [PATCH 46/93] click: add tools command - using captain.cli._stages Signed-off-by: Ricardo Pardini --- captain/click_cli/_main.py | 2 +- captain/click_cli/_tools.py | 98 +++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 captain/click_cli/_tools.py diff --git a/captain/click_cli/_main.py b/captain/click_cli/_main.py index fd2c330..da9bbf4 100644 --- a/captain/click_cli/_main.py +++ b/captain/click_cli/_main.py @@ -114,6 +114,6 @@ def cli(ctx: click.Context) -> None: def main() -> None: """Console-script entry point.""" # Import subcommand modules to register them on the group. - from captain.click_cli import _build, _builder, _release_publish # noqa: F401 + from captain.click_cli import _build, _builder, _release_publish, _tools # noqa: F401 cli() diff --git a/captain/click_cli/_tools.py b/captain/click_cli/_tools.py new file mode 100644 index 0000000..c3267d2 --- /dev/null +++ b/captain/click_cli/_tools.py @@ -0,0 +1,98 @@ +"""``captain tools`` — download tools (containerd, runc, nerdctl, CNI plugins).""" + +from __future__ import annotations + +import logging + +import click + +from captain.click_cli._main import cli, common_options, resolve_project_dir +from captain.config import Config + +log = logging.getLogger(__name__) + + +@cli.command( + "tools", + short_help="Download tools (containerd, runc, nerdctl, CNI).", +) +@common_options +@click.option( + "--builder-image", + envvar="BUILDER_IMAGE", + default="captainos-builder", + show_default=True, + help="Docker builder image name.", +) +@click.option( + "--no-cache", + envvar="NO_CACHE", + is_flag=True, + default=False, + help="Rebuild the builder image without Docker layer cache.", +) +@click.option( + "--tools-mode", + envvar="TOOLS_MODE", + default="docker", + show_default=True, + type=click.Choice(["docker", "native", "skip"], case_sensitive=False), + metavar="MODE", + help="Tools download stage execution mode (docker, native, skip).", +) +@click.option( + "--force-tools", + envvar="FORCE_TOOLS", + is_flag=True, + default=False, + help="Re-download tools even if outputs already exist.", +) +def tools_cmd( + *, + arch: str, + flavor_id: str, + project_dir: str | None, + verbose: bool, + builder_image: str, + no_cache: bool, + tools_mode: str, + force_tools: bool, +) -> None: + """Download tools (containerd, runc, nerdctl, CNI plugins). + + Fetches pre-built binaries for the target architecture and stages + them under ``mkosi.output/tools/{arch}/``. The tools are later + merged into the initramfs by mkosi via ``--extra-tree``. + + \b + Examples + -------- + captain tools + captain tools --arch arm64 + captain tools --tools-mode native + captain tools --force-tools + """ + _configure_logging(verbose) + + proj = resolve_project_dir(project_dir) + + cfg = Config( + project_dir=proj, + output_dir=proj / "out", + arch=arch, + flavor_id=flavor_id, + builder_image=builder_image, + no_cache=no_cache, + tools_mode=tools_mode, + force_tools=force_tools, + ) + + from captain.cli._stages import _build_tools_stage + + _build_tools_stage(cfg) + log.info("Tools stage complete!") + + +def _configure_logging(verbose: bool) -> None: + level = logging.DEBUG if verbose else logging.INFO + logging.getLogger("captain").setLevel(level) From c879098f4b578243c1efbada0d77551b78b9ddf9 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 00:07:46 +0200 Subject: [PATCH 47/93] click: some sugar, list available flavors via reflection Signed-off-by: Ricardo Pardini --- captain/click_cli/_main.py | 5 ++++- captain/click_cli/_release_publish.py | 3 ++- captain/flavor.py | 25 +++++++++++++++++++++++++ captain/util.py | 11 +++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/captain/click_cli/_main.py b/captain/click_cli/_main.py index da9bbf4..fe6e6bc 100644 --- a/captain/click_cli/_main.py +++ b/captain/click_cli/_main.py @@ -11,6 +11,8 @@ import click from captain.config import DEFAULT_FLAVOR_ID +from captain.flavor import list_available_flavors +from captain.util import detect_current_machine_arch log = logging.getLogger(__name__) @@ -26,7 +28,7 @@ def common_options(fn: Any) -> Any: @click.option( "--arch", envvar="ARCH", - default="amd64", + default=(detect_current_machine_arch()), show_default=True, type=click.Choice(["amd64", "arm64"], case_sensitive=False), metavar="ARCH", @@ -37,6 +39,7 @@ def common_options(fn: Any) -> Any: envvar="FLAVOR_ID", default=DEFAULT_FLAVOR_ID, show_default=True, + type=click.Choice(list_available_flavors(), case_sensitive=False), help="Flavor (kernel/board config) to build.", ) @click.option( diff --git a/captain/click_cli/_release_publish.py b/captain/click_cli/_release_publish.py index 3a85fb9..ec1b100 100644 --- a/captain/click_cli/_release_publish.py +++ b/captain/click_cli/_release_publish.py @@ -72,7 +72,8 @@ default=None, type=click.Choice(["amd64", "arm64", "combined"], case_sensitive=False), metavar="TARGET", - help="Artifact target: amd64, arm64, or combined (default: value of --arch).", + help="Artifact target: amd64, arm64, or combined (default: value of --arch); " + "combined requires trixie-full or equivalent flavor with both architectures' outputs present.", ) @click.option( "--git-sha", diff --git a/captain/flavor.py b/captain/flavor.py index c79ee2d..18098da 100644 --- a/captain/flavor.py +++ b/captain/flavor.py @@ -115,6 +115,31 @@ def has_iso(self) -> bool: return False +def list_available_flavors() -> list[str]: + import importlib + import pkgutil + + package = importlib.import_module("captain.flavors") + # iter_modules finds immediate children; walk_packages recurses + ret = [] + for _finder, module_name, _is_pkg in pkgutil.walk_packages( + package.__path__, prefix=f"{package.__name__}." + ): + try: + module = importlib.import_module(module_name) + except Exception as exc: + log.debug(f"Skipping {module_name}: {exc}") + continue + + fn = getattr(module, "create_flavor", None) + if fn is not None and callable(fn): + flavor_id = module_name.rsplit(".", 1)[-1].replace("_", "-") + ret.append(flavor_id) + log.debug("Discovered flavor '%s' via module %s", flavor_id, module_name) + + return sorted(ret) + + def create_and_setup_flavor_for_id(flavor_id: str, cfg: Config) -> BaseFlavor: log.debug("Creating and setting up flavor for id '%s'", flavor_id) flavor_id_underscore = flavor_id.replace("-", "_") diff --git a/captain/util.py b/captain/util.py index 61736fa..9e2330b 100644 --- a/captain/util.py +++ b/captain/util.py @@ -4,6 +4,7 @@ import logging import os +import platform import subprocess import sys import tarfile @@ -53,6 +54,16 @@ def get_arch_info(arch: str) -> ArchInfo: sys.exit(1) +def detect_current_machine_arch() -> str: + machine = platform.machine().lower() + if machine in ("aarch64", "arm64"): + return "arm64" + elif machine in ("x86_64", "amd64"): + return "amd64" + else: + raise RuntimeError(f"Unsupported architecture: {machine}") + + def run( cmd: list[str], *, From c630d31081203f944997eb483a2bc1bba1f09380 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 01:00:25 +0200 Subject: [PATCH 48/93] docker: build_builder(): more info Signed-off-by: Ricardo Pardini --- captain/docker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/captain/docker.py b/captain/docker.py index 4a021d5..a93c881 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -50,7 +50,7 @@ def build_builder(cfg: Config) -> None: tagged_image = f"{cfg.builder_image}:{tag}" if not cfg.no_cache and _image_exists(tagged_image): - log.info("Docker image '%s' is up to date.", cfg.builder_image) + log.info("Docker image '%s' is up to date with %s.", cfg.builder_image, tagged_image) # Ensure the un-hashed tag exists so later docker-run calls that # reference cfg.builder_image (without the hash suffix) succeed. # This matters when the hashed tag was pre-loaded by CI. From a276554d3f63d2c83c2b5e0022cef000fa332262 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 01:03:07 +0200 Subject: [PATCH 49/93] click: kill old tools cli (keep stage) click: kill old cli completely Signed-off-by: Ricardo Pardini --- build.py | 14 +- captain/cli/__init__.py | 22 - captain/cli/_commands.py | 250 -------- captain/cli/_main.py | 150 ----- captain/cli/_parser.py | 554 ------------------ captain/cli/_release.py | 241 -------- captain/{click_cli => click}/__init__.py | 2 +- captain/{click_cli => click}/_build.py | 19 +- captain/{click_cli => click}/_builder.py | 3 +- captain/{click_cli => click}/_main.py | 2 +- .../{click_cli => click}/_release_publish.py | 5 +- captain/{cli => click}/_stages.py | 0 captain/{click_cli => click}/_tools.py | 6 +- click_cli.py | 32 - pyproject.toml | 3 +- 15 files changed, 24 insertions(+), 1279 deletions(-) delete mode 100644 captain/cli/__init__.py delete mode 100644 captain/cli/_commands.py delete mode 100644 captain/cli/_main.py delete mode 100644 captain/cli/_parser.py delete mode 100644 captain/cli/_release.py rename captain/{click_cli => click}/__init__.py (92%) rename captain/{click_cli => click}/_build.py (93%) rename captain/{click_cli => click}/_builder.py (97%) rename captain/{click_cli => click}/_main.py (97%) rename captain/{click_cli => click}/_release_publish.py (98%) rename captain/{cli => click}/_stages.py (100%) rename captain/{click_cli => click}/_tools.py (94%) delete mode 100644 click_cli.py diff --git a/build.py b/build.py index df5324f..07f38da 100755 --- a/build.py +++ b/build.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 -"""CaptainOS build system entry point. +"""CaptainOS build system — click-based CLI entry point. -Requires: Python >= 3.13, Rich, Docker (unless all stages use native or skip). -It is recommended to use Astral's uv to run this script, which will automatically - install dependencies in an isolated environment. -This will automatically use uv to re-launch itself when stages use Docker. +Requires: Python >= 3.13 and a lot of dependencies; Use Astral's ``uv`` to run:: + + uv run click_cli.py --help + uv run click_cli.py builder + uv run click_cli.py build --arch arm64 + uv run click_cli.py release-publish --target combined """ import sys @@ -14,7 +16,7 @@ sys.exit(1) try: - from captain.cli import main + from captain.click import main except ImportError as exc: print(f"ERROR: {exc}", file=sys.stderr) uv_url = "https://docs.astral.sh/uv/getting-started/installation/" diff --git a/captain/cli/__init__.py b/captain/cli/__init__.py deleted file mode 100644 index 77481c1..0000000 --- a/captain/cli/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -"""CLI entry point — single configargparse parser with pre-extracted subcommand. - -Every configuration parameter is both a ``--cli-flag`` and an environment -variable, following the ff priority model: - - CLI args > environment variables > defaults - -The subcommand (``build``, ``initramfs``, ``tools``, …) is extracted from -``sys.argv`` *before* parsing so that flags work in any position:: - - ./build.py --arch=arm64 initramfs # works - ./build.py initramfs --arch=arm64 # also works - ARCH=arm64 ./build.py initramfs # also works -""" - -from captain.cli._main import main -from captain.cli._parser import COMMANDS - -__all__ = [ - "COMMANDS", - "main", -] diff --git a/captain/cli/_commands.py b/captain/cli/_commands.py deleted file mode 100644 index 6d31918..0000000 --- a/captain/cli/_commands.py +++ /dev/null @@ -1,250 +0,0 @@ -"""Build and utility command handlers.""" - -from __future__ import annotations - -import logging -import shutil -import sys -from pathlib import Path - -from captain import artifacts, docker, qemu -from captain.config import Config -from captain.flavor import BaseFlavor -from captain.util import run - -from ._stages import ( - _build_iso_stage, - _build_mkosi_stage, - _build_tools_stage, -) - -log = logging.getLogger(__name__) - - -def _cmd_tools(cfg: Config, _extra_args: list[str]) -> None: - """Download tools (containerd, runc, nerdctl, CNI plugins).""" - _build_tools_stage(cfg) - log.info("Tools stage complete!") - - -def _cmd_initramfs(cfg: Config, flavor: BaseFlavor, extra_args: list[str]) -> None: - """Build only the initramfs via mkosi, then collect artifacts.""" - _build_mkosi_stage(cfg, extra_args) - artifacts.collect_initramfs(cfg) - artifacts.collect_kernel(cfg) - artifacts.collect_dtbs(cfg) - log.info("Initramfs build complete!") - - -def _cmd_iso(cfg: Config, _extra_args: list[str]) -> None: - """Build only the ISO image.""" - _build_iso_stage(cfg) - artifacts.collect_iso(cfg) - log.info("ISO build complete!") - - -def _cmd_build(cfg: Config, flavor: BaseFlavor, extra_args: list[str]) -> None: - """Full build: tools → initramfs → iso → artifacts.""" - _build_tools_stage(cfg) - _cmd_initramfs(cfg, flavor, extra_args) # delegate, so it also collects - - if flavor.has_iso(): - _build_iso_stage(cfg) - else: - log.info("Flavor '%s' does not have an ISO stage; skipping.", flavor.id) - - artifacts.collect(cfg) - log.info("Build complete!") - - -def _cmd_shell(cfg: Config, _extra_args: list[str]) -> None: - """Interactive shell inside the builder container.""" - docker.build_builder(cfg) - log.info("Entering builder shell (type 'exit' to leave)...") - docker.run_in_builder( - cfg, - *(["-it"] if sys.stdout.isatty() and sys.stdin.isatty() else []), - "--entrypoint", - "/bin/bash", - cfg.builder_image, - ) - - -def _cmd_clean(cfg: Config, _extra_args: list[str], args: object = None) -> None: - """Remove build artifacts for the selected flavor, or all.""" - clean_all = getattr(args, "clean_all", False) - - if clean_all: - _clean_all(cfg) - else: - _clean_version(cfg) - - -def _clean_version(cfg: Config) -> None: - """Remove build artifacts for a single flavor.""" - kver = cfg.flavor_id - log.info("Cleaning build artifacts for kernel %s (%s)...", kver, cfg.arch) - mkosi_output = cfg.mkosi_output - - # Version-specific directories under mkosi.output/{stage}/{version}/{arch} - version_dirs = [ - mkosi_output / "kernel" / kver / cfg.arch, - mkosi_output / "initramfs" / kver / cfg.arch, - mkosi_output / "iso" / kver / cfg.arch, - ] - - has_docker = shutil.which("docker") is not None - existing = [d for d in version_dirs if d.exists()] - if existing and has_docker: - # Use Docker to remove root-owned files from mkosi. - # Invoke rm directly (no shell) to avoid injection via path components. - container_path_args = [ - f"/work/mkosi.output/{d.relative_to(mkosi_output)}" for d in existing - ] - run( - [ - "docker", - "run", - "--rm", - "-v", - f"{cfg.project_dir}:/work", - "-w", - "/work", - "debian:trixie", - "rm", - "-rf", - "--", - *container_path_args, - ], - ) - elif existing: - for d in existing: - shutil.rmtree(d, ignore_errors=True) - - # Remove versioned artifacts from out/ - if cfg.output_dir.exists(): - for pattern in ( - f"vmlinuz-{kver}-*", - f"initramfs-{kver}-*", - f"captainos-{kver}-*", - f"sha256sums-{kver}-*", - ): - for p in cfg.output_dir.glob(pattern): - p.unlink(missing_ok=True) - - log.info("Clean complete for kernel %s.", kver) - - -def _clean_all(cfg: Config) -> None: - """Remove all build artifacts (all flavors).""" - log.info("Cleaning ALL build artifacts...") - mkosi_output = cfg.mkosi_output - mkosi_cache = cfg.project_dir / "mkosi.cache" - - has_docker = shutil.which("docker") is not None - if has_docker: - # Use Docker to remove root-owned files from mkosi - if mkosi_output.exists() or mkosi_cache.exists(): - run( - [ - "docker", - "run", - "--rm", - "-v", - f"{cfg.project_dir}:/work", - "-w", - "/work", - "debian:trixie", - "sh", - "-c", - "rm -rf /work/mkosi.output/image*" - " /work/mkosi.output/initramfs" - " /work/mkosi.output/kernel" - " /work/mkosi.output/tools" - " /work/mkosi.output/iso" - " /work/mkosi.cache", - ], - ) - else: - # No Docker available — remove directly (may need sudo for root-owned mkosi files) - for pattern in ("image*", "initramfs", "kernel", "tools", "iso"): - for p in mkosi_output.glob(pattern): - if p.is_dir(): - shutil.rmtree(p, ignore_errors=True) - else: - p.unlink(missing_ok=True) - if mkosi_cache.exists(): - shutil.rmtree(mkosi_cache, ignore_errors=True) - - if cfg.output_dir.exists(): - shutil.rmtree(cfg.output_dir) - log.info("Clean complete.") - - -def _cmd_summary(cfg: Config, _extra_args: list[str]) -> None: - """Print mkosi configuration summary.""" - tools_tree = str(cfg.tools_output) - output_dir = str(cfg.initramfs_output) - match cfg.mkosi_mode: - case "docker": - docker.build_builder(cfg) - container_tree = f"/work/mkosi.output/tools/{cfg.arch}" - container_outdir = f"/work/mkosi.output/initramfs/{cfg.flavor_id}/{cfg.arch}" - docker.run_mkosi( - cfg, - f"--extra-tree={container_tree}", - f"--output-dir={container_outdir}", - "summary", - ) - case "native": - run( - [ - "mkosi", - f"--architecture={cfg.arch_info.mkosi_arch}", - f"--extra-tree={tools_tree}", - f"--output-dir={output_dir}", - "summary", - ], - cwd=cfg.project_dir, - ) - case "skip": - log.error("Cannot show mkosi summary when MKOSI_MODE=skip.") - raise SystemExit(1) - - -def _cmd_checksums(cfg: Config, _extra_args: list[str], args: object = None) -> None: - """Compute SHA-256 checksums for the specified files.""" - files = getattr(args, "files", None) or [] - output = getattr(args, "output", None) - - if files: - # Explicit mode: user provided specific files and output. - if not output: - log.error("--output is required when specifying files explicitly.") - raise SystemExit(1) - artifacts.collect_checksums( - [Path(f) for f in files], - Path(output), - ) - else: - # Default mode: produce checksums for the selected architecture. - out = cfg.output_dir - oarch = cfg.arch_info.output_arch - kver = cfg.flavor_id - arch_files = [ - out / f"vmlinuz-{kver}-{oarch}", - out / f"initramfs-{kver}-{oarch}", - out / f"captainos-{kver}-{oarch}.iso", - ] - existing = [f for f in arch_files if f.is_file()] - if not existing: - log.error("No artifacts found for %s-%s in %s", kver, oarch, out) - raise SystemExit(1) - dest = Path(output) if output else out / f"sha256sums-{kver}-{oarch}.txt" - artifacts.collect_checksums(existing, dest) - log.info("Checksums complete!") - - -def _cmd_qemu_test(cfg: Config, _extra_args: list[str], args: object = None) -> None: - """Boot the image in QEMU for testing.""" - qemu.run_qemu(cfg, args=args) # type: ignore[arg-type] diff --git a/captain/cli/_main.py b/captain/cli/_main.py deleted file mode 100644 index a2aecfc..0000000 --- a/captain/cli/_main.py +++ /dev/null @@ -1,150 +0,0 @@ -"""CLI entry point — single configargparse parser with pre-extracted subcommand. - -Every configuration parameter is both a ``--cli-flag`` and an environment -variable, following the ff priority model: - - CLI args > environment variables > defaults - -The subcommand (``build``, ``kernel``, ``tools``, …) is extracted from -``sys.argv`` *before* parsing so that flags work in any position:: - - ./build.py --arch=arm64 kernel # works - ./build.py kernel --arch=arm64 # also works - ARCH=arm64 ./build.py kernel # also works -""" - -from __future__ import annotations - -import logging -import sys -from pathlib import Path - -import captain.flavor -from captain import docker -from captain.config import Config -from captain.flavor import BaseFlavor -from captain.util import run - -from ._commands import ( - _cmd_build, - _cmd_checksums, - _cmd_clean, - _cmd_initramfs, - _cmd_iso, - _cmd_qemu_test, - _cmd_shell, - _cmd_summary, - _cmd_tools, -) -from ._parser import _build_parser, _extract_command -from ._release import _cmd_release - -log = logging.getLogger(__name__) - - -def main(project_dir: Path | None = None) -> None: - """Main CLI entry point.""" - - # 1. Extract the subcommand from argv before parsing so flags - # work in any position (before or after the command name). - raw_argv = sys.argv[1:] - command, flag_argv = _extract_command(raw_argv) - - # For release subcommands, defer -h/--help to _cmd_release so it - # can print subcommand-specific help instead of the generic release help. - # We defer whenever there's any positional token (not just valid ones), - # so that invalid subcommands like "push" show the proper error instead - # of the parent help. - help_deferred = False - if command == "release": - has_positional = any(not tok.startswith("-") for tok in flag_argv) - has_help = "-h" in flag_argv or "--help" in flag_argv - if has_positional and has_help: - flag_argv = [t for t in flag_argv if t not in ("-h", "--help")] - help_deferred = True - - # 2. Build the parser (TINK flags added only for qemu-test). - parser = _build_parser(command) - - # 3. Parse known args — anything unrecognised passes through to mkosi. - args, extra = parser.parse_known_args(flag_argv) - if help_deferred: - extra.append("--help") - - # 4. Separate --force (mkosi passthrough) from the rest. - mkosi_args: list[str] = [] - if getattr(args, "force", False): - mkosi_args.append("--force") - - # 5. Determine project directory. - if project_dir is None: - project_dir = Path(__file__).resolve().parent.parent.parent - - # 6. Build Config from the parsed namespace. - cfg = Config.from_args(args, project_dir) - cfg.mkosi_args = mkosi_args - - # 7. Instantiate the flavor (from cfg.flavor_id) - flavor: BaseFlavor = captain.flavor.create_and_setup_flavor_for_id(cfg.flavor_id, cfg) - - # 8. Dispatch. - dispatch: dict[str, object] = { - "build": _cmd_build, - "tools": _cmd_tools, - "initramfs": _cmd_initramfs, - "iso": _cmd_iso, - "checksums": _cmd_checksums, - "shell": _cmd_shell, - "clean": _cmd_clean, - "release": _cmd_release, - "summary": _cmd_summary, - "qemu-test": _cmd_qemu_test, - } - - log.debug("Dispatching command '%s', extra args: %s", command, extra) - handler = dispatch.get(command) - if handler is not None: - # If building, or doing initramfs (mkosi), - # generate the flavor first so the files are in place. - if command in ("build", "initramfs", "publish"): - flavor.generate() - handler(cfg, flavor, extra) # type: ignore[operator] - elif command in ("release"): - handler(cfg, flavor, extra, args=args) # type: ignore[operator] - elif command in ("qemu-test", "checksums", "clean"): - handler(cfg, extra, args=args) # type: ignore[operator] - else: - handler(cfg, extra) # type: ignore[operator] - else: - log.error("You hit ungolden path; passing stuff to mkosi directly is not reproducible.") - # Pass through to mkosi (shouldn't happen with _extract_command - # but kept as a safety net). - tools_tree = str(cfg.tools_output) - output_dir = str(cfg.initramfs_output) - match cfg.mkosi_mode: - case "docker": - docker.build_builder(cfg) - container_tree = f"/work/mkosi.output/tools/{cfg.arch}" - container_outdir = f"/work/mkosi.output/initramfs/{cfg.flavor_id}/{cfg.arch}" - docker.run_mkosi( - cfg, - f"--extra-tree={container_tree}", - f"--output-dir={container_outdir}", - command, - *extra, - ) - case "native": - run( - [ - "mkosi", - f"--architecture={cfg.arch_info.mkosi_arch}", - f"--extra-tree={tools_tree}", - f"--output-dir={output_dir}", - command, - *extra, - ], - cwd=cfg.project_dir, - ) - case "skip": - log.error("Cannot pass '%s' to mkosi when MKOSI_MODE=skip.", command) - raise SystemExit(1) diff --git a/captain/cli/_parser.py b/captain/cli/_parser.py deleted file mode 100644 index 46229c4..0000000 --- a/captain/cli/_parser.py +++ /dev/null @@ -1,554 +0,0 @@ -"""CLI parser infrastructure — formatter, constants, and flag definitions.""" - -from __future__ import annotations - -import argparse -import shutil -import sys -from collections.abc import Callable, Iterable - -import configargparse - -from captain.config import DEFAULT_FLAVOR_ID - -# --------------------------------------------------------------------------- -# Known subcommands (order matters for help text) -# --------------------------------------------------------------------------- - -COMMANDS: dict[str, str] = { - "build": "Run all build stages: kernel → tools → initramfs → iso (default)", - "kernel": "Build only the kernel + modules", - "tools": "Download tools (containerd, runc, nerdctl, CNI)", - "initramfs": "Build only the initramfs via mkosi", - "iso": "Build a UEFI-bootable ISO image", - "checksums": "Compute SHA-256 checksums for specified files", - "release": "OCI artifact operations (publish, pull, tag)", - "shell": "Interactive shell inside the builder container", - "clean": "Remove build artifacts (per kernel version or all)", - "summary": "Print mkosi configuration summary", - "qemu-test": "Boot the image in QEMU for testing", -} - -VALID_MODES = ("docker", "native", "skip") - -# Boolean (store_true) flags — these do NOT consume the next token as a value. -# Used by _extract_command to avoid treating a flag value as a subcommand. -_BOOLEAN_FLAGS = frozenset( - { - "--all", - "--force-kernel", - "--force-tools", - "--force-iso", - "--force", - "--no-cache", - "-h", - "--help", - } -) - - -class _HelpFormatter(argparse.RawDescriptionHelpFormatter): - """Clean help: word-wrapped text, raw epilog, no env-var refs, - no defaults when the value is empty / None / False, and a short - usage line.""" - - def _get_help_string(self, action: argparse.Action) -> str: - """Append ``(default: X)`` only when X is meaningful.""" - text = action.help or "" - if action.default in (None, "", False, argparse.SUPPRESS): - return text - if "%(default)" not in text: - text += " (default: %(default)s)" - return text - - def _format_usage( - self, - usage: str | None, - actions: Iterable[argparse.Action], - groups: Iterable[argparse._MutuallyExclusiveGroup], - prefix: str | None, - ) -> str: - """Show a short usage line with the command placeholder.""" - prog = self._prog - # Top-level ("build.py") and release ("build.py release") have subcommands. - if prog in ("build.py", "build.py release"): - return f"usage: {prog} [command] [flags]\n\n" - if prog == "build.py release tag": - return f"usage: {prog} [flags]\n\n" - return f"usage: {prog} [flags]\n\n" - - -def _extract_command(argv: list[str]) -> tuple[str, list[str]]: - """Remove and return the first recognised subcommand from *argv*. - - Returns ``("build", argv)`` when no subcommand is found. - - The scanner skips tokens that are likely flag *values* (the token - immediately after a ``--flag`` that is not boolean and does not use - ``=`` syntax). This prevents ``--builder-image build`` from - incorrectly extracting ``build`` as the subcommand. - """ - prev_was_value_flag = False - for i, tok in enumerate(argv): - if tok.startswith("-"): - # Boolean flags don't consume the next token. - prev_was_value_flag = False if "=" in tok else tok not in _BOOLEAN_FLAGS - continue - if prev_was_value_flag: - # This token is the value of the preceding flag — skip it. - prev_was_value_flag = False - continue - # Standalone positional token — check if it's a command. - if tok in COMMANDS: - return tok, argv[:i] + argv[i + 1 :] - # Unknown positional token — not a recognised command. - valid = ", ".join(COMMANDS) - print( - f"error: unknown command '{tok}'\nvalid commands: {valid}", - file=sys.stderr, - ) - raise SystemExit(2) - return "build", list(argv) - - -def _build_parser(command: str) -> configargparse.ArgParser: - """Construct a command-specific CLI parser. - - Only the flags relevant to *command* are added, so ``--help`` - shows a focused help message. - """ - - # -- description & epilog ------------------------------------------ - if command == "build": - desc = "Build CaptainOS images. Stages: kernel → tools → initramfs → iso." - commands_list = "\n".join(f" {name:14s} {d}" for name, d in COMMANDS.items()) - epilog = f"""\ -commands: -{commands_list} -""" - elif command == "release": - desc = "OCI release workflow: pull (or build) → publish → tag" - release_cmds = { - "publish": "Publish artifacts as a multi-arch OCI image", - "pull": "Pull and extract artifacts (amd64, arm64, or combined)", - "tag": "Tag all artifact images with a version", - } - commands_list = "\n".join(f" {name:14s} {d}" for name, d in release_cmds.items()) - epilog = f"""\ -commands: -{commands_list} -""" - else: - desc = COMMANDS.get(command, command) - epilog = None - - # Adapt to the real terminal width so argparse wraps at word - # boundaries instead of letting the terminal hard-wrap mid-word. - # max_help_position=38 accommodates the widest flag+metavar. - columns = shutil.get_terminal_size().columns - - parser = configargparse.ArgParser( - prog=f"build.py {command}" if command != "build" else "build.py", - description=desc, - epilog=epilog, - add_env_var_help=False, - formatter_class=lambda prog: _HelpFormatter( - prog, - max_help_position=38, - width=columns, - ), - ) - - # -- Add only the flag groups relevant to this command ------------- - for adder in _COMMAND_FLAGS.get(command, []): - adder(parser) - - return parser - - -# --------------------------------------------------------------------------- -# Flag-group helpers — each adds one argument group to the parser -# --------------------------------------------------------------------------- - - -def _add_common_flags(parser: configargparse.ArgParser) -> None: - """--arch, --builder-image, --no-cache""" - g = parser.add_argument_group("build configuration") - g.add_argument( - "--arch", - env_var="ARCH", - default="amd64", - choices=["amd64", "arm64"], - help="target architecture", - ) - g.add_argument( - "--builder-image", - env_var="BUILDER_IMAGE", - metavar="IMAGE", - default="captainos-builder", - help="Docker builder image name", - ) - g.add_argument( - "--no-cache", - env_var="NO_CACHE", - action="store_true", - default=False, - help="rebuild builder image without Docker cache", - ) - - -def _add_flavor_flags(parser: configargparse.ArgParser) -> None: - """--flavor-id""" - g = parser.add_argument_group("kernel") - g.add_argument( - "--flavor-id", - env_var="FLAVOR_ID", - metavar="VER", - default=DEFAULT_FLAVOR_ID, - help="kernel version to build", - ) - - -def _add_tools_flags(parser: configargparse.ArgParser) -> None: - """--tools-mode, --force-tools""" - g = parser.add_argument_group("tools") - g.add_argument( - "--tools-mode", - env_var="TOOLS_MODE", - default="docker", - choices=list(VALID_MODES), - help="tools stage execution mode", - ) - g.add_argument( - "--force-tools", - env_var="FORCE_TOOLS", - action="store_true", - default=False, - help="re-download tools even if outputs exist", - ) - - -def _add_mkosi_flags(parser: configargparse.ArgParser) -> None: - """--mkosi-mode, --force (mkosi passthrough)""" - g = parser.add_argument_group("initramfs (mkosi)") - g.add_argument( - "--mkosi-mode", - env_var="MKOSI_MODE", - default="docker", - choices=list(VALID_MODES), - help="mkosi stage execution mode", - ) - g.add_argument( - "--force", - action="store_true", - default=False, - help="passed through to mkosi as --force", - ) - - -def _add_summary_flags(parser: configargparse.ArgParser) -> None: - """--mkosi-mode only (no --force).""" - g = parser.add_argument_group("mkosi") - g.add_argument( - "--mkosi-mode", - env_var="MKOSI_MODE", - default="docker", - choices=list(VALID_MODES), - help="mkosi stage execution mode", - ) - - -def _add_iso_flags(parser: configargparse.ArgParser) -> None: - """--iso-mode, --force-iso""" - g = parser.add_argument_group("iso") - g.add_argument( - "--iso-mode", - env_var="ISO_MODE", - default="docker", - choices=list(VALID_MODES), - help="iso stage execution mode", - ) - g.add_argument( - "--force-iso", - env_var="FORCE_ISO", - action="store_true", - default=False, - help="force ISO rebuild even if outputs exist", - ) - - -def _add_mode_flags(parser: configargparse.ArgParser) -> None: - """All four --*-mode flags (used by 'shell' which checks needs_docker).""" - g = parser.add_argument_group("stage modes") - for stage in ("kernel", "tools", "mkosi", "iso"): - g.add_argument( - f"--{stage}-mode", - env_var=f"{stage.upper()}_MODE", - default="docker", - choices=list(VALID_MODES), - help=f"{stage} stage execution mode", - ) - - -def _add_clean_flags(parser: configargparse.ArgParser) -> None: - """--all flag for the clean command.""" - g = parser.add_argument_group("clean") - g.add_argument( - "--all", - env_var="CLEAN_ALL", - action="store_true", - default=False, - dest="clean_all", - help="remove ALL build artifacts instead of just the selected kernel version", - ) - - -def _add_checksums_flags(parser: configargparse.ArgParser) -> None: - """--output and positional file arguments for the checksums command.""" - g = parser.add_argument_group("checksums") - g.add_argument( - "--output", - "-o", - metavar="FILE", - default=None, - help="path to write the checksum file (default: out/sha256sums-{arch}.txt)", - ) - g.add_argument( - "files", - nargs="*", - metavar="FILE", - help="files to checksum (default: standard release artifacts in out/)", - ) - - -def _add_release_flags(parser: configargparse.ArgParser) -> None: - """--release-mode and OCI registry flags for the release command.""" - _add_release_base_flags(parser) - _add_release_target_flag(parser) - _add_release_pull_output(parser) - - -def _add_release_base_flags(parser: configargparse.ArgParser) -> None: - """Core release flags shared by all release subcommands.""" - g = parser.add_argument_group("release") - g.add_argument( - "--release-mode", - env_var="RELEASE_MODE", - default="native", - choices=list(VALID_MODES), - metavar="MODE", - help="release stage execution mode", - ) - - g = parser.add_argument_group("OCI registry") - g.add_argument( - "--registry", - env_var="REGISTRY", - metavar="HOST", - default="ghcr.io", - help="OCI registry hostname", - ) - g.add_argument( - "--repository", - env_var="GITHUB_REPOSITORY", - metavar="OWNER/NAME", - default="tinkerbell/captain", - help="repository (owner/name)", - ) - g.add_argument( - "--oci-artifact-name", - env_var="OCI_ARTIFACT_NAME", - metavar="NAME", - default="artifacts", - help="OCI artifact image name", - ) - - -def _add_release_target_flag(parser: configargparse.ArgParser) -> None: - """--target flag for publish and pull (not tag).""" - g = parser.add_argument_group("target") - g.add_argument( - "--target", - env_var="TARGET", - default=None, - choices=["amd64", "arm64", "combined"], - help="artifact target (amd64, arm64, or combined; default: --arch value)", - ) - g.add_argument( - "--git-sha", - env_var="GITHUB_SHA", - metavar="SHA", - default=None, - help="git commit SHA (default: from git rev-parse HEAD)", - ) - g.add_argument( - "--version-exclude", - env_var="VERSION_EXCLUDE", - metavar="TAG", - default=None, - help="tag to exclude from git-describe version lookup", - ) - g.add_argument( - "--force", - env_var="FORCE", - action="store_true", - default=False, - help="publish even if the image already exists in the registry", - ) - - -def _add_release_pull_output(parser: configargparse.ArgParser) -> None: - """--pull-output flag (only relevant for 'release pull').""" - g = parser.add_argument_group("pull") - g.add_argument( - "--pull-output", - metavar="DIR", - default=None, - help="output directory for pulled artifacts", - ) - - -def _add_release_tag_version(parser: configargparse.ArgParser) -> None: - """Positional argument for 'release tag'.""" - parser.add_argument( - "version", - nargs="?", - default=None, - help="version tag to apply (e.g. v1.0.0)", - ) - - -def _add_qemu_flags(parser: configargparse.ArgParser) -> None: - """--qemu-append, --qemu-mem, --qemu-smp""" - g = parser.add_argument_group("qemu") - g.add_argument( - "--qemu-append", - env_var="QEMU_APPEND", - metavar="ARGS", - default="", - help="extra kernel cmdline args for qemu-test", - ) - g.add_argument( - "--qemu-mem", - env_var="QEMU_MEM", - metavar="SIZE", - default="2G", - help="QEMU RAM size", - ) - g.add_argument( - "--qemu-smp", - env_var="QEMU_SMP", - metavar="N", - default="2", - help="QEMU CPU count", - ) - - -def _add_tink_flags(parser: configargparse.ArgParser) -> None: - """Tinkerbell kernel cmdline flags + --ipam.""" - g = parser.add_argument_group("tinkerbell") - g.add_argument( - "--tink-worker-image", - env_var="TINK_WORKER_IMAGE", - metavar="IMAGE", - default="ghcr.io/tinkerbell/tink-agent:latest", - help="tink-agent container image reference", - ) - g.add_argument( - "--tink-docker-registry", - env_var="TINK_DOCKER_REGISTRY", - metavar="HOST", - default="", - help="registry host (triggers tink-agent services)", - ) - g.add_argument( - "--tink-grpc-authority", - env_var="TINK_GRPC_AUTHORITY", - metavar="ADDR", - default="", - help="tink-server gRPC endpoint (host:port)", - ) - g.add_argument( - "--tink-worker-id", - env_var="TINK_WORKER_ID", - metavar="ID", - default="", - help="machine / worker ID", - ) - g.add_argument( - "--tink-tls", - env_var="TINK_TLS", - metavar="BOOL", - default="false", - help="enable TLS to tink-server", - ) - g.add_argument( - "--tink-insecure-tls", - env_var="TINK_INSECURE_TLS", - metavar="BOOL", - default="true", - help="allow insecure TLS", - ) - g.add_argument( - "--tink-insecure-registries", - env_var="TINK_INSECURE_REGISTRIES", - metavar="LIST", - default="", - help="comma-separated insecure registries", - ) - g.add_argument( - "--tink-registry-username", - env_var="TINK_REGISTRY_USERNAME", - metavar="USER", - default="", - help="registry auth username", - ) - g.add_argument( - "--tink-registry-password", - env_var="TINK_REGISTRY_PASSWORD", - metavar="PASS", - default="", - help="registry auth password", - ) - g.add_argument( - "--tink-syslog-host", - env_var="TINK_SYSLOG_HOST", - metavar="HOST", - default="", - help="remote syslog host", - ) - g.add_argument( - "--tink-facility", - env_var="TINK_FACILITY", - metavar="CODE", - default="", - help="facility code", - ) - g.add_argument( - "--ipam", - env_var="IPAM", - metavar="PARAM", - default="", - help="static networking IPAM parameter", - ) - - -# Map command → list of flag-group adders. -_COMMAND_FLAGS: dict[str, list[Callable[..., None]]] = { - "build": [ - _add_common_flags, - _add_flavor_flags, - _add_tools_flags, - _add_mkosi_flags, - _add_iso_flags, - ], - "tools": [_add_common_flags, _add_tools_flags], - "initramfs": [_add_common_flags, _add_flavor_flags, _add_mkosi_flags], - "iso": [_add_common_flags, _add_flavor_flags, _add_iso_flags], - "checksums": [_add_common_flags, _add_flavor_flags, _add_checksums_flags], - "release": [_add_common_flags, _add_flavor_flags, _add_release_flags], - "shell": [_add_common_flags], - "clean": [_add_common_flags, _add_flavor_flags, _add_clean_flags], - "summary": [_add_common_flags, _add_flavor_flags, _add_summary_flags], - "qemu-test": [_add_common_flags, _add_flavor_flags, _add_qemu_flags, _add_tink_flags], -} diff --git a/captain/cli/_release.py b/captain/cli/_release.py deleted file mode 100644 index 4ed0c7d..0000000 --- a/captain/cli/_release.py +++ /dev/null @@ -1,241 +0,0 @@ -"""Release subcommand — publish, pull, tag.""" - -from __future__ import annotations - -import logging -import shutil -import subprocess -from pathlib import Path - -import configargparse - -from captain import docker, oci -from captain.config import Config -from captain.flavor import BaseFlavor -from captain.util import check_release_dependencies - -from ._parser import ( - _add_common_flags, - _add_flavor_flags, - _add_release_base_flags, - _add_release_pull_output, - _add_release_tag_version, - _add_release_target_flag, - _HelpFormatter, -) - -log = logging.getLogger(__name__) - -_RELEASE_SUBCOMMANDS = ("publish", "pull", "tag") - -_RELEASE_SUBCMD_INFO: dict[str, tuple[str, list]] = { - "publish": ( - "Publish artifacts as a multi-arch OCI image", - [_add_common_flags, _add_flavor_flags, _add_release_base_flags, _add_release_target_flag], - ), - "pull": ( - "Pull and extract artifacts (amd64, arm64, or combined)", - [ - _add_common_flags, - _add_flavor_flags, - _add_release_base_flags, - _add_release_target_flag, - _add_release_pull_output, - ], - ), - "tag": ( - "Tag all artifact images with a version", - [_add_common_flags, _add_flavor_flags, _add_release_base_flags, _add_release_tag_version], - ), -} - - -def _print_release_subcmd_help(sub: str, *, exit_code: int = 0) -> None: - """Print help for a release subcommand and exit.""" - desc, adders = _RELEASE_SUBCMD_INFO[sub] - columns = shutil.get_terminal_size().columns - parser = configargparse.ArgParser( - prog=f"build.py release {sub}", - description=desc, - add_env_var_help=False, - formatter_class=lambda prog: _HelpFormatter( - prog, - max_help_position=38, - width=columns, - ), - ) - for adder in adders: - adder(parser) - parser.print_help() - raise SystemExit(exit_code) - - -def _resolve_git_sha(args: object, project_dir: Path) -> str: - """Return the git SHA from args or by running git rev-parse.""" - sha = getattr(args, "git_sha", None) - if sha: - return sha - - result = subprocess.run( - ["git", "rev-parse", "HEAD"], - capture_output=True, - text=True, - check=True, - cwd=project_dir, - ) - return result.stdout.strip() - - -def _cmd_release( - cfg: Config, flavor: BaseFlavor, extra_args: list[str], args: object = None -) -> None: - """OCI artifact operations: publish, pull, tag.""" - - # Peel the release subcommand from extra_args. - if not extra_args: - log.error( - "Missing release subcommand.\n usage: build.py release {%s}\n", - ",".join(_RELEASE_SUBCOMMANDS), - ) - raise SystemExit(2) - - sub = extra_args[0] - rest = extra_args[1:] - - if sub not in _RELEASE_SUBCOMMANDS: - log.error( - "Unknown release subcommand '%s'.\n valid: %s\n", - sub, - ", ".join(_RELEASE_SUBCOMMANDS), - ) - raise SystemExit(2) - - # Handle --help / -h for the subcommand. - if "-h" in rest or "--help" in rest: - _print_release_subcmd_help(sub) - - # --- validate required args early --------------------------------- - if sub == "tag" and not rest: - log.error("Missing version argument.") - _print_release_subcmd_help(sub, exit_code=2) - if sub == "pull" and not getattr(args, "pull_output", None): - log.error("--pull-output is required for 'release pull'.") - _print_release_subcmd_help(sub, exit_code=2) - - # --- skip --------------------------------------------------------- - if cfg.release_mode == "skip": - log.info("RELEASE_MODE=skip — skipping release operation") - return - - # --- docker ------------------------------------------------------- - if cfg.release_mode == "docker": - docker.build_release_image(cfg) - log.info("Running release %s (docker)...", sub) - # Forward release-specific env vars into the container. - registry = getattr(args, "registry", "ghcr.io") - repository = getattr(args, "repository", "tinkerbell/captain") - artifact_name = getattr(args, "oci_artifact_name", "artifacts") - sha = _resolve_git_sha(args, cfg.project_dir) - env_args: list[str] = [ - "-e", - f"FLAVOR_ID={cfg.flavor_id}", - "-e", - f"REGISTRY={registry}", - "-e", - f"GITHUB_REPOSITORY={repository}", - "-e", - f"OCI_ARTIFACT_NAME={artifact_name}", - "-e", - f"GITHUB_SHA={sha}", - ] - exclude = getattr(args, "version_exclude", None) - if exclude: - env_args += ["-e", f"VERSION_EXCLUDE={exclude}"] - if sub in ("publish", "pull"): - target = getattr(args, "target", None) or cfg.arch - env_args += ["-e", f"TARGET={target}"] - if getattr(args, "force", False): - env_args += ["-e", "FORCE=true"] - pull_output = getattr(args, "pull_output", None) - - # Build the inner command. - inner_cmd = ["/work/build.py", "release", sub] - if pull_output: - inner_cmd += ["--pull-output", pull_output] - inner_cmd += list(rest) - - try: - docker.run_in_release( - cfg, - *env_args, - "--entrypoint", - "/usr/bin/uv", - docker.RELEASE_IMAGE, - *(["--verbose"] if log.isEnabledFor(logging.DEBUG) else []), - "run", - *inner_cmd, - ) - except subprocess.CalledProcessError as exc: - raise SystemExit(exc.returncode) from None - paths_to_fix = ["/work/out"] - if pull_output: - container_pull_output = f"/work/{pull_output.lstrip('/')}" - paths_to_fix.append(container_pull_output) - docker.fix_docker_ownership(cfg, paths_to_fix) - return - - # --- native ------------------------------------------------------- - if cfg.release_mode == "native": - missing = check_release_dependencies() - if missing: - log.error("Missing release tools: %s", ", ".join(missing)) - log.error("Install them or set --release-mode=docker.") - raise SystemExit(1) - # Common OCI parameters. - registry = getattr(args, "registry", "ghcr.io") - repository = getattr(args, "repository", "tinkerbell/captain") - artifact_name = getattr(args, "oci_artifact_name", "artifacts") - exclude = getattr(args, "version_exclude", None) - sha = _resolve_git_sha(args, cfg.project_dir) - tag = oci.compute_version_tag(cfg.project_dir, sha, exclude=exclude) - tag = f"{tag}-{cfg.flavor_id}" - - if sub == "publish": - target = getattr(args, "target", None) or cfg.arch - force = getattr(args, "force", False) - oci.publish( - cfg, - flavor, - target=target, - registry=registry, - repository=repository, - artifact_name=artifact_name, - tag=tag, - sha=sha, - force=force, - ) - - elif sub == "pull": - target = getattr(args, "target", None) or cfg.arch - pull_output = getattr(args, "pull_output", None) - if pull_output is None: - log.error("--pull-output is required for 'release pull'.") - raise SystemExit(2) - oci.pull( - registry=registry, - repository=repository, - artifact_name=artifact_name, - tag=tag, - target=target, - output_dir=Path(pull_output), - ) - - elif sub == "tag": - version = rest[0] - oci.tag_all( - registry=registry, - repository=repository, - artifact_name=artifact_name, - src_tag=tag, - new_tag=version, - ) diff --git a/captain/click_cli/__init__.py b/captain/click/__init__.py similarity index 92% rename from captain/click_cli/__init__.py rename to captain/click/__init__.py index b8b35d1..50adb24 100644 --- a/captain/click_cli/__init__.py +++ b/captain/click/__init__.py @@ -15,6 +15,6 @@ eval "$(_CAPTAIN_COMPLETE=zsh_source captain)" """ -from captain.click_cli._main import main +from captain.click._main import main __all__ = ["main"] diff --git a/captain/click_cli/_build.py b/captain/click/_build.py similarity index 93% rename from captain/click_cli/_build.py rename to captain/click/_build.py index 8d23edc..3148ec9 100644 --- a/captain/click_cli/_build.py +++ b/captain/click/_build.py @@ -4,10 +4,15 @@ import logging -import click - import captain.flavor -from captain.click_cli._main import cli, common_options, resolve_project_dir +import click +from captain import artifacts +from captain.click._main import cli, common_options, resolve_project_dir +from captain.click._stages import ( + _build_iso_stage, + _build_mkosi_stage, + _build_tools_stage, +) from captain.config import Config log = logging.getLogger(__name__) @@ -135,14 +140,6 @@ def build_cmd( flavor = captain.flavor.create_and_setup_flavor_for_id(cfg.flavor_id, cfg) flavor.generate() - # Import build commands (reuse existing stage orchestration). - from captain import artifacts - from captain.cli._stages import ( - _build_iso_stage, - _build_mkosi_stage, - _build_tools_stage, - ) - # Tools stage. _build_tools_stage(cfg) diff --git a/captain/click_cli/_builder.py b/captain/click/_builder.py similarity index 97% rename from captain/click_cli/_builder.py rename to captain/click/_builder.py index db167b6..80e4848 100644 --- a/captain/click_cli/_builder.py +++ b/captain/click/_builder.py @@ -5,8 +5,7 @@ import logging import click - -from captain.click_cli._main import cli, common_options, resolve_project_dir +from captain.click._main import cli, common_options, resolve_project_dir from captain.config import Config from captain.docker import build_builder diff --git a/captain/click_cli/_main.py b/captain/click/_main.py similarity index 97% rename from captain/click_cli/_main.py rename to captain/click/_main.py index fe6e6bc..fd3d9f3 100644 --- a/captain/click_cli/_main.py +++ b/captain/click/_main.py @@ -117,6 +117,6 @@ def cli(ctx: click.Context) -> None: def main() -> None: """Console-script entry point.""" # Import subcommand modules to register them on the group. - from captain.click_cli import _build, _builder, _release_publish, _tools # noqa: F401 + from captain.click import _build, _builder, _release_publish, _tools # noqa: F401 cli() diff --git a/captain/click_cli/_release_publish.py b/captain/click/_release_publish.py similarity index 98% rename from captain/click_cli/_release_publish.py rename to captain/click/_release_publish.py index ec1b100..991fa1f 100644 --- a/captain/click_cli/_release_publish.py +++ b/captain/click/_release_publish.py @@ -6,11 +6,10 @@ import subprocess from pathlib import Path -import click - import captain.flavor +import click from captain import oci -from captain.click_cli._main import cli, common_options, resolve_project_dir +from captain.click._main import cli, common_options, resolve_project_dir from captain.config import Config from captain.util import check_release_dependencies diff --git a/captain/cli/_stages.py b/captain/click/_stages.py similarity index 100% rename from captain/cli/_stages.py rename to captain/click/_stages.py diff --git a/captain/click_cli/_tools.py b/captain/click/_tools.py similarity index 94% rename from captain/click_cli/_tools.py rename to captain/click/_tools.py index c3267d2..f857e18 100644 --- a/captain/click_cli/_tools.py +++ b/captain/click/_tools.py @@ -5,8 +5,8 @@ import logging import click - -from captain.click_cli._main import cli, common_options, resolve_project_dir +from captain.click._main import cli, common_options, resolve_project_dir +from captain.click._stages import _build_tools_stage from captain.config import Config log = logging.getLogger(__name__) @@ -87,8 +87,6 @@ def tools_cmd( force_tools=force_tools, ) - from captain.cli._stages import _build_tools_stage - _build_tools_stage(cfg) log.info("Tools stage complete!") diff --git a/click_cli.py b/click_cli.py deleted file mode 100644 index e0147bc..0000000 --- a/click_cli.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -"""CaptainOS build system — click-based CLI entry point. - -Requires: Python >= 3.13, click, Rich, Docker (unless all stages use native/skip). -Use Astral's ``uv`` to run:: - - uv run click_cli.py --help - uv run click_cli.py builder - uv run click_cli.py build --arch arm64 - uv run click_cli.py release-publish --target combined - -Or, after ``uv pip install -e .``:: - - captain --help -""" - -import sys - -if sys.version_info < (3, 13): - print("ERROR: Python >= 3.13 is required.", file=sys.stderr) - sys.exit(1) - -try: - from captain.click_cli import main -except ImportError as exc: - print(f"ERROR: {exc}", file=sys.stderr) - uv_url = "https://docs.astral.sh/uv/getting-started/installation/" - print(f"Missing dependencies, use uv to run. See {uv_url}", file=sys.stderr) - sys.exit(1) - -if __name__ == "__main__": - main() diff --git a/pyproject.toml b/pyproject.toml index e98295b..fea7067 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,13 +8,12 @@ name = "captain" requires-python = ">=3.13" dependencies = [ "click>=8.1", - "configargparse>=1.7", "rich>=14.3.3", "jinja2>=3.1.6" ] [project.scripts] -captain = "captain.click_cli:main" +captain = "build:main" [project.optional-dependencies] dev = ["ruff>=0.15.9", "pyright>=1.1"] From 37896715f9ab60f490744eb22faa8da128275943 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 01:41:56 +0200 Subject: [PATCH 50/93] click: add iso command back - as it is called internally via docker Signed-off-by: Ricardo Pardini --- captain/click/_build.py | 3 +- captain/click/_builder.py | 1 + captain/click/_iso.py | 92 +++++++++++++++++++++++++++++++ captain/click/_main.py | 2 +- captain/click/_release_publish.py | 3 +- captain/click/_tools.py | 1 + 6 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 captain/click/_iso.py diff --git a/captain/click/_build.py b/captain/click/_build.py index 3148ec9..f46cf63 100644 --- a/captain/click/_build.py +++ b/captain/click/_build.py @@ -4,8 +4,9 @@ import logging -import captain.flavor import click + +import captain.flavor from captain import artifacts from captain.click._main import cli, common_options, resolve_project_dir from captain.click._stages import ( diff --git a/captain/click/_builder.py b/captain/click/_builder.py index 80e4848..398354b 100644 --- a/captain/click/_builder.py +++ b/captain/click/_builder.py @@ -5,6 +5,7 @@ import logging import click + from captain.click._main import cli, common_options, resolve_project_dir from captain.config import Config from captain.docker import build_builder diff --git a/captain/click/_iso.py b/captain/click/_iso.py new file mode 100644 index 0000000..32b173a --- /dev/null +++ b/captain/click/_iso.py @@ -0,0 +1,92 @@ +"""``captain iso`` — build a bootable ISO image for the specified flavor and architecture.""" + +from __future__ import annotations + +import logging + +import click + +import captain.flavor +from captain import artifacts +from captain.click._main import cli, common_options, resolve_project_dir +from captain.click._stages import ( + _build_iso_stage, +) +from captain.config import Config + +log = logging.getLogger(__name__) + + +@cli.command( + "iso", + short_help="Build ISO image only. Part of build.", +) +@common_options +@click.option( + "--builder-image", + envvar="BUILDER_IMAGE", + default="captainos-builder", + show_default=True, + help="Docker builder image name.", +) +@click.option( + "--no-cache", + envvar="NO_CACHE", + is_flag=True, + default=False, + help="Rebuild the builder image without Docker layer cache.", +) +@click.option( + "--iso-mode", + envvar="ISO_MODE", + default="docker", + show_default=True, + type=click.Choice(["docker", "native", "skip"], case_sensitive=False), + metavar="MODE", + help="ISO build stage execution mode (docker, native, skip).", +) +@click.option( + "--force-iso", + envvar="FORCE_ISO", + is_flag=True, + default=False, + help="Force ISO rebuild even if outputs already exist.", +) +def build_cmd( + *, + arch: str, + flavor_id: str, + project_dir: str | None, + verbose: bool, + builder_image: str, + no_cache: bool, + iso_mode: str, + force_iso: bool, +) -> None: + """Run the CaptainOS ISO build.""" + _configure_logging(verbose) + + proj = resolve_project_dir(project_dir) + + cfg = Config( + project_dir=proj, + output_dir=proj / "out", + arch=arch, + flavor_id=flavor_id, + builder_image=builder_image, + no_cache=no_cache, + iso_mode=iso_mode, + force_iso=force_iso, + ) + + # Instantiate the flavor + captain.flavor.create_and_setup_flavor_for_id(cfg.flavor_id, cfg) + + _build_iso_stage(cfg) + artifacts.collect_iso(cfg) + log.info("ISO build complete!!!") + + +def _configure_logging(verbose: bool) -> None: + level = logging.DEBUG if verbose else logging.INFO + logging.getLogger("captain").setLevel(level) diff --git a/captain/click/_main.py b/captain/click/_main.py index fd3d9f3..cd9928d 100644 --- a/captain/click/_main.py +++ b/captain/click/_main.py @@ -117,6 +117,6 @@ def cli(ctx: click.Context) -> None: def main() -> None: """Console-script entry point.""" # Import subcommand modules to register them on the group. - from captain.click import _build, _builder, _release_publish, _tools # noqa: F401 + from captain.click import _build, _builder, _iso, _release_publish, _tools # noqa: F401 cli() diff --git a/captain/click/_release_publish.py b/captain/click/_release_publish.py index 991fa1f..ca19dc0 100644 --- a/captain/click/_release_publish.py +++ b/captain/click/_release_publish.py @@ -6,8 +6,9 @@ import subprocess from pathlib import Path -import captain.flavor import click + +import captain.flavor from captain import oci from captain.click._main import cli, common_options, resolve_project_dir from captain.config import Config diff --git a/captain/click/_tools.py b/captain/click/_tools.py index f857e18..ada986b 100644 --- a/captain/click/_tools.py +++ b/captain/click/_tools.py @@ -5,6 +5,7 @@ import logging import click + from captain.click._main import cli, common_options, resolve_project_dir from captain.click._stages import _build_tools_stage from captain.config import Config From e01520dfef86d07432de79111b9a90e0b8b387b6 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 01:44:18 +0200 Subject: [PATCH 51/93] gha: switch to click cli Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 428a71e..16b8dd5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,7 @@ jobs: uses: astral-sh/setup-uv@v7 - name: Download tools - run: uv run ./build.py tools + run: uv run captain tools - name: Save tools cache if: github.ref == 'refs/heads/main' && steps.tools-cache.outputs.cache-hit != 'true' @@ -123,7 +123,7 @@ jobs: uses: astral-sh/setup-uv@v7 - name: Build initramfs - run: uv run ./build.py --arch=${{ matrix.arch }} # full build, incl initramfs and iso + run: uv run captain build --arch=${{ matrix.arch }} # full build, incl initramfs and iso when appropriate - name: Upload initramfs artifacts uses: actions/upload-artifact@v6 @@ -133,7 +133,7 @@ jobs: retention-days: 1 # ------------------------------------------------------------------- - # Build UEFI-bootable ISO (depends on initramfs) + # UEFI-bootable ISO - only for certain flavors (eg trixie-full) # ------------------------------------------------------------------- - name: Upload ISO artifact @@ -160,7 +160,7 @@ jobs: if: github.ref == 'refs/heads/main' env: TARGET: ${{ matrix.arch }} - run: uv run ./build.py release publish + run: uv run captain release publish # ------------------------------------------------------------------- # Publish combined multi-arch image (reuses per-arch registry blobs) @@ -219,4 +219,4 @@ jobs: - name: Publish combined image to GHCR env: FLAVOR_ID: "${{ env.DEFAULT_FLAVOR_ID }}" - run: uv run ./build.py release publish + run: uv run captain release publish From f0b6a1db9dbd6f54d95467b0b984548f0d2302fe Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 02:10:51 +0200 Subject: [PATCH 52/93] gha: release-publish Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16b8dd5..e9dc8e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,7 +160,7 @@ jobs: if: github.ref == 'refs/heads/main' env: TARGET: ${{ matrix.arch }} - run: uv run captain release publish + run: uv run captain release-publish # ------------------------------------------------------------------- # Publish combined multi-arch image (reuses per-arch registry blobs) @@ -219,4 +219,4 @@ jobs: - name: Publish combined image to GHCR env: FLAVOR_ID: "${{ env.DEFAULT_FLAVOR_ID }}" - run: uv run captain release publish + run: uv run captain release-publish From 3919364f2b4370bc9e1b8ff4cf1298570fce1fd8 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 11:34:47 +0200 Subject: [PATCH 53/93] captain: rework `obtain_builder` Signed-off-by: Ricardo Pardini --- captain/click/_build.py | 6 +- captain/click/_builder.py | 77 +++------------------ captain/click/_iso.py | 6 +- captain/click/_main.py | 23 ++++++- captain/click/_release_publish.py | 26 ++----- captain/click/_stages.py | 6 +- captain/click/_tools.py | 6 +- captain/config.py | 82 +---------------------- captain/docker.py | 108 ++++++++++++++++++------------ 9 files changed, 123 insertions(+), 217 deletions(-) diff --git a/captain/click/_build.py b/captain/click/_build.py index f46cf63..1414b40 100644 --- a/captain/click/_build.py +++ b/captain/click/_build.py @@ -92,8 +92,9 @@ def build_cmd( flavor_id: str, project_dir: str | None, verbose: bool, + builder_registry: str | None, + builder_repository: str | None, builder_image: str, - no_cache: bool, mkosi_mode: str, tools_mode: str, iso_mode: str, @@ -125,8 +126,9 @@ def build_cmd( output_dir=proj / "out", arch=arch, flavor_id=flavor_id, + builder_registry=builder_registry, + builder_repository=builder_repository, builder_image=builder_image, - no_cache=no_cache, tools_mode=tools_mode, mkosi_mode=mkosi_mode, iso_mode=iso_mode, diff --git a/captain/click/_builder.py b/captain/click/_builder.py index 398354b..858bff2 100644 --- a/captain/click/_builder.py +++ b/captain/click/_builder.py @@ -8,71 +8,36 @@ from captain.click._main import cli, common_options, resolve_project_dir from captain.config import Config -from captain.docker import build_builder +from captain.docker import obtain_builder log = logging.getLogger(__name__) @cli.command( "builder", - short_help="Build the Docker builder image, optionally push it.", + short_help="Build the Docker builder image and optionally push it.", ) @common_options -@click.option( - "--builder-image", - envvar="BUILDER_IMAGE", - default="captainos-builder", - show_default=True, - help="Docker builder image name.", -) -@click.option( - "--no-cache", - envvar="NO_CACHE", - is_flag=True, - default=False, - help="Rebuild the builder image without Docker layer cache.", -) @click.option( "--push", is_flag=True, default=False, help="Push the built image to a registry after building.", ) -@click.option( - "--registry", - envvar="REGISTRY", - default=None, - help="Registry hostname (e.g. ghcr.io). Required when --push is set.", -) -@click.option( - "--registry-path", - envvar="REGISTRY_PATH", - default=None, - help="Repository path within the registry (e.g. tinkerbell/captain/builder).", -) -@click.option( - "--tag", - "push_tag", - default=None, - help="Tag to apply when pushing (default: Dockerfile content hash).", -) def builder_cmd( *, arch: str, flavor_id: str, project_dir: str | None, verbose: bool, + builder_registry: str | None, + builder_repository: str | None, builder_image: str, - no_cache: bool, push: bool, - registry: str | None, - registry_path: str | None, - push_tag: str | None, ) -> None: """Build the Docker builder image used by other build stages. - By default the image is built locally only. Pass --push together - with --registry and --registry-path to also push the image to a + By default the image is built locally only. Pass --push to also push the image to a remote container registry. \b @@ -80,8 +45,7 @@ def builder_cmd( -------- captain builder captain builder --no-cache - captain builder --push --registry ghcr.io --registry-path tinkerbell/captain/builder - captain builder --push --registry ghcr.io --registry-path tinkerbell/captain/builder --tag lt + captain builder --push """ _configure_logging(verbose) @@ -92,37 +56,16 @@ def builder_cmd( output_dir=proj / "out", arch=arch, flavor_id=flavor_id, + builder_registry=builder_registry, + builder_repository=builder_repository, builder_image=builder_image, - no_cache=no_cache, + builder_push=push, ) # 1. Build the image. - build_builder(cfg) + obtain_builder(cfg) log.info("Builder image '%s' is ready.", cfg.builder_image) - # 2. Optionally push. - if push: - if not registry or not registry_path: - raise click.UsageError( - "--registry and --registry-path are required when --push is set." - ) - import hashlib - - from captain.util import run - - if push_tag is None: - # Use the Dockerfile content hash (same logic as docker.py) - dockerfile = cfg.project_dir / "Dockerfile" - push_tag = hashlib.sha256(dockerfile.read_bytes()).hexdigest() - - full_ref = f"{registry}/{registry_path}:{push_tag}" - log.info("Pushing builder image → %s", full_ref) - - # Tag and push. - run(["docker", "tag", cfg.builder_image, full_ref]) - run(["docker", "push", full_ref]) - log.info("Push complete: %s", full_ref) - def _configure_logging(verbose: bool) -> None: """Set the captain logger level based on the --verbose flag.""" diff --git a/captain/click/_iso.py b/captain/click/_iso.py index 32b173a..66482e2 100644 --- a/captain/click/_iso.py +++ b/captain/click/_iso.py @@ -58,8 +58,9 @@ def build_cmd( flavor_id: str, project_dir: str | None, verbose: bool, + builder_registry: str | None, + builder_repository: str | None, builder_image: str, - no_cache: bool, iso_mode: str, force_iso: bool, ) -> None: @@ -73,8 +74,9 @@ def build_cmd( output_dir=proj / "out", arch=arch, flavor_id=flavor_id, + builder_registry=builder_registry, + builder_repository=builder_repository, builder_image=builder_image, - no_cache=no_cache, iso_mode=iso_mode, force_iso=force_iso, ) diff --git a/captain/click/_main.py b/captain/click/_main.py index cd9928d..7895a93 100644 --- a/captain/click/_main.py +++ b/captain/click/_main.py @@ -50,12 +50,33 @@ def common_options(fn: Any) -> Any: help="Project root directory (auto-detected when omitted).", ) @click.option( - "-v", "--verbose", + "-v", is_flag=True, default=False, help="Enable debug-level logging.", ) + @click.option( + "--builder-registry", + envvar="REGISTRY", + default="ghcr.io", + show_default=True, + help="OCI registry hostname for the Docker builder image", + ) + @click.option( + "--builder-repository", + envvar="GITHUB_REPOSITORY", + default="tinkerbell/captain", + show_default=True, + help="Repository path (owner/name) for the Docker builder image", + ) + @click.option( + "--builder-image", + envvar="BUILDER_IMAGE", + default="captainos-builder", + show_default=True, + help="Local name/tag of Docker builder image name", + ) @functools.wraps(fn) def wrapper(**kwargs: Any) -> Any: return fn(**kwargs) diff --git a/captain/click/_release_publish.py b/captain/click/_release_publish.py index ca19dc0..2d2370e 100644 --- a/captain/click/_release_publish.py +++ b/captain/click/_release_publish.py @@ -22,20 +22,6 @@ short_help="Publish build artifacts as a multi-arch OCI image.", ) @common_options -@click.option( - "--builder-image", - envvar="BUILDER_IMAGE", - default="captainos-builder", - show_default=True, - help="Docker builder image name.", -) -@click.option( - "--no-cache", - envvar="NO_CACHE", - is_flag=True, - default=False, - help="Rebuild images without Docker layer cache.", -) @click.option( "--release-mode", envvar="RELEASE_MODE", @@ -100,8 +86,9 @@ def release_publish_cmd( flavor_id: str, project_dir: str | None, verbose: bool, + builder_registry: str | None, + builder_repository: str | None, builder_image: str, - no_cache: bool, release_mode: str, registry: str, repository: str, @@ -140,8 +127,9 @@ def release_publish_cmd( output_dir=proj / "out", arch=arch, flavor_id=flavor_id, + builder_registry=builder_registry, + builder_repository=builder_repository, builder_image=builder_image, - no_cache=no_cache, release_mode=release_mode, ) @@ -154,7 +142,7 @@ def release_publish_cmd( if cfg.release_mode == "docker": from captain import docker - docker.build_release_image(cfg) + docker.obtain_builder(cfg) sha = _resolve_git_sha(git_sha, proj) env_args: list[str] = [ @@ -179,12 +167,12 @@ def release_publish_cmd( inner_cmd = ["/work/build.py", "release", "publish"] try: - docker.run_in_release( + docker.run_in_builder( cfg, *env_args, "--entrypoint", "/usr/bin/uv", - docker.RELEASE_IMAGE, + cfg.builder_image, *(["--verbose"] if log.isEnabledFor(logging.DEBUG) else []), "run", *inner_cmd, diff --git a/captain/click/_stages.py b/captain/click/_stages.py index 3c39954..4296343 100644 --- a/captain/click/_stages.py +++ b/captain/click/_stages.py @@ -26,7 +26,7 @@ def _build_tools_stage(cfg: Config) -> None: return # --- docker ------------------------------------------------------- - docker.build_builder(cfg) + docker.obtain_builder(cfg) log.info("Downloading tools (nerdctl, containerd, etc.) docker...") docker.run_in_builder( cfg, @@ -75,7 +75,7 @@ def _build_mkosi_stage(cfg: Config, extra_args: list[str]) -> None: return # --- docker ------------------------------------------------------- - docker.build_builder(cfg) + docker.obtain_builder(cfg) log.info("Building initrd with mkosi (docker)...") tools_tree = f"/work/mkosi.output/tools/{cfg.arch}" output_dir = f"/work/mkosi.output/initramfs/{cfg.flavor_id}/{cfg.arch}" @@ -117,7 +117,7 @@ def _build_iso_stage(cfg: Config) -> None: return # --- docker ------------------------------------------------------- - docker.build_builder(cfg) + docker.obtain_builder(cfg) log.info("Building ISO (docker)...") docker.run_in_builder( cfg, diff --git a/captain/click/_tools.py b/captain/click/_tools.py index ada986b..92d18e3 100644 --- a/captain/click/_tools.py +++ b/captain/click/_tools.py @@ -54,8 +54,9 @@ def tools_cmd( flavor_id: str, project_dir: str | None, verbose: bool, + builder_registry: str | None, + builder_repository: str | None, builder_image: str, - no_cache: bool, tools_mode: str, force_tools: bool, ) -> None: @@ -82,8 +83,9 @@ def tools_cmd( output_dir=proj / "out", arch=arch, flavor_id=flavor_id, + builder_registry=builder_registry, + builder_repository=builder_repository, builder_image=builder_image, - no_cache=no_cache, tools_mode=tools_mode, force_tools=force_tools, ) diff --git a/captain/config.py b/captain/config.py index 1baeff4..f0418d4 100644 --- a/captain/config.py +++ b/captain/config.py @@ -2,9 +2,7 @@ from __future__ import annotations -import argparse import logging -import os import sys from dataclasses import dataclass, field from pathlib import Path @@ -34,8 +32,10 @@ class Config: flavor_id: str = DEFAULT_FLAVOR_ID # Docker + builder_registry: str | None = None + builder_repository: str | None = None builder_image: str = "captainos-builder" - no_cache: bool = False + builder_push: bool = False # Per-stage mode: "docker" | "native" | "skip" tools_mode: str = "docker" @@ -71,82 +71,6 @@ def __post_init__(self) -> None: log.error("%s=%r is invalid. Valid values: %s", name, value, ", ".join(VALID_MODES)) sys.exit(1) - @property - def needs_docker(self) -> bool: - """True if any stage requires Docker.""" - return ( - self.tools_mode == "docker" - or self.mkosi_mode == "docker" - or self.iso_mode == "docker" - or self.release_mode == "docker" - ) - - @classmethod - def from_args(cls, args: argparse.Namespace, project_dir: Path | None) -> Config: - """Create a Config from a parsed :class:`argparse.Namespace`. - - The *args* namespace is produced by :mod:`configargparse` which - has already resolved the priority chain: - CLI flags > environment variables > defaults. - - ``getattr`` with fallbacks is used because per-subcommand - parsers only define the flags relevant to that subcommand. - """ - - if project_dir is None: - raise ValueError("project_dir must be provided to Config.from_args") - - log.debug( - "Creating Config from args: %s (env flavor: %s)", args, os.environ.get("FLAVOR_ID") - ) - - return cls( - project_dir=project_dir, - output_dir=project_dir / "out", - arch=getattr(args, "arch", "amd64"), - flavor_id=getattr(args, "flavor_id", DEFAULT_FLAVOR_ID), - builder_image=getattr(args, "builder_image", "captainos-builder"), - no_cache=getattr(args, "no_cache", False), - tools_mode=getattr(args, "tools_mode", "docker"), - mkosi_mode=getattr(args, "mkosi_mode", "docker"), - iso_mode=getattr(args, "iso_mode", "docker"), - release_mode=getattr(args, "release_mode", "docker"), - force_tools=getattr(args, "force_tools", False), - force_iso=getattr(args, "force_iso", False), - qemu_append=getattr(args, "qemu_append", ""), - qemu_mem=getattr(args, "qemu_mem", "2G"), - qemu_smp=getattr(args, "qemu_smp", "2"), - ) - - @classmethod - def from_env(cls, project_dir: Path) -> Config: - """Create a Config from environment variables (legacy helper). - - Prefer :meth:`from_args` in the CLI path. This method remains - for any non-CLI callers (e.g. tests, scripts) that need a - ``Config`` without going through argparse. - """ - - log.debug("Creating Config from env: %s", os.environ) - - return cls( - project_dir=project_dir, - output_dir=project_dir / "out", - arch=os.environ.get("ARCH", "amd64"), - flavor_id=os.environ.get("FLAVOR_ID", DEFAULT_FLAVOR_ID), - builder_image=os.environ.get("BUILDER_IMAGE", "captainos-builder"), - no_cache=os.environ.get("NO_CACHE") == "1", - tools_mode=os.environ.get("TOOLS_MODE", "docker"), - mkosi_mode=os.environ.get("MKOSI_MODE", "docker"), - iso_mode=os.environ.get("ISO_MODE", "docker"), - release_mode=os.environ.get("RELEASE_MODE", "docker"), - force_tools=os.environ.get("FORCE_TOOLS") == "1", - force_iso=os.environ.get("FORCE_ISO") == "1", - qemu_append=os.environ.get("QEMU_APPEND", ""), - qemu_mem=os.environ.get("QEMU_MEM", "2G"), - qemu_smp=os.environ.get("QEMU_SMP", "2"), - ) - @property def tools_output(self) -> Path: """Per-arch staging directory for downloaded tools. diff --git a/captain/docker.py b/captain/docker.py index a93c881..83a4a92 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -9,7 +9,7 @@ from pathlib import Path from captain.config import Config -from captain.util import run +from captain.util import detect_current_machine_arch, run log = logging.getLogger(__name__) @@ -28,46 +28,89 @@ def _dockerfile_hash(cfg: Config) -> str: """Return the SHA-256 hex digest of the Dockerfile content. This is used as an image tag so that Dockerfile changes are detected - automatically. The value intentionally matches what GitHub Actions - ``hashFiles('Dockerfile')`` produces, allowing the CI - ``docker/build-push-action`` step to pre-load an image with the same - tag that ``build_builder`` will look for. + automatically. """ dockerfile = cfg.project_dir / "Dockerfile" - return hashlib.sha256(dockerfile.read_bytes()).hexdigest() + local_arch = detect_current_machine_arch() + hex_digest = hashlib.sha256(dockerfile.read_bytes()).hexdigest() + return f"{local_arch}-{hex_digest}" -def build_builder(cfg: Config) -> None: +def obtain_builder(cfg: Config) -> None: """Build the Docker builder image when the Dockerfile has changed. The image is tagged with a content hash of the Dockerfile so that changes are detected even when the base image name stays the same. - When the matching tag already exists locally (e.g. pre-loaded by a CI - ``docker/build-push-action`` step with ``load: true``), we skip the - build entirely. Use ``NO_CACHE=1`` to force a full rebuild. """ tag = _dockerfile_hash(cfg) - tagged_image = f"{cfg.builder_image}:{tag}" + remote_tagged_image = f"{cfg.builder_registry}/{cfg.builder_repository}/builder:{tag}" + local_tagged_image = f"{cfg.builder_image}:{tag}" + + log.debug( + "Checking for existing builder image with tag '%s' or remote image '%s'", + local_tagged_image, + remote_tagged_image, + ) - if not cfg.no_cache and _image_exists(tagged_image): - log.info("Docker image '%s' is up to date with %s.", cfg.builder_image, tagged_image) + if _image_exists(local_tagged_image): + log.info("Docker image '%s' is up to date with %s.", cfg.builder_image, local_tagged_image) # Ensure the un-hashed tag exists so later docker-run calls that # reference cfg.builder_image (without the hash suffix) succeed. - # This matters when the hashed tag was pre-loaded by CI. - run(["docker", "tag", tagged_image, cfg.builder_image], check=False) + run(["docker", "tag", local_tagged_image, cfg.builder_image], check=False) + return + + # Check if the remote name exists locally... (was pre-pulled somehow) + if _image_exists(remote_tagged_image): + log.info( + "Docker image '%s' already exists locally (pre-pulled). Tagging as '%s'.", + remote_tagged_image, + cfg.builder_image, + ) + run(["docker", "tag", remote_tagged_image, cfg.builder_image], check=False) + return + + # Check if we can pull the remote image (exists in registry and matches our Dockerfile hash) + if ( + run( + ["docker", "pull", remote_tagged_image], + check=False, + capture=False, + ).returncode + == 0 + ): + log.info( + "Pulled Docker image '%s' from registry. Tagging as '%s'.", + remote_tagged_image, + cfg.builder_image, + ) + run(["docker", "tag", remote_tagged_image, cfg.builder_image], check=False) return + # build locally if no existing image was found. log.info("Building Docker image '%s'...", cfg.builder_image) - cmd = ["docker", "buildx", "build"] - if cfg.no_cache: - cmd.append("--no-cache") - cmd.extend( - ["--progress=plain", "-t", tagged_image, "-t", cfg.builder_image, str(cfg.project_dir)] + run( + [ + "docker", + "buildx", + "build", + "--progress=plain", + "-t", + local_tagged_image, + "-t", + cfg.builder_image, + str(cfg.project_dir), + ] ) - run(cmd) - -RELEASE_IMAGE = "captainos-release" + # Optionally push the image after building it + if cfg.builder_push: + log.info( + "Pushing Docker image '%s' to registry as '%s'...", + cfg.builder_image, + remote_tagged_image, + ) + run(["docker", "tag", local_tagged_image, remote_tagged_image], check=False) + run(["docker", "push", remote_tagged_image]) def _release_dockerfile_hash(cfg: Config) -> str: @@ -76,25 +119,6 @@ def _release_dockerfile_hash(cfg: Config) -> str: return hashlib.sha256(dockerfile.read_bytes()).hexdigest() -def build_release_image(cfg: Config) -> None: - """Build the release Docker image from ``Dockerfile.release``.""" - tag = _release_dockerfile_hash(cfg) - tagged_image = f"{RELEASE_IMAGE}:{tag}" - - if not cfg.no_cache and _image_exists(tagged_image): - log.info("Docker image '%s' is up to date.", RELEASE_IMAGE) - run(["docker", "tag", tagged_image, RELEASE_IMAGE]) - return - - log.info("Building Docker image '%s'...", RELEASE_IMAGE) - cmd = ["docker", "buildx", "build", "-f", str(cfg.project_dir / "Dockerfile.release")] - if cfg.no_cache: - cmd.append("--no-cache") - cmd.extend(["--progress=plain"]) - cmd.extend(["-t", tagged_image, "-t", RELEASE_IMAGE, str(cfg.project_dir)]) - run(cmd) - - def run_in_release(cfg: Config, *extra_args: str) -> None: """Run a command inside the release container. From 0c9d2829496a722ef46dd2ba23b575ddfa1c24ed Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 11:35:16 +0200 Subject: [PATCH 54/93] gha: separate build-dockerfile arch-based matrix job Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9dc8e6..7093d79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,12 +83,40 @@ jobs: mkosi.output/tools/${{ matrix.arch }}/opt/cni retention-days: 1 + # ------------------------------------------------------------------- + # Build Docker builder image, per-arch; pushes to ghcr.io. + # ------------------------------------------------------------------- + build-dockerfile: + needs: [ lint ] + runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} + strategy: + fail-fast: false + matrix: { arch: [ amd64, arm64 ] } + env: + ARCH: ${{ matrix.arch }} + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Log in to GHCR + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build Dockerfile and push + run: uv run captain builder --push + # ------------------------------------------------------------------- # Build initramfs via mkosi (depends on tools) # ------------------------------------------------------------------- build-all: runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} - needs: [ download-tools ] + needs: [ download-tools, build-dockerfile ] strategy: fail-fast: false matrix: From decb2f0b6b1c43cd3c6c7c66bfc1e917449449cb Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 11:51:11 +0200 Subject: [PATCH 55/93] captain: adapt to options moved to common Signed-off-by: Ricardo Pardini --- captain/click/_build.py | 21 --------------------- captain/click/_builder.py | 9 --------- captain/click/_iso.py | 21 --------------------- captain/click/_main.py | 7 ------- captain/click/_release_publish.py | 7 ------- captain/click/_tools.py | 21 --------------------- 6 files changed, 86 deletions(-) diff --git a/captain/click/_build.py b/captain/click/_build.py index 1414b40..ffdfcaf 100644 --- a/captain/click/_build.py +++ b/captain/click/_build.py @@ -24,20 +24,6 @@ short_help="Run the full build pipeline via mkosi.", ) @common_options -@click.option( - "--builder-image", - envvar="BUILDER_IMAGE", - default="captainos-builder", - show_default=True, - help="Docker builder image name.", -) -@click.option( - "--no-cache", - envvar="NO_CACHE", - is_flag=True, - default=False, - help="Rebuild the builder image without Docker layer cache.", -) @click.option( "--mkosi-mode", envvar="MKOSI_MODE", @@ -91,7 +77,6 @@ def build_cmd( arch: str, flavor_id: str, project_dir: str | None, - verbose: bool, builder_registry: str | None, builder_repository: str | None, builder_image: str, @@ -117,7 +102,6 @@ def build_cmd( captain build --mkosi-mode native --tools-mode native captain build --force --force-tools """ - _configure_logging(verbose) proj = resolve_project_dir(project_dir) @@ -162,8 +146,3 @@ def build_cmd( # Final artifact collection. artifacts.collect(cfg) log.info("Build complete!") - - -def _configure_logging(verbose: bool) -> None: - level = logging.DEBUG if verbose else logging.INFO - logging.getLogger("captain").setLevel(level) diff --git a/captain/click/_builder.py b/captain/click/_builder.py index 858bff2..ecd6ef5 100644 --- a/captain/click/_builder.py +++ b/captain/click/_builder.py @@ -29,7 +29,6 @@ def builder_cmd( arch: str, flavor_id: str, project_dir: str | None, - verbose: bool, builder_registry: str | None, builder_repository: str | None, builder_image: str, @@ -47,8 +46,6 @@ def builder_cmd( captain builder --no-cache captain builder --push """ - _configure_logging(verbose) - proj = resolve_project_dir(project_dir) cfg = Config( @@ -65,9 +62,3 @@ def builder_cmd( # 1. Build the image. obtain_builder(cfg) log.info("Builder image '%s' is ready.", cfg.builder_image) - - -def _configure_logging(verbose: bool) -> None: - """Set the captain logger level based on the --verbose flag.""" - level = logging.DEBUG if verbose else logging.INFO - logging.getLogger("captain").setLevel(level) diff --git a/captain/click/_iso.py b/captain/click/_iso.py index 66482e2..46a353f 100644 --- a/captain/click/_iso.py +++ b/captain/click/_iso.py @@ -22,20 +22,6 @@ short_help="Build ISO image only. Part of build.", ) @common_options -@click.option( - "--builder-image", - envvar="BUILDER_IMAGE", - default="captainos-builder", - show_default=True, - help="Docker builder image name.", -) -@click.option( - "--no-cache", - envvar="NO_CACHE", - is_flag=True, - default=False, - help="Rebuild the builder image without Docker layer cache.", -) @click.option( "--iso-mode", envvar="ISO_MODE", @@ -57,7 +43,6 @@ def build_cmd( arch: str, flavor_id: str, project_dir: str | None, - verbose: bool, builder_registry: str | None, builder_repository: str | None, builder_image: str, @@ -65,7 +50,6 @@ def build_cmd( force_iso: bool, ) -> None: """Run the CaptainOS ISO build.""" - _configure_logging(verbose) proj = resolve_project_dir(project_dir) @@ -87,8 +71,3 @@ def build_cmd( _build_iso_stage(cfg) artifacts.collect_iso(cfg) log.info("ISO build complete!!!") - - -def _configure_logging(verbose: bool) -> None: - level = logging.DEBUG if verbose else logging.INFO - logging.getLogger("captain").setLevel(level) diff --git a/captain/click/_main.py b/captain/click/_main.py index 7895a93..a07f47f 100644 --- a/captain/click/_main.py +++ b/captain/click/_main.py @@ -49,13 +49,6 @@ def common_options(fn: Any) -> Any: type=click.Path(exists=True, file_okay=False, resolve_path=True), help="Project root directory (auto-detected when omitted).", ) - @click.option( - "--verbose", - "-v", - is_flag=True, - default=False, - help="Enable debug-level logging.", - ) @click.option( "--builder-registry", envvar="REGISTRY", diff --git a/captain/click/_release_publish.py b/captain/click/_release_publish.py index 2d2370e..884590b 100644 --- a/captain/click/_release_publish.py +++ b/captain/click/_release_publish.py @@ -85,7 +85,6 @@ def release_publish_cmd( arch: str, flavor_id: str, project_dir: str | None, - verbose: bool, builder_registry: str | None, builder_repository: str | None, builder_image: str, @@ -115,7 +114,6 @@ def release_publish_cmd( captain release-publish --target combined --force captain release-publish --registry ghcr.io --repository tinkerbell/captain """ - _configure_logging(verbose) proj = resolve_project_dir(project_dir) @@ -225,8 +223,3 @@ def _resolve_git_sha(sha: str | None, project_dir: Path) -> str: cwd=project_dir, ) return result.stdout.strip() - - -def _configure_logging(verbose: bool) -> None: - level = logging.DEBUG if verbose else logging.INFO - logging.getLogger("captain").setLevel(level) diff --git a/captain/click/_tools.py b/captain/click/_tools.py index 92d18e3..3aec481 100644 --- a/captain/click/_tools.py +++ b/captain/click/_tools.py @@ -18,20 +18,6 @@ short_help="Download tools (containerd, runc, nerdctl, CNI).", ) @common_options -@click.option( - "--builder-image", - envvar="BUILDER_IMAGE", - default="captainos-builder", - show_default=True, - help="Docker builder image name.", -) -@click.option( - "--no-cache", - envvar="NO_CACHE", - is_flag=True, - default=False, - help="Rebuild the builder image without Docker layer cache.", -) @click.option( "--tools-mode", envvar="TOOLS_MODE", @@ -53,7 +39,6 @@ def tools_cmd( arch: str, flavor_id: str, project_dir: str | None, - verbose: bool, builder_registry: str | None, builder_repository: str | None, builder_image: str, @@ -74,7 +59,6 @@ def tools_cmd( captain tools --tools-mode native captain tools --force-tools """ - _configure_logging(verbose) proj = resolve_project_dir(project_dir) @@ -92,8 +76,3 @@ def tools_cmd( _build_tools_stage(cfg) log.info("Tools stage complete!") - - -def _configure_logging(verbose: bool) -> None: - level = logging.DEBUG if verbose else logging.INFO - logging.getLogger("captain").setLevel(level) From 899c25a9c841c12d8f4bb3e094e0305da0501279 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 12:04:41 +0200 Subject: [PATCH 56/93] captain: simplify Rich logging and trace handler captain: simplify Rich logging and trace handler; let's not have two `click` packages Signed-off-by: Ricardo Pardini --- build.py | 10 +++--- captain/__init__.py | 38 ++++++++++------------ captain/{click => cli}/__init__.py | 2 +- captain/{click => cli}/_build.py | 4 +-- captain/{click => cli}/_builder.py | 2 +- captain/{click => cli}/_iso.py | 4 +-- captain/{click => cli}/_main.py | 2 +- captain/{click => cli}/_release_publish.py | 2 +- captain/{click => cli}/_stages.py | 0 captain/{click => cli}/_tools.py | 4 +-- 10 files changed, 33 insertions(+), 35 deletions(-) rename captain/{click => cli}/__init__.py (93%) rename captain/{click => cli}/_build.py (97%) rename captain/{click => cli}/_builder.py (95%) rename captain/{click => cli}/_iso.py (93%) rename captain/{click => cli}/_main.py (97%) rename captain/{click => cli}/_release_publish.py (98%) rename captain/{click => cli}/_stages.py (100%) rename captain/{click => cli}/_tools.py (93%) diff --git a/build.py b/build.py index 07f38da..2375ec0 100755 --- a/build.py +++ b/build.py @@ -3,10 +3,10 @@ Requires: Python >= 3.13 and a lot of dependencies; Use Astral's ``uv`` to run:: - uv run click_cli.py --help - uv run click_cli.py builder - uv run click_cli.py build --arch arm64 - uv run click_cli.py release-publish --target combined + uv run build.py --help + uv run build.py builder + uv run build.py build --arch arm64 + uv run build.py release-publish --target combined """ import sys @@ -16,7 +16,7 @@ sys.exit(1) try: - from captain.click import main + from captain.cli import main except ImportError as exc: print(f"ERROR: {exc}", file=sys.stderr) uv_url = "https://docs.astral.sh/uv/getting-started/installation/" diff --git a/captain/__init__.py b/captain/__init__.py index 4cdd54e..efb57c6 100644 --- a/captain/__init__.py +++ b/captain/__init__.py @@ -9,6 +9,7 @@ import logging import os +import click from rich.console import Console from rich.logging import RichHandler from rich.traceback import install as _install_rich_traceback @@ -21,12 +22,12 @@ console: Console = Console(stderr=True, color_system="standard", width=160, highlight=False) # Install Rich traceback handler globally (once, at import time). -_install_rich_traceback(console=console, show_locals=True, width=None) +_install_rich_traceback( + console=console, show_locals=False, width=None, suppress=[click], max_frames=2 +) class _StageFormatter(logging.Formatter): - """Show the module path relative to the ``captain`` package as a prefix.""" - def format(self, record: logging.LogRecord) -> str: name = record.name record.__dict__["stage"] = name @@ -36,20 +37,17 @@ def format(self, record: logging.LogRecord) -> str: return super().format(record) -# Configure the ``captain`` logger hierarchy once. -_root = logging.getLogger("captain") - -if not _root.handlers: - _handler = RichHandler( - console=console, - show_time=False, - show_level=True, - show_path=True, - markup=True, # interprets [braket]stuff[/bracket] in log messages, beware - rich_tracebacks=True, - tracebacks_show_locals=True, - ) - _handler.setFormatter(_StageFormatter("%(stage)s: %(message)s")) - _root.addHandler(_handler) - _root.setLevel(logging.DEBUG) - _root.propagate = False +_handler = RichHandler( + console=console, + show_time=False, + show_level=True, + show_path=True, + markup=True, # interprets [braket]stuff[/bracket] in log messages, beware + rich_tracebacks=True, + tracebacks_show_locals=False, + tracebacks_max_frames=2, # too many frames and we get confused + tracebacks_suppress=[click], # don't wanna see click infra code in traces +) +_handler.setFormatter(_StageFormatter("%(stage)s: %(message)s")) + +logging.basicConfig(level="DEBUG", datefmt="[%X]", handlers=[_handler]) diff --git a/captain/click/__init__.py b/captain/cli/__init__.py similarity index 93% rename from captain/click/__init__.py rename to captain/cli/__init__.py index 50adb24..442221b 100644 --- a/captain/click/__init__.py +++ b/captain/cli/__init__.py @@ -15,6 +15,6 @@ eval "$(_CAPTAIN_COMPLETE=zsh_source captain)" """ -from captain.click._main import main +from captain.cli._main import main __all__ = ["main"] diff --git a/captain/click/_build.py b/captain/cli/_build.py similarity index 97% rename from captain/click/_build.py rename to captain/cli/_build.py index ffdfcaf..ee356a7 100644 --- a/captain/click/_build.py +++ b/captain/cli/_build.py @@ -8,8 +8,8 @@ import captain.flavor from captain import artifacts -from captain.click._main import cli, common_options, resolve_project_dir -from captain.click._stages import ( +from captain.cli._main import cli, common_options, resolve_project_dir +from captain.cli._stages import ( _build_iso_stage, _build_mkosi_stage, _build_tools_stage, diff --git a/captain/click/_builder.py b/captain/cli/_builder.py similarity index 95% rename from captain/click/_builder.py rename to captain/cli/_builder.py index ecd6ef5..1caad5e 100644 --- a/captain/click/_builder.py +++ b/captain/cli/_builder.py @@ -6,7 +6,7 @@ import click -from captain.click._main import cli, common_options, resolve_project_dir +from captain.cli._main import cli, common_options, resolve_project_dir from captain.config import Config from captain.docker import obtain_builder diff --git a/captain/click/_iso.py b/captain/cli/_iso.py similarity index 93% rename from captain/click/_iso.py rename to captain/cli/_iso.py index 46a353f..1081acd 100644 --- a/captain/click/_iso.py +++ b/captain/cli/_iso.py @@ -8,8 +8,8 @@ import captain.flavor from captain import artifacts -from captain.click._main import cli, common_options, resolve_project_dir -from captain.click._stages import ( +from captain.cli._main import cli, common_options, resolve_project_dir +from captain.cli._stages import ( _build_iso_stage, ) from captain.config import Config diff --git a/captain/click/_main.py b/captain/cli/_main.py similarity index 97% rename from captain/click/_main.py rename to captain/cli/_main.py index a07f47f..0fe79ca 100644 --- a/captain/click/_main.py +++ b/captain/cli/_main.py @@ -131,6 +131,6 @@ def cli(ctx: click.Context) -> None: def main() -> None: """Console-script entry point.""" # Import subcommand modules to register them on the group. - from captain.click import _build, _builder, _iso, _release_publish, _tools # noqa: F401 + from captain.cli import _build, _builder, _iso, _release_publish, _tools # noqa: F401 cli() diff --git a/captain/click/_release_publish.py b/captain/cli/_release_publish.py similarity index 98% rename from captain/click/_release_publish.py rename to captain/cli/_release_publish.py index 884590b..f8da0b7 100644 --- a/captain/click/_release_publish.py +++ b/captain/cli/_release_publish.py @@ -10,7 +10,7 @@ import captain.flavor from captain import oci -from captain.click._main import cli, common_options, resolve_project_dir +from captain.cli._main import cli, common_options, resolve_project_dir from captain.config import Config from captain.util import check_release_dependencies diff --git a/captain/click/_stages.py b/captain/cli/_stages.py similarity index 100% rename from captain/click/_stages.py rename to captain/cli/_stages.py diff --git a/captain/click/_tools.py b/captain/cli/_tools.py similarity index 93% rename from captain/click/_tools.py rename to captain/cli/_tools.py index 3aec481..187bd31 100644 --- a/captain/click/_tools.py +++ b/captain/cli/_tools.py @@ -6,8 +6,8 @@ import click -from captain.click._main import cli, common_options, resolve_project_dir -from captain.click._stages import _build_tools_stage +from captain.cli._main import cli, common_options, resolve_project_dir +from captain.cli._stages import _build_tools_stage from captain.config import Config log = logging.getLogger(__name__) From 4f4f4acd5289489472eadcd5ae286d9a1d040521 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 12:12:47 +0200 Subject: [PATCH 57/93] gha: force tools to run native Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7093d79..6a5647c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,7 @@ jobs: uses: astral-sh/setup-uv@v7 - name: Download tools - run: uv run captain tools + run: uv run captain tools --tools-mode=native - name: Save tools cache if: github.ref == 'refs/heads/main' && steps.tools-cache.outputs.cache-hit != 'true' From a5929e0788e29289b12d32777a8ab93c6fbeba40 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 14:33:23 +0200 Subject: [PATCH 58/93] docker/Dockerfile: consolidate single Dockerfile; split and balance layers to optimize for parallel pulls Signed-off-by: Ricardo Pardini --- Dockerfile | 129 ++++++++++++++++++++++---------- Dockerfile.release | 46 ------------ captain/cli/_release_publish.py | 4 +- captain/docker.py | 99 +++++++++++------------- 4 files changed, 137 insertions(+), 141 deletions(-) delete mode 100644 Dockerfile.release diff --git a/Dockerfile b/Dockerfile index 6750a78..4199681 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,23 @@ # Builder container for CaptainOS using mkosi # Encapsulates all mkosi dependencies for reproducible builds. -# Usage: docker build -t captainos-builder . && docker run --rm --privileged -v $(pwd):/work captainos-builder build +# Includes skopeo and buildah for OCI image manipulation, and uv for Python tool management. FROM debian:trixie -ARG MKOSI_VERSION=v26 - # Avoid interactive prompts -ENV DEBIAN_FRONTEND=noninteractive +ENV DEBIAN_FRONTEND=noninteractive BUILDAH_ISOLATION=chroot FORCE_COLOR=1 -# Install mkosi runtime dependencies and kernel build dependencies in one layer -RUN apt-get -o "Dpkg::Use-Pty=0" update && apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends \ +# Add foreign architecture for cross-compilation (arm64 on amd64 and vice versa) and apt-update +# Immediately install the cross-arch grub dependencies +RUN <<-FRAGMENT_WITH_VARIABLES +# Determine arch/cross-arch and install grub and other basic packages (around 200mb layer) +NATIVE_ARCH="$(dpkg --print-architecture)" +FOREIGN_ARCH=$([ "$NATIVE_ARCH" = "amd64" ] && echo "arm64" || echo "amd64") +dpkg --add-architecture "$FOREIGN_ARCH" +apt-get -o "Dpkg::Use-Pty=0" update +apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends \ + "grub-efi-${NATIVE_ARCH}-bin" \ + "grub-efi-${FOREIGN_ARCH}-bin:${FOREIGN_ARCH}" \ + grub-common \ apt \ dpkg \ debian-archive-keyring \ @@ -21,66 +29,111 @@ RUN apt-get -o "Dpkg::Use-Pty=0" update && apt-get -o "Dpkg::Use-Pty=0" install systemd-container \ systemd \ udev \ - bubblewrap \ squashfs-tools \ mtools \ erofs-utils \ dosfstools \ e2fsprogs \ btrfs-progs \ - # Kernel build deps - build-essential \ - gcc \ - gcc-aarch64-linux-gnu \ + tree +FRAGMENT_WITH_VARIABLES + +# Cross-architecture support (arm64 on x86_64 and vice versa) - huge single package +RUN <<-QEMU_USER_FRAGMENT +# Install qemu-user but then delete all un-needed qemu binaries to save space (we only need aarch64 and x86_64) +apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends qemu-user +echo 'All qemus: ' +ls -lah /usr/bin/qemu-* +# keep only qemu binary for the arches we're interested in: aarch64 and x86_64 +echo 'To be deleted: ' +find /usr/bin -name 'qemu-*' -not -name 'qemu-aarch64' -not -name 'qemu-x86_64' -not -name 'qemu-arm*' -not -name 'qemu-amd*' -print0 | xargs -0 ls -lah +echo 'Deleting: ' +find /usr/bin -name 'qemu-*' -not -name 'qemu-aarch64' -not -name 'qemu-x86_64' -not -name 'qemu-arm*' -not -name 'qemu-amd*' -print0 | xargs -0 rm -fv +echo 'Remaining: ' +ls -lah /usr/bin/qemu-* + +QEMU_USER_FRAGMENT + +# Extra kernel build tools +RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends \ make \ flex \ bison \ bc \ libelf-dev \ libssl-dev \ + dpkg-dev \ dwarves \ - pahole \ + pahole + +# Those are pulled by build-essential (cross...), but are quite big; pull them ealier to balance layer size +RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends \ + binutils-common \ + libasan8 \ + liblsan0 \ + libubsan1 \ + libhwasan0 \ + binutils-x86-64-linux-gnu \ + libasan8-amd64-cross \ + liblsan0-amd64-cross \ + libtsan2-amd64-cross \ + libc6-amd64-cross \ + linux-libc-dev-amd64-cross \ + libc6-dev-amd64-cross + +RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends \ rsync \ coreutils \ - # Cross-architecture support (arm64 on x86_64 and vice versa) - qemu-user-static \ - # Network tools (for fetching kernel source etc.) git \ curl \ ca-certificates \ - # Binary compression + qemu-user-static + +# Then both, of which one will already be fulfilled +RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends crossbuild-essential-arm64 +RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends crossbuild-essential-amd64 + +# Buildah and Skopeo +# Binary compression +# ISO image creation +# Kernel build deps: build-essential +RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends \ + build-essential \ + containernetworking-plugins \ + bubblewrap \ + skopeo \ upx-ucl \ - # ISO image creation - xorriso \ - grub-common \ - && NATIVE_ARCH="$(dpkg --print-architecture)" \ - && FOREIGN_ARCH=$([ "$NATIVE_ARCH" = "amd64" ] && echo "arm64" || echo "amd64") \ - && apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends "grub-efi-${NATIVE_ARCH}-bin" \ - && dpkg --add-architecture "$FOREIGN_ARCH" \ - && apt-get -o "Dpkg::Use-Pty=0" update \ - && apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends "grub-efi-${FOREIGN_ARCH}-bin:${FOREIGN_ARCH}" \ - && rm -rf /var/lib/apt/lists/* + xorriso -# Install astral-sh's uv with a script - install to /usr for global access -RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="/usr/bin" sh +RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends \ + buildah -# Verify uv is functional -RUN uv --version +RUN <<-CONFIG_FRAG +## A few small config fragments to make life easier +# git: Ignore owner mismatches in /work, which will be bind-mounted from the host +git config --global --add safe.directory /work \ +# buildah: Configure rootless storage driver and chroot isolation (no user-namespace required — we only assemble scratch images, never RUN anything inside them). +printf '[storage]\ndriver = "vfs"\nrunroot = "/var/tmp/buildah-runroot"\ngraphroot = "/var/tmp/buildah-storage"\n' > /etc/containers/storage.conf +# Buildah 1.39+ on Debian requires netavark but we never need networking +# (all images are FROM scratch with no RUN steps). A no-op stub satisfies +# the startup check. +mkdir -p /usr/libexec/podman +printf '#!/bin/sh\nexit 0\n' > /usr/libexec/podman/netavark +chmod +x /usr/libexec/podman/netavark +CONFIG_FRAG -# Install mkosi from GitHub (not on PyPI) via uv; symlink to /usr/bin for global access -RUN uv tool install "git+https://github.com/systemd/mkosi.git@${MKOSI_VERSION}" -RUN ln -sf ~/.local/bin/mkosi /usr/bin/mkosi +# Install astral-sh's uv with a script - install to /usr for global access +RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="/usr/bin" sh && uv --version -# Verify mkosi is functional -RUN mkosi --version +# Install mkosi from GitHub (not on PyPI) via uv; symlink to /usr/bin for global access +ARG MKOSI_VERSION=v26 +RUN uv tool install "git+https://github.com/systemd/mkosi.git@${MKOSI_VERSION}" && ln -sf ~/.local/bin/mkosi /usr/bin/mkosi && mkosi --version # Prime uv's cache with our pyproject.toml to speed up runtime COPY pyproject.toml /tmp/pyproject.toml COPY captain /tmp/captain COPY build.py /tmp/build.py WORKDIR /tmp -RUN uv --verbose run build.py --help +RUN uv --verbose run captain --version WORKDIR /work -ENTRYPOINT ["mkosi"] -CMD ["build"] diff --git a/Dockerfile.release b/Dockerfile.release deleted file mode 100644 index b63204c..0000000 --- a/Dockerfile.release +++ /dev/null @@ -1,46 +0,0 @@ -# Lightweight container for OCI release operations (publish, index, pull, tag). -# -# Usage: -# docker build -f Dockerfile.release -t captainos-release . -# docker run --rm -v $(pwd):/work captainos-release release publish -FROM debian:trixie - -# Install buildah, skopeo, curl and git -RUN apt-get -o "Dpkg::Use-Pty=0" update && apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends \ - buildah \ - skopeo \ - curl \ - git \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* \ - && git config --global --add safe.directory /work - -# Configure rootless storage driver and chroot isolation (no user-namespace -# required — we only assemble scratch images, never RUN anything inside them). -RUN printf '[storage]\ndriver = "vfs"\nrunroot = "/var/tmp/buildah-runroot"\ngraphroot = "/var/tmp/buildah-storage"\n' \ - > /etc/containers/storage.conf -ENV BUILDAH_ISOLATION=chroot - -# Buildah 1.39+ on Debian requires netavark but we never need networking -# (all images are FROM scratch with no RUN steps). A no-op stub satisfies -# the startup check. -RUN mkdir -p /usr/libexec/podman \ - && printf '#!/bin/sh\nexit 0\n' > /usr/libexec/podman/netavark \ - && chmod +x /usr/libexec/podman/netavark - -# Install astral-sh's uv with a script - install to /usr for global access -RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="/usr/bin" sh - -# Verify uv is functional -RUN uv --version - -# Prime uv's cache with our pyproject.toml to speed up runtime -COPY pyproject.toml /tmp/pyproject.toml -COPY captain /tmp/captain -COPY build.py /tmp/build.py -WORKDIR /tmp -RUN uv --verbose run build.py --help - -WORKDIR /work -ENTRYPOINT ["/usr/bin/uv", "run", "/work/build.py"] -CMD ["release", "--help"] diff --git a/captain/cli/_release_publish.py b/captain/cli/_release_publish.py index f8da0b7..5104553 100644 --- a/captain/cli/_release_publish.py +++ b/captain/cli/_release_publish.py @@ -59,7 +59,7 @@ type=click.Choice(["amd64", "arm64", "combined"], case_sensitive=False), metavar="TARGET", help="Artifact target: amd64, arm64, or combined (default: value of --arch); " - "combined requires trixie-full or equivalent flavor with both architectures' outputs present.", + "combined requires trixie-full or equivalent flavor with both arch's outputs present.", ) @click.option( "--git-sha", @@ -162,7 +162,7 @@ def release_publish_cmd( if force: env_args += ["-e", "FORCE=true"] - inner_cmd = ["/work/build.py", "release", "publish"] + inner_cmd = ["captain", "release-publish"] try: docker.run_in_builder( diff --git a/captain/docker.py b/captain/docker.py index 83a4a92..6584537 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -102,6 +102,32 @@ def obtain_builder(cfg: Config) -> None: ] ) + # Show the layer size distribution for the built image to help with debugging and optimization. + log.info("Docker image '%s' built successfully. Layer size distribution:", local_tagged_image) + layer_sizes_lines = run( + [ + "docker", + "history", + "--no-trunc", + "--format", + "-> {{.Size}} :: '{{.CreatedBy}}'", + local_tagged_image, + ], + capture=True, + check=True, + ) + layers = [] + for line in layer_sizes_lines.stdout.strip().splitlines(): + line = line.strip() + if line.startswith("-> "): + # remove double whitespace chars to make it easier to read + line = " ".join(line.split()) + layers.append(line) + # reverse the array to match the order + layers.reverse() + for layer in layers: + log.info("Layer info: %s", layer) + # Optionally push the image after building it if cfg.builder_push: log.info( @@ -113,60 +139,6 @@ def obtain_builder(cfg: Config) -> None: run(["docker", "push", remote_tagged_image]) -def _release_dockerfile_hash(cfg: Config) -> str: - """Return the SHA-256 hex digest of the Dockerfile.release content.""" - dockerfile = cfg.project_dir / "Dockerfile.release" - return hashlib.sha256(dockerfile.read_bytes()).hexdigest() - - -def run_in_release(cfg: Config, *extra_args: str) -> None: - """Run a command inside the release container. - - Similar to :func:`run_in_builder` but uses the lightweight release - image which has buildah, skopeo, Python, and git. - """ - docker_args: list[str] = [ - "docker", - "run", - "--rm", - # Buildah needs mount/remount capabilities for layer operations. - "--privileged", - "-v", - f"{cfg.project_dir}:/work", - "-w", - "/work", - "-e", - "CAPTAIN_IN_DOCKER=docker", - "-e", - f"ARCH={cfg.arch}", - "-e", - "RELEASE_MODE=native", - # Chroot isolation lets buildah work inside an unprivileged container - # (no user namespaces needed — we only assemble scratch images). - "-e", - "BUILDAH_ISOLATION=chroot", - "-e", - f"BUILDAH_INSECURE={os.environ.get('BUILDAH_INSECURE', '')}", - "-e", - f"TERM={os.environ.get('TERM', 'xterm-256color')}", - "-e", - "FORCE_COLOR=1", - "-e", - f"COLUMNS={os.environ.get('COLUMNS', '200')}", - "-e", - f"GITHUB_ACTIONS={os.environ.get('GITHUB_ACTIONS', '')}", - ] - # Forward host registry credentials so buildah/skopeo can authenticate. - # The caller sets these env vars on the host (e.g. via docker login or - # CI secrets); they are passed through to the container as-is. - for var in ("REGISTRY_AUTH_FILE", "REGISTRY_USERNAME", "REGISTRY_PASSWORD"): - val = os.environ.get(var) - if val: - docker_args += ["-e", f"{var}={val}"] - docker_args.extend(extra_args) - run(docker_args) - - def run_in_builder(cfg: Config, *extra_args: str) -> None: """Run a command inside the Docker builder container. @@ -176,7 +148,7 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: "docker", "run", "--rm", - "--privileged", + "--privileged", # yes, this is required for both buildah and mkosi in the container "-w", "/work", "-e", @@ -189,6 +161,14 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: f"FORCE_TOOLS={int(cfg.force_tools)}", "-e", f"FORCE_ISO={int(cfg.force_iso)}", + # Chroot isolation lets buildah work inside an unprivileged container + # (no user namespaces needed — we only assemble scratch images). + "-e", + "BUILDAH_ISOLATION=chroot", + "-e", + f"BUILDAH_INSECURE={os.environ.get('BUILDAH_INSECURE', '')}", + "-e", + "RELEASE_MODE=native", "-e", "TOOLS_MODE=native", "-e", @@ -207,6 +187,15 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: f"GITHUB_ACTIONS={os.environ.get('GITHUB_ACTIONS', '')}", ] + # Forward host registry credentials so buildah/skopeo can authenticate. + # The caller sets these env vars on the host (e.g. via docker login or + # CI secrets); they are passed through to the container as-is. + for var in ("REGISTRY_AUTH_FILE", "REGISTRY_USERNAME", "REGISTRY_PASSWORD"): + val = os.environ.get(var) + if val: + docker_args += ["-e", f"{var}={val}"] + docker_args.extend(extra_args) + docker_args += ["--mount", "type=volume,source=captain-workdir,target=/work"] docker_args += ["-v", f"{cfg.project_dir}/mkosi.output:/work/mkosi.output"] From bd4b47180089eb8a8efe08763207c2da66737b1c Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 16:30:24 +0200 Subject: [PATCH 59/93] docker: rework re-launching inside docker & docker envs Signed-off-by: Ricardo Pardini --- captain/cli/_stages.py | 24 ++---------- captain/docker.py | 85 +++++++++++++++++++++--------------------- 2 files changed, 45 insertions(+), 64 deletions(-) diff --git a/captain/cli/_stages.py b/captain/cli/_stages.py index 4296343..e7082e7 100644 --- a/captain/cli/_stages.py +++ b/captain/cli/_stages.py @@ -28,16 +28,7 @@ def _build_tools_stage(cfg: Config) -> None: # --- docker ------------------------------------------------------- docker.obtain_builder(cfg) log.info("Downloading tools (nerdctl, containerd, etc.) docker...") - docker.run_in_builder( - cfg, - "--entrypoint", - "/usr/bin/uv", - cfg.builder_image, - *(["--verbose"] if log.isEnabledFor(logging.DEBUG) else []), - "run", - "/work/build.py", - "tools", - ) + docker.run_captain_in_builder(cfg, "tools") docker.fix_docker_ownership(cfg, ["/work/mkosi.output"]) @@ -79,7 +70,7 @@ def _build_mkosi_stage(cfg: Config, extra_args: list[str]) -> None: log.info("Building initrd with mkosi (docker)...") tools_tree = f"/work/mkosi.output/tools/{cfg.arch}" output_dir = f"/work/mkosi.output/initramfs/{cfg.flavor_id}/{cfg.arch}" - docker.run_mkosi( + docker.run_mkosi_in_builder( cfg, f"--extra-tree={tools_tree}", f"--output-dir={output_dir}", @@ -119,16 +110,7 @@ def _build_iso_stage(cfg: Config) -> None: # --- docker ------------------------------------------------------- docker.obtain_builder(cfg) log.info("Building ISO (docker)...") - docker.run_in_builder( - cfg, - "--entrypoint", - "/usr/bin/uv", - cfg.builder_image, - *(["--verbose"] if log.isEnabledFor(logging.DEBUG) else []), - "run", - "/work/build.py", - "iso", - ) + docker.run_captain_in_builder(cfg, "iso") docker.fix_docker_ownership( cfg, [ diff --git a/captain/docker.py b/captain/docker.py index 6584537..5320496 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -144,6 +144,31 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: *extra_args* are appended after the docker run flags and image name. """ + + docker_envs: dict[str, str] = { + "CAPTAIN_IN_DOCKER": "docker", + "ARCH": cfg.arch, + "FLAVOR_ID": cfg.flavor_id, + "FORCE_TOOLS": str(int(cfg.force_tools)), + "FORCE_ISO": str(int(cfg.force_iso)), + "BUILDAH_ISOLATION": "chroot", + "BUILDAH_INSECURE": os.environ.get("BUILDAH_INSECURE", ""), + "RELEASE_MODE": "native", + "TOOLS_MODE": "native", + "MKOSI_MODE": "native", + "ISO_MODE": "native", + "TERM": os.environ.get("TERM", "xterm-256color"), + "FORCE_COLOR": "1", + "COLUMNS": os.environ.get("COLUMNS", "200"), + "GITHUB_ACTIONS": os.environ.get("GITHUB_ACTIONS", ""), + # Forward host registry credentials so buildah/skopeo can authenticate. + # The caller sets these env vars on the host (e.g. via docker login or + # CI secrets); they are passed through to the container as-is. + "REGISTRY_AUTH_FILE": os.environ.get("REGISTRY_AUTH_FILE", ""), + "REGISTRY_USERNAME": os.environ.get("REGISTRY_USERNAME", ""), + "REGISTRY_PASSWORD": os.environ.get("REGISTRY_PASSWORD", ""), + } + docker_args: list[str] = [ "docker", "run", @@ -151,50 +176,10 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: "--privileged", # yes, this is required for both buildah and mkosi in the container "-w", "/work", - "-e", - "CAPTAIN_IN_DOCKER=docker", - "-e", - f"ARCH={cfg.arch}", - "-e", - f"FLAVOR_ID={cfg.flavor_id}", - "-e", - f"FORCE_TOOLS={int(cfg.force_tools)}", - "-e", - f"FORCE_ISO={int(cfg.force_iso)}", - # Chroot isolation lets buildah work inside an unprivileged container - # (no user namespaces needed — we only assemble scratch images). - "-e", - "BUILDAH_ISOLATION=chroot", - "-e", - f"BUILDAH_INSECURE={os.environ.get('BUILDAH_INSECURE', '')}", - "-e", - "RELEASE_MODE=native", - "-e", - "TOOLS_MODE=native", - "-e", - "MKOSI_MODE=native", - "-e", - "ISO_MODE=native", - "-e", - "RELEASE_MODE=native", - "-e", - f"TERM={os.environ.get('TERM', 'xterm-256color')}", - "-e", - "FORCE_COLOR=1", - "-e", - f"COLUMNS={os.environ.get('COLUMNS', '200')}", - "-e", - f"GITHUB_ACTIONS={os.environ.get('GITHUB_ACTIONS', '')}", ] - # Forward host registry credentials so buildah/skopeo can authenticate. - # The caller sets these env vars on the host (e.g. via docker login or - # CI secrets); they are passed through to the container as-is. - for var in ("REGISTRY_AUTH_FILE", "REGISTRY_USERNAME", "REGISTRY_PASSWORD"): - val = os.environ.get(var) - if val: - docker_args += ["-e", f"{var}={val}"] - docker_args.extend(extra_args) + for k, v in docker_envs.items(): + docker_args += ["-e", f"{k}={v}"] docker_args += ["--mount", "type=volume,source=captain-workdir,target=/work"] @@ -218,12 +203,26 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: run(docker_args) -def run_mkosi(cfg: Config, *mkosi_args: str) -> None: +def run_captain_in_builder(cfg: Config, *extra_args: str): + log.debug("Running 'captain %s' in builder container...", extra_args) + run_in_builder( + cfg, + cfg.builder_image, + "/usr/bin/uv", + *(["--verbose"] if log.isEnabledFor(logging.DEBUG) else []), + "run", + "captain", + *extra_args, + ) + + +def run_mkosi_in_builder(cfg: Config, *mkosi_args: str) -> None: """Run mkosi inside the builder container.""" ensure_binfmt(cfg) run_in_builder( cfg, cfg.builder_image, + "/usr/bin/mkosi", f"--architecture={cfg.arch_info.mkosi_arch}", *mkosi_args, ) From fa7b258693d92ff43167e6c883df85a1ef38e7d3 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 17:05:59 +0200 Subject: [PATCH 60/93] docker/mkosi: get rid of tools tree; back to Debian's trixie system Python + pip for mkosi - mkosi re-launches itself and ignores uv's env without a tools tree - uv instead does use the system Python if it matches constraints - build in /work in Dockerfile, allows to reuse .venv - don't mount a volume in /work, instead use the one provided by the Dockerfile Signed-off-by: Ricardo Pardini --- Dockerfile | 23 +++++++++++---------- captain/docker.py | 7 +++---- captain/flavors/common_debian/mkosi.conf.j2 | 7 +------ 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4199681..b02bcdb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -105,8 +105,11 @@ RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends \ upx-ucl \ xorriso -RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends \ - buildah +# Buildah is pretty huge, gets its own layer. +RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends buildah + +# This is just to appease mkosi's later stages. +RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends python3 python3-pip python3-pefile RUN <<-CONFIG_FRAG ## A few small config fragments to make life easier @@ -123,17 +126,15 @@ chmod +x /usr/libexec/podman/netavark CONFIG_FRAG # Install astral-sh's uv with a script - install to /usr for global access -RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="/usr/bin" sh && uv --version +RUN echo -n 'System Python: ' && python3 --version && curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="/usr/bin" sh && uv --version -# Install mkosi from GitHub (not on PyPI) via uv; symlink to /usr/bin for global access +# Install mkosi from GitHub (not on PyPI) via the system pip3 ARG MKOSI_VERSION=v26 -RUN uv tool install "git+https://github.com/systemd/mkosi.git@${MKOSI_VERSION}" && ln -sf ~/.local/bin/mkosi /usr/bin/mkosi && mkosi --version +RUN pip3 install --break-system-packages --root-user-action=ignore "git+https://github.com/systemd/mkosi.git@${MKOSI_VERSION}" && mkosi --version && command -v mkosi # Prime uv's cache with our pyproject.toml to speed up runtime -COPY pyproject.toml /tmp/pyproject.toml -COPY captain /tmp/captain -COPY build.py /tmp/build.py -WORKDIR /tmp -RUN uv --verbose run captain --version - +COPY pyproject.toml /work/pyproject.toml +COPY captain /work/captain +COPY build.py /work/build.py WORKDIR /work +RUN uv --verbose run captain --version diff --git a/captain/docker.py b/captain/docker.py index 5320496..d7ec38c 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -181,13 +181,12 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: for k, v in docker_envs.items(): docker_args += ["-e", f"{k}={v}"] - docker_args += ["--mount", "type=volume,source=captain-workdir,target=/work"] - docker_args += ["-v", f"{cfg.project_dir}/mkosi.output:/work/mkosi.output"] + docker_args += ["-v", f"{cfg.project_dir}/out:/work/out"] + docker_args += ["-v", f"{cfg.project_dir}/mkosi.extra:/work/mkosi.extra"] docker_args += ["-v", f"{cfg.project_dir}/mkosi.sandbox:/work/mkosi.sandbox"] docker_args += ["-v", f"{cfg.project_dir}/mkosi.skeleton:/work/mkosi.skeleton"] - docker_args += ["-v", f"{cfg.project_dir}/out:/work/out"] docker_args += ["-v", f"{cfg.project_dir}/mkosi.conf:/work/mkosi.conf"] docker_args += ["-v", f"{cfg.project_dir}/mkosi.finalize:/work/mkosi.finalize"] @@ -222,7 +221,7 @@ def run_mkosi_in_builder(cfg: Config, *mkosi_args: str) -> None: run_in_builder( cfg, cfg.builder_image, - "/usr/bin/mkosi", + "/usr/local/bin/mkosi", f"--architecture={cfg.arch_info.mkosi_arch}", *mkosi_args, ) diff --git a/captain/flavors/common_debian/mkosi.conf.j2 b/captain/flavors/common_debian/mkosi.conf.j2 index 5913c5a..ce128aa 100644 --- a/captain/flavors/common_debian/mkosi.conf.j2 +++ b/captain/flavors/common_debian/mkosi.conf.j2 @@ -10,13 +10,8 @@ CompressLevel=19 OutputDirectory=mkosi.output [Build] -Environment=TERM=dumb -ToolsTree=yes -ToolsTreeDistribution=debian -ToolsTreeRelease=trixie -ToolsTreeSandboxTrees=mkosi.sandbox Incremental=yes -CacheDirectory=mkosi.cache +CacheDirectory=mkosi.cache.for.incremental [Content] Bootable=no From 04f1fa1e15670cde8d69e051a05d917995de44d8 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 7 Apr 2026 17:48:13 +0200 Subject: [PATCH 61/93] captain: honor FORCE_COLOR=1 for internal logging Signed-off-by: Ricardo Pardini --- captain/__init__.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/captain/__init__.py b/captain/__init__.py index efb57c6..2d69888 100644 --- a/captain/__init__.py +++ b/captain/__init__.py @@ -15,16 +15,17 @@ from rich.traceback import install as _install_rich_traceback # Rich console — writes to stderr so log output never pollutes piped stdout. -# If running under GHA, force colors. -if os.environ.get("GITHUB_ACTIONS", "") == "": - console: Console = Console(stderr=True) -else: - console: Console = Console(stderr=True, color_system="standard", width=160, highlight=False) - # Install Rich traceback handler globally (once, at import time). -_install_rich_traceback( - console=console, show_locals=False, width=None, suppress=[click], max_frames=2 -) +if os.environ.get("FORCE_COLOR", "0") == "1": + console: Console = Console(stderr=True, color_system="standard", width=160, highlight=False) + _install_rich_traceback( + console=console, show_locals=False, width=160, suppress=[click], max_frames=2 + ) +else: + console: Console = Console(stderr=True) + _install_rich_traceback( + console=console, show_locals=False, width=None, suppress=[click], max_frames=2 + ) class _StageFormatter(logging.Formatter): From c764e1204b72c7944459a7db450aad569a0da7c5 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Wed, 8 Apr 2026 11:03:33 +0200 Subject: [PATCH 62/93] flavors: tighten semantics, refactor - refactor add_static_dir() into BaseFlavor - make has_iso() abstractmethod at BaseFlavor level - make kernel_packages() abstractmethod at common-debian level Signed-off-by: Ricardo Pardini --- captain/flavor.py | 10 ++++++ captain/flavors/common_armbian/__init__.py | 12 +++---- captain/flavors/common_debian/__init__.py | 39 ++++++++-------------- 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/captain/flavor.py b/captain/flavor.py index 18098da..ea248df 100644 --- a/captain/flavor.py +++ b/captain/flavor.py @@ -4,6 +4,7 @@ import logging import shutil +from abc import abstractmethod from pathlib import Path from typing import Protocol, runtime_checkable @@ -69,6 +70,14 @@ def specific_flavor_dir(self, flavor_id: str) -> Path: raise SystemExit(1) return flavor_dir + def add_static_dir(self, dir_to_include: str, flavor_dir: Path): + extra_dir = flavor_dir / dir_to_include + if extra_dir.exists() and extra_dir.is_dir(): + for extra_file in extra_dir.rglob("*"): + if extra_file.is_file(): + relative_path = extra_file.relative_to(flavor_dir) + self.static_map[str(relative_path)] = extra_file + def render_templates(self, output_dir: Path): log.debug("Called BaseFlavor.render_templates() with output_dir: %s", output_dir) # Use jinja2 to render all templates in self.template_map, writing output to output_dir @@ -111,6 +120,7 @@ def copy_static_files(self, project_dir): destination_path.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(source_path, destination_path) + @abstractmethod def has_iso(self) -> bool: return False diff --git a/captain/flavors/common_armbian/__init__.py b/captain/flavors/common_armbian/__init__.py index e79bdfd..388cf31 100644 --- a/captain/flavors/common_armbian/__init__.py +++ b/captain/flavors/common_armbian/__init__.py @@ -16,13 +16,9 @@ class ArmbianCommonFlavor(DebianCommonFlavor): def setup(self, cfg: Config, flavor_dir: Path) -> None: super().setup(cfg, flavor_dir) + this_flavor_dir = self.specific_flavor_dir("common-armbian") + self.add_static_dir("mkosi.sandbox", this_flavor_dir) - # Now, lets enumerate and add all the static files this flavor's mkosi.sandbox directory - # and add them to self.static_map with the key being the relative path from the flavor dir - extra_dir = this_flavor_dir / "mkosi.sandbox" - if extra_dir.exists() and extra_dir.is_dir(): - for extra_file in extra_dir.rglob("*"): - if extra_file.is_file(): - relative_path = extra_file.relative_to(this_flavor_dir) - self.static_map[str(relative_path)] = extra_file + def has_iso(self) -> bool: + return False diff --git a/captain/flavors/common_debian/__init__.py b/captain/flavors/common_debian/__init__.py index 60c84dc..051db43 100644 --- a/captain/flavors/common_debian/__init__.py +++ b/captain/flavors/common_debian/__init__.py @@ -1,4 +1,5 @@ import logging +from abc import abstractmethod from dataclasses import dataclass from pathlib import Path @@ -18,39 +19,25 @@ def setup(self, cfg: Config, flavor_dir: Path) -> None: super().setup(cfg, flavor_dir) this_flavor_dir = self.specific_flavor_dir("common-debian") + + # Templates self.template_map["mkosi.conf"] = [this_flavor_dir / "mkosi.conf.j2"] + self.template_map["mkosi.postinst"] = [ this_flavor_dir / "bash.header.sh", this_flavor_dir / "mkosi.postinst.sh.j2", ] + self.template_map["mkosi.finalize"] = [ this_flavor_dir / "bash.header.sh", this_flavor_dir / "mkosi.finalize.sh.j2", ] - # Now, lets enumerate and add all the static files this flavor's mkosi.extra directory - # and add them to self.static_map with the key being the relative path from the flavor dir - extra_dir = this_flavor_dir / "mkosi.extra" - if extra_dir.exists() and extra_dir.is_dir(): - for extra_file in extra_dir.rglob("*"): - if extra_file.is_file(): - relative_path = extra_file.relative_to(this_flavor_dir) - self.static_map[str(relative_path)] = extra_file - - # Now, lets enumerate and add all the static files this flavor's mkosi.sandbox directory - # and add them to self.static_map with the key being the relative path from the flavor dir - extra_dir = this_flavor_dir / "mkosi.sandbox" - if extra_dir.exists() and extra_dir.is_dir(): - for extra_file in extra_dir.rglob("*"): - if extra_file.is_file(): - relative_path = extra_file.relative_to(this_flavor_dir) - self.static_map[str(relative_path)] = extra_file - - # Now, lets enumerate and add all the static files this flavor's mkosi.skeleton directory - # and add them to self.static_map with the key being the relative path from the flavor dir - extra_dir = this_flavor_dir / "mkosi.skeleton" - if extra_dir.exists() and extra_dir.is_dir(): - for extra_file in extra_dir.rglob("*"): - if extra_file.is_file(): - relative_path = extra_file.relative_to(this_flavor_dir) - self.static_map[str(relative_path)] = extra_file + # Static files + self.add_static_dir("mkosi.extra", this_flavor_dir) + self.add_static_dir("mkosi.sandbox", this_flavor_dir) + self.add_static_dir("mkosi.skeleton", this_flavor_dir) + + @abstractmethod + def kernel_packages(self) -> set[str]: + pass From 1a2174ce8f15feba4a93d950e250eac3e5503477 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Wed, 8 Apr 2026 11:23:49 +0200 Subject: [PATCH 63/93] flavors: introduce common-acpi, move acpi/impi stuff there - so it's `common-debian > common-acpi > trixie-full` - in the future: `common-debian > common-acpi > trixie-mainline` - common-acpi implements `has_iso()` as True Signed-off-by: Ricardo Pardini --- captain/flavors/common_acpi/__init__.py | 28 +++++++++++++++++++ .../mkosi.extra/etc/acpi/events/powerbtn | 0 .../mkosi.extra/etc/acpi/powerbtn.sh | 0 .../mkosi.extra/etc/modules-load.d/ipmi.conf | 0 captain/flavors/trixie_full/__init__.py | 8 ++---- 5 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 captain/flavors/common_acpi/__init__.py rename captain/flavors/{common_debian => common_acpi}/mkosi.extra/etc/acpi/events/powerbtn (100%) rename captain/flavors/{common_debian => common_acpi}/mkosi.extra/etc/acpi/powerbtn.sh (100%) rename captain/flavors/{common_debian => common_acpi}/mkosi.extra/etc/modules-load.d/ipmi.conf (100%) diff --git a/captain/flavors/common_acpi/__init__.py b/captain/flavors/common_acpi/__init__.py new file mode 100644 index 0000000..b9e35eb --- /dev/null +++ b/captain/flavors/common_acpi/__init__.py @@ -0,0 +1,28 @@ +import logging +from dataclasses import dataclass +from pathlib import Path + +from captain.config import Config +from captain.flavors.common_debian import DebianCommonFlavor + +log: logging.Logger = logging.getLogger(__name__) + + +@dataclass +class TrixieACPIFlavor(DebianCommonFlavor): + id = "trixie-acpi" + name = "Trixie ACPI Common" + description = "Debian Trixie based on UEFI+ACPI machines" + supported_architectures = frozenset(["amd64", "arm64"]) + + def setup(self, cfg: Config, flavor_dir: Path) -> None: + super().setup(cfg, flavor_dir) + + this_flavor_dir = self.specific_flavor_dir("common-acpi") + + # Static files + self.add_static_dir("mkosi.extra", this_flavor_dir) + + # This flavor can produce working ISO images (generic UEFI/ACPI) + def has_iso(self) -> bool: + return True diff --git a/captain/flavors/common_debian/mkosi.extra/etc/acpi/events/powerbtn b/captain/flavors/common_acpi/mkosi.extra/etc/acpi/events/powerbtn similarity index 100% rename from captain/flavors/common_debian/mkosi.extra/etc/acpi/events/powerbtn rename to captain/flavors/common_acpi/mkosi.extra/etc/acpi/events/powerbtn diff --git a/captain/flavors/common_debian/mkosi.extra/etc/acpi/powerbtn.sh b/captain/flavors/common_acpi/mkosi.extra/etc/acpi/powerbtn.sh similarity index 100% rename from captain/flavors/common_debian/mkosi.extra/etc/acpi/powerbtn.sh rename to captain/flavors/common_acpi/mkosi.extra/etc/acpi/powerbtn.sh diff --git a/captain/flavors/common_debian/mkosi.extra/etc/modules-load.d/ipmi.conf b/captain/flavors/common_acpi/mkosi.extra/etc/modules-load.d/ipmi.conf similarity index 100% rename from captain/flavors/common_debian/mkosi.extra/etc/modules-load.d/ipmi.conf rename to captain/flavors/common_acpi/mkosi.extra/etc/modules-load.d/ipmi.conf diff --git a/captain/flavors/trixie_full/__init__.py b/captain/flavors/trixie_full/__init__.py index dd28880..9b81c31 100644 --- a/captain/flavors/trixie_full/__init__.py +++ b/captain/flavors/trixie_full/__init__.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from captain.flavor import BaseFlavor -from captain.flavors.common_debian import DebianCommonFlavor +from captain.flavors.common_acpi import TrixieACPIFlavor log: logging.Logger = logging.getLogger(__name__) @@ -12,7 +12,7 @@ def create_flavor() -> BaseFlavor: @dataclass -class TrixieFullFlavor(DebianCommonFlavor): +class TrixieFullFlavor(TrixieACPIFlavor): id = "trixie-full" name = "Trixie Full" description = "Debian Trixie based with linux-image-generic standard Debian kernel" @@ -20,7 +20,3 @@ class TrixieFullFlavor(DebianCommonFlavor): def kernel_packages(self) -> set[str]: return {"linux-image-generic"} - - # This flavor can produce working ISO images (generic UEFI/ACPI) - def has_iso(self) -> bool: - return True From 69bf5a97f788b73bbc242ddbf7f0d63c91572ead Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Wed, 8 Apr 2026 13:03:52 +0200 Subject: [PATCH 64/93] click: introduce CliContext and reuse common options via @click.Group - CliContext is a Config factory, taking both common and specific options - actually implement --verbose, thus new default logging level is INFO Signed-off-by: Ricardo Pardini --- captain/__init__.py | 2 +- captain/cli/_build.py | 23 +---- captain/cli/_builder.py | 25 +---- captain/cli/_iso.py | 23 +---- captain/cli/_main.py | 157 +++++++++++++++++++++----------- captain/cli/_release_publish.py | 36 ++------ captain/cli/_tools.py | 23 +---- 7 files changed, 129 insertions(+), 160 deletions(-) diff --git a/captain/__init__.py b/captain/__init__.py index 2d69888..3b7ff4f 100644 --- a/captain/__init__.py +++ b/captain/__init__.py @@ -51,4 +51,4 @@ def format(self, record: logging.LogRecord) -> str: ) _handler.setFormatter(_StageFormatter("%(stage)s: %(message)s")) -logging.basicConfig(level="DEBUG", datefmt="[%X]", handlers=[_handler]) +logging.basicConfig(level="INFO", datefmt="[%X]", handlers=[_handler]) diff --git a/captain/cli/_build.py b/captain/cli/_build.py index ee356a7..b18dfe6 100644 --- a/captain/cli/_build.py +++ b/captain/cli/_build.py @@ -8,13 +8,12 @@ import captain.flavor from captain import artifacts -from captain.cli._main import cli, common_options, resolve_project_dir +from captain.cli._main import CliContext, cli from captain.cli._stages import ( _build_iso_stage, _build_mkosi_stage, _build_tools_stage, ) -from captain.config import Config log = logging.getLogger(__name__) @@ -23,7 +22,6 @@ "build", short_help="Run the full build pipeline via mkosi.", ) -@common_options @click.option( "--mkosi-mode", envvar="MKOSI_MODE", @@ -72,14 +70,10 @@ default=False, help="Force ISO rebuild even if outputs already exist.", ) +@click.pass_obj def build_cmd( + cli_ctx: CliContext, *, - arch: str, - flavor_id: str, - project_dir: str | None, - builder_registry: str | None, - builder_repository: str | None, - builder_image: str, mkosi_mode: str, tools_mode: str, iso_mode: str, @@ -103,16 +97,7 @@ def build_cmd( captain build --force --force-tools """ - proj = resolve_project_dir(project_dir) - - cfg = Config( - project_dir=proj, - output_dir=proj / "out", - arch=arch, - flavor_id=flavor_id, - builder_registry=builder_registry, - builder_repository=builder_repository, - builder_image=builder_image, + cfg = cli_ctx.make_config( tools_mode=tools_mode, mkosi_mode=mkosi_mode, iso_mode=iso_mode, diff --git a/captain/cli/_builder.py b/captain/cli/_builder.py index 1caad5e..8e8d3ea 100644 --- a/captain/cli/_builder.py +++ b/captain/cli/_builder.py @@ -6,8 +6,7 @@ import click -from captain.cli._main import cli, common_options, resolve_project_dir -from captain.config import Config +from captain.cli._main import CliContext, cli from captain.docker import obtain_builder log = logging.getLogger(__name__) @@ -17,21 +16,16 @@ "builder", short_help="Build the Docker builder image and optionally push it.", ) -@common_options @click.option( "--push", is_flag=True, default=False, help="Push the built image to a registry after building.", ) +@click.pass_obj def builder_cmd( + cli_ctx: CliContext, *, - arch: str, - flavor_id: str, - project_dir: str | None, - builder_registry: str | None, - builder_repository: str | None, - builder_image: str, push: bool, ) -> None: """Build the Docker builder image used by other build stages. @@ -46,18 +40,7 @@ def builder_cmd( captain builder --no-cache captain builder --push """ - proj = resolve_project_dir(project_dir) - - cfg = Config( - project_dir=proj, - output_dir=proj / "out", - arch=arch, - flavor_id=flavor_id, - builder_registry=builder_registry, - builder_repository=builder_repository, - builder_image=builder_image, - builder_push=push, - ) + cfg = cli_ctx.make_config(builder_push=push) # 1. Build the image. obtain_builder(cfg) diff --git a/captain/cli/_iso.py b/captain/cli/_iso.py index 1081acd..2e72b44 100644 --- a/captain/cli/_iso.py +++ b/captain/cli/_iso.py @@ -8,11 +8,10 @@ import captain.flavor from captain import artifacts -from captain.cli._main import cli, common_options, resolve_project_dir +from captain.cli._main import CliContext, cli from captain.cli._stages import ( _build_iso_stage, ) -from captain.config import Config log = logging.getLogger(__name__) @@ -21,7 +20,6 @@ "iso", short_help="Build ISO image only. Part of build.", ) -@common_options @click.option( "--iso-mode", envvar="ISO_MODE", @@ -38,29 +36,16 @@ default=False, help="Force ISO rebuild even if outputs already exist.", ) +@click.pass_obj def build_cmd( + cli_ctx: CliContext, *, - arch: str, - flavor_id: str, - project_dir: str | None, - builder_registry: str | None, - builder_repository: str | None, - builder_image: str, iso_mode: str, force_iso: bool, ) -> None: """Run the CaptainOS ISO build.""" - proj = resolve_project_dir(project_dir) - - cfg = Config( - project_dir=proj, - output_dir=proj / "out", - arch=arch, - flavor_id=flavor_id, - builder_registry=builder_registry, - builder_repository=builder_repository, - builder_image=builder_image, + cfg = cli_ctx.make_config( iso_mode=iso_mode, force_iso=force_iso, ) diff --git a/captain/cli/_main.py b/captain/cli/_main.py index 0fe79ca..352c730 100644 --- a/captain/cli/_main.py +++ b/captain/cli/_main.py @@ -2,15 +2,15 @@ from __future__ import annotations -import functools import logging import sys +from dataclasses import dataclass from pathlib import Path from typing import Any import click -from captain.config import DEFAULT_FLAVOR_ID +from captain.config import DEFAULT_FLAVOR_ID, Config from captain.flavor import list_available_flavors from captain.util import detect_current_machine_arch @@ -18,63 +18,33 @@ # --------------------------------------------------------------------------- -# Shared option decorators +# CLI context object — shared state for all subcommands # --------------------------------------------------------------------------- -def common_options(fn: Any) -> Any: - """Decorate a click command with options shared by every subcommand.""" +@dataclass(slots=True) +class CliContext: + """Resolved common CLI options, passed to subcommands via ``@click.pass_obj``.""" - @click.option( - "--arch", - envvar="ARCH", - default=(detect_current_machine_arch()), - show_default=True, - type=click.Choice(["amd64", "arm64"], case_sensitive=False), - metavar="ARCH", - help="Target architecture (amd64, arm64).", - ) - @click.option( - "--flavor-id", - envvar="FLAVOR_ID", - default=DEFAULT_FLAVOR_ID, - show_default=True, - type=click.Choice(list_available_flavors(), case_sensitive=False), - help="Flavor (kernel/board config) to build.", - ) - @click.option( - "--project-dir", - envvar="CAPTAIN_PROJECT_DIR", - default=None, - type=click.Path(exists=True, file_okay=False, resolve_path=True), - help="Project root directory (auto-detected when omitted).", - ) - @click.option( - "--builder-registry", - envvar="REGISTRY", - default="ghcr.io", - show_default=True, - help="OCI registry hostname for the Docker builder image", - ) - @click.option( - "--builder-repository", - envvar="GITHUB_REPOSITORY", - default="tinkerbell/captain", - show_default=True, - help="Repository path (owner/name) for the Docker builder image", - ) - @click.option( - "--builder-image", - envvar="BUILDER_IMAGE", - default="captainos-builder", - show_default=True, - help="Local name/tag of Docker builder image name", - ) - @functools.wraps(fn) - def wrapper(**kwargs: Any) -> Any: - return fn(**kwargs) + project_dir: Path + arch: str + flavor_id: str + builder_registry: str | None + builder_repository: str | None + builder_image: str - return wrapper + def make_config(self, **overrides: Any) -> Config: + """Build a :class:`Config` from the common options plus per-command *overrides*.""" + return Config( + project_dir=self.project_dir, + output_dir=self.project_dir / "out", + arch=self.arch, + flavor_id=self.flavor_id, + builder_registry=self.builder_registry, + builder_repository=self.builder_repository, + builder_image=self.builder_image, + **overrides, + ) # --------------------------------------------------------------------------- @@ -116,11 +86,88 @@ def resolve_project_dir(project_dir: str | None) -> Path: ), ) @click.version_option(package_name="captain") +@click.option( + "-v", + "--verbose", + is_flag=True, + default=False, + envvar="CAPTAIN_VERBOSE", + help="Enable verbose (DEBUG-level) logging.", +) +@click.option( + "--arch", + envvar="ARCH", + default=(detect_current_machine_arch()), + show_default=True, + type=click.Choice(["amd64", "arm64"], case_sensitive=False), + metavar="ARCH", + help="Target architecture (amd64, arm64).", +) +@click.option( + "--flavor-id", + envvar="FLAVOR_ID", + default=DEFAULT_FLAVOR_ID, + show_default=True, + type=click.Choice(list_available_flavors(), case_sensitive=False), + help="Flavor (kernel/board config) to build.", +) +@click.option( + "--project-dir", + envvar="CAPTAIN_PROJECT_DIR", + default=None, + type=click.Path(exists=True, file_okay=False, resolve_path=True), + help="Project root directory (auto-detected when omitted).", +) +@click.option( + "--builder-registry", + envvar="REGISTRY", + default="ghcr.io", + show_default=True, + help="OCI registry hostname for the Docker builder image", +) +@click.option( + "--builder-repository", + envvar="GITHUB_REPOSITORY", + default="tinkerbell/captain", + show_default=True, + help="Repository path (owner/name) for the Docker builder image", +) +@click.option( + "--builder-image", + envvar="BUILDER_IMAGE", + default="captainos-builder", + show_default=True, + help="Local name/tag of Docker builder image name", +) @click.pass_context -def cli(ctx: click.Context) -> None: +def cli( + ctx: click.Context, + *, + verbose: bool, + arch: str, + flavor_id: str, + project_dir: str | None, + builder_registry: str | None, + builder_repository: str | None, + builder_image: str, +) -> None: """CaptainOS build system — click CLI.""" + # Configure log level based on --verbose. + logging.getLogger().setLevel(logging.DEBUG if verbose else logging.INFO) + if ctx.invoked_subcommand is None: click.echo(ctx.get_help()) + return + + # Build the shared context object for subcommands. + ctx.obj = CliContext( + project_dir=resolve_project_dir(project_dir), + arch=arch, + flavor_id=flavor_id, + builder_registry=builder_registry, + builder_repository=builder_repository, + builder_image=builder_image, + ) # --------------------------------------------------------------------------- diff --git a/captain/cli/_release_publish.py b/captain/cli/_release_publish.py index 5104553..1ac129c 100644 --- a/captain/cli/_release_publish.py +++ b/captain/cli/_release_publish.py @@ -10,8 +10,7 @@ import captain.flavor from captain import oci -from captain.cli._main import cli, common_options, resolve_project_dir -from captain.config import Config +from captain.cli._main import CliContext, cli from captain.util import check_release_dependencies log = logging.getLogger(__name__) @@ -21,7 +20,6 @@ "release-publish", short_help="Publish build artifacts as a multi-arch OCI image.", ) -@common_options @click.option( "--release-mode", envvar="RELEASE_MODE", @@ -80,14 +78,10 @@ default=False, help="Publish even if the image already exists in the registry.", ) +@click.pass_obj def release_publish_cmd( + cli_ctx: CliContext, *, - arch: str, - flavor_id: str, - project_dir: str | None, - builder_registry: str | None, - builder_repository: str | None, - builder_image: str, release_mode: str, registry: str, repository: str, @@ -115,21 +109,11 @@ def release_publish_cmd( captain release-publish --registry ghcr.io --repository tinkerbell/captain """ - proj = resolve_project_dir(project_dir) - if target is None: - target = arch - - cfg = Config( - project_dir=proj, - output_dir=proj / "out", - arch=arch, - flavor_id=flavor_id, - builder_registry=builder_registry, - builder_repository=builder_repository, - builder_image=builder_image, - release_mode=release_mode, - ) + target = cli_ctx.arch + assert isinstance(target, str) + + cfg = cli_ctx.make_config(release_mode=release_mode) # --- skip mode -------------------------------------------------------- if cfg.release_mode == "skip": @@ -141,7 +125,7 @@ def release_publish_cmd( from captain import docker docker.obtain_builder(cfg) - sha = _resolve_git_sha(git_sha, proj) + sha = _resolve_git_sha(git_sha, cfg.project_dir) env_args: list[str] = [ "-e", @@ -187,8 +171,8 @@ def release_publish_cmd( log.error("Install them or set --release-mode=docker.") raise SystemExit(1) - sha = _resolve_git_sha(git_sha, proj) - tag = oci.compute_version_tag(proj, sha, exclude=version_exclude) + sha = _resolve_git_sha(git_sha, cfg.project_dir) + tag = oci.compute_version_tag(cfg.project_dir, sha, exclude=version_exclude) tag = f"{tag}-{cfg.flavor_id}" flavor = captain.flavor.create_and_setup_flavor_for_id(cfg.flavor_id, cfg) diff --git a/captain/cli/_tools.py b/captain/cli/_tools.py index 187bd31..16e0fd3 100644 --- a/captain/cli/_tools.py +++ b/captain/cli/_tools.py @@ -6,9 +6,8 @@ import click -from captain.cli._main import cli, common_options, resolve_project_dir +from captain.cli._main import CliContext, cli from captain.cli._stages import _build_tools_stage -from captain.config import Config log = logging.getLogger(__name__) @@ -17,7 +16,6 @@ "tools", short_help="Download tools (containerd, runc, nerdctl, CNI).", ) -@common_options @click.option( "--tools-mode", envvar="TOOLS_MODE", @@ -34,14 +32,10 @@ default=False, help="Re-download tools even if outputs already exist.", ) +@click.pass_obj def tools_cmd( + cli_ctx: CliContext, *, - arch: str, - flavor_id: str, - project_dir: str | None, - builder_registry: str | None, - builder_repository: str | None, - builder_image: str, tools_mode: str, force_tools: bool, ) -> None: @@ -60,16 +54,7 @@ def tools_cmd( captain tools --force-tools """ - proj = resolve_project_dir(project_dir) - - cfg = Config( - project_dir=proj, - output_dir=proj / "out", - arch=arch, - flavor_id=flavor_id, - builder_registry=builder_registry, - builder_repository=builder_repository, - builder_image=builder_image, + cfg = cli_ctx.make_config( tools_mode=tools_mode, force_tools=force_tools, ) From bd55dd593658de92d7a7a22025d68fd698c076f6 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Wed, 8 Apr 2026 13:34:11 +0200 Subject: [PATCH 65/93] captain: make Rich richer - show Locals - show all frames in traces - use Highlights for FORCE_COLOR=1 Signed-off-by: Ricardo Pardini --- captain/__init__.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/captain/__init__.py b/captain/__init__.py index 3b7ff4f..9b50f46 100644 --- a/captain/__init__.py +++ b/captain/__init__.py @@ -17,15 +17,11 @@ # Rich console — writes to stderr so log output never pollutes piped stdout. # Install Rich traceback handler globally (once, at import time). if os.environ.get("FORCE_COLOR", "0") == "1": - console: Console = Console(stderr=True, color_system="standard", width=160, highlight=False) - _install_rich_traceback( - console=console, show_locals=False, width=160, suppress=[click], max_frames=2 - ) + console: Console = Console(stderr=True, color_system="standard", width=160) + _install_rich_traceback(console=console, show_locals=True, width=160, suppress=[click]) else: console: Console = Console(stderr=True) - _install_rich_traceback( - console=console, show_locals=False, width=None, suppress=[click], max_frames=2 - ) + _install_rich_traceback(console=console, show_locals=True, width=None, suppress=[click]) class _StageFormatter(logging.Formatter): @@ -45,8 +41,7 @@ def format(self, record: logging.LogRecord) -> str: show_path=True, markup=True, # interprets [braket]stuff[/bracket] in log messages, beware rich_tracebacks=True, - tracebacks_show_locals=False, - tracebacks_max_frames=2, # too many frames and we get confused + tracebacks_show_locals=True, tracebacks_suppress=[click], # don't wanna see click infra code in traces ) _handler.setFormatter(_StageFormatter("%(stage)s: %(message)s")) From f7d31ee28b7c153f48384f69bd5ce9c8643b1005 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Wed, 8 Apr 2026 13:37:03 +0200 Subject: [PATCH 66/93] captain: introduce Trogon(/Textual) to auto-create a TUI from Click - See https://github.com/Textualize/trogon and https://github.com/Textualize/textual - this cashes out on the Click / Rich investment Signed-off-by: Ricardo Pardini --- captain/cli/_main.py | 4 ++++ pyproject.toml | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/captain/cli/_main.py b/captain/cli/_main.py index 352c730..664007e 100644 --- a/captain/cli/_main.py +++ b/captain/cli/_main.py @@ -9,6 +9,7 @@ from typing import Any import click +from trogon import tui from captain.config import DEFAULT_FLAVOR_ID, Config from captain.flavor import list_available_flavors @@ -67,6 +68,8 @@ def resolve_project_dir(project_dir: str | None) -> Path: # --------------------------------------------------------------------------- # Top-level Click group # --------------------------------------------------------------------------- +# Important: decorator order matters here. The @tui() decorator must be outermost +# to properly wrap the entire CLI, including subcommands. CONTEXT_SETTINGS = dict( help_option_names=["-h", "--help"], @@ -74,6 +77,7 @@ def resolve_project_dir(project_dir: str | None) -> Path: ) +@tui() @click.group( context_settings=CONTEXT_SETTINGS, invoke_without_command=True, diff --git a/pyproject.toml b/pyproject.toml index fea7067..3ecbea2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,8 @@ requires-python = ">=3.13" dependencies = [ "click>=8.1", "rich>=14.3.3", - "jinja2>=3.1.6" + "jinja2>=3.1.6", + "trogon>=0.6.0", ] [project.scripts] From bd7edd2718f4bccfe2efcd40e03a3397648a124d Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Wed, 8 Apr 2026 15:13:18 +0200 Subject: [PATCH 67/93] gha: use envvars, not --options - reduce suffering while I juggle Click Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a5647c..ed55f81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,9 @@ jobs: uses: astral-sh/setup-uv@v7 - name: Download tools - run: uv run captain tools --tools-mode=native + env: + TOOLS_MODE: native + run: uv run captain tools - name: Save tools cache if: github.ref == 'refs/heads/main' && steps.tools-cache.outputs.cache-hit != 'true' @@ -151,7 +153,7 @@ jobs: uses: astral-sh/setup-uv@v7 - name: Build initramfs - run: uv run captain build --arch=${{ matrix.arch }} # full build, incl initramfs and iso when appropriate + run: uv run captain build # full build, incl initramfs and iso when appropriate - name: Upload initramfs artifacts uses: actions/upload-artifact@v6 From eb0675d7d6454d81bae63fb57137f009b6c81b22 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Wed, 8 Apr 2026 15:16:04 +0200 Subject: [PATCH 68/93] captain: even Richer Rich - uv: split `Config::verbose_uv` from --verbose/Logging.DEBUG; use --quiet otherwise - use `shutil.get_terminal_size` to obtain and pass-down-Docker COLUMNS - nicer logging format, use a whale emoji for in-Docker logs - show Rich Table with Docker environment vars if --verbose - show Rich Panel and Rich Syntax for `util.run()` if --verbose Signed-off-by: Ricardo Pardini --- captain/__init__.py | 15 +++++++++------ captain/cli/_release_publish.py | 2 +- captain/config.py | 3 +++ captain/docker.py | 17 +++++++++++++++-- captain/util.py | 11 ++++++++++- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/captain/__init__.py b/captain/__init__.py index 9b50f46..c962002 100644 --- a/captain/__init__.py +++ b/captain/__init__.py @@ -8,17 +8,21 @@ import logging import os +import shutil import click from rich.console import Console from rich.logging import RichHandler from rich.traceback import install as _install_rich_traceback +# Obtain terminal width +env_columns = shutil.get_terminal_size(fallback=(161, 24)).columns + # Rich console — writes to stderr so log output never pollutes piped stdout. # Install Rich traceback handler globally (once, at import time). if os.environ.get("FORCE_COLOR", "0") == "1": - console: Console = Console(stderr=True, color_system="standard", width=160) - _install_rich_traceback(console=console, show_locals=True, width=160, suppress=[click]) + console: Console = Console(stderr=True, color_system="standard", width=env_columns) + _install_rich_traceback(console=console, show_locals=True, width=env_columns, suppress=[click]) else: console: Console = Console(stderr=True) _install_rich_traceback(console=console, show_locals=True, width=None, suppress=[click]) @@ -26,11 +30,10 @@ class _StageFormatter(logging.Formatter): def format(self, record: logging.LogRecord) -> str: - name = record.name + name = record.name.replace("captain.", "") record.__dict__["stage"] = name if os.environ.get("CAPTAIN_IN_DOCKER", "") == "docker": - # Running on host: show stage names in blue for visual clarity. - record.__dict__["stage"] = f"[bold][blue]in-docker[/bold]: [/blue]{name}" + record.__dict__["stage"] = f"🐳 {name}" return super().format(record) @@ -44,6 +47,6 @@ def format(self, record: logging.LogRecord) -> str: tracebacks_show_locals=True, tracebacks_suppress=[click], # don't wanna see click infra code in traces ) -_handler.setFormatter(_StageFormatter("%(stage)s: %(message)s")) +_handler.setFormatter(_StageFormatter("[bold][cyan]%(stage)s[/cyan][/bold]: %(message)s")) logging.basicConfig(level="INFO", datefmt="[%X]", handlers=[_handler]) diff --git a/captain/cli/_release_publish.py b/captain/cli/_release_publish.py index 1ac129c..daa4b66 100644 --- a/captain/cli/_release_publish.py +++ b/captain/cli/_release_publish.py @@ -155,7 +155,7 @@ def release_publish_cmd( "--entrypoint", "/usr/bin/uv", cfg.builder_image, - *(["--verbose"] if log.isEnabledFor(logging.DEBUG) else []), + *(["--verbose"] if cfg.verbose_uv else ["--quiet"]), "run", *inner_cmd, ) diff --git a/captain/config.py b/captain/config.py index f0418d4..fee1ad3 100644 --- a/captain/config.py +++ b/captain/config.py @@ -58,6 +58,9 @@ class Config: # Derived (set in __post_init__) arch_info: ArchInfo = field(init=False) + # Call uv (eg in Docker) using its own --verbose flag + verbose_uv: bool = False + def __post_init__(self) -> None: self.arch_info = get_arch_info(self.arch) self.arch = self.arch_info.arch # normalise aliases (x86_64 → amd64, etc.) diff --git a/captain/docker.py b/captain/docker.py index d7ec38c..5ce7fd8 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -8,6 +8,9 @@ import platform from pathlib import Path +from rich.table import Table + +import captain from captain.config import Config from captain.util import detect_current_machine_arch, run @@ -159,7 +162,7 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: "ISO_MODE": "native", "TERM": os.environ.get("TERM", "xterm-256color"), "FORCE_COLOR": "1", - "COLUMNS": os.environ.get("COLUMNS", "200"), + "COLUMNS": str(captain.env_columns), "GITHUB_ACTIONS": os.environ.get("GITHUB_ACTIONS", ""), # Forward host registry credentials so buildah/skopeo can authenticate. # The caller sets these env vars on the host (e.g. via docker login or @@ -178,6 +181,16 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: "/work", ] + if log.isEnabledFor(logging.DEBUG): + table = Table( + title="Docker Environment Variables", show_header=True, header_style="bold cyan" + ) + table.add_column("Environment Variable", style="green") + table.add_column("Value", style="yellow") + for key, value in sorted(docker_envs.items()): + table.add_row(key, value) + captain.console.print(table) + for k, v in docker_envs.items(): docker_args += ["-e", f"{k}={v}"] @@ -208,7 +221,7 @@ def run_captain_in_builder(cfg: Config, *extra_args: str): cfg, cfg.builder_image, "/usr/bin/uv", - *(["--verbose"] if log.isEnabledFor(logging.DEBUG) else []), + *(["--verbose"] if cfg.verbose_uv else ["--quiet"]), "run", "captain", *extra_args, diff --git a/captain/util.py b/captain/util.py index 9e2330b..5ed8aea 100644 --- a/captain/util.py +++ b/captain/util.py @@ -13,6 +13,7 @@ from rich.panel import Panel from rich.rule import Rule +from rich.syntax import Syntax import captain @@ -79,7 +80,15 @@ def run( # If not capturing, and debugging, emit a Rich separator line, for visual clarity. if not capture and log.isEnabledFor(logging.DEBUG): - captain.console.print(Panel(f"Running command: {' '.join(cmd)}", style="green")) + syntax = Syntax( + " ".join(cmd), "bash", theme="monokai", word_wrap=True, background_color="default" + ) + panel = Panel( + syntax, + title="Executing shell command", + width=captain.console.width, + ) + captain.console.print(panel) captain.console.print(Rule(f"⮕ Starting subprocess: {cmd} ⮕", style="green")) proc = subprocess.run( From 4a893e17e891971ee4cdfb27f650b333e506a777 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Wed, 8 Apr 2026 18:31:55 +0200 Subject: [PATCH 69/93] gha: don't upload .iso as part of initramfs artifact - we've a separate artifact for .iso Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed55f81..da145cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -159,8 +159,12 @@ jobs: uses: actions/upload-artifact@v6 with: name: initramfs-${{ matrix.FLAVOR_ID }}-${{ matrix.arch }} - path: out/ + # The full 'out/' directory contents, but not any .iso files (if any) since those are uploaded later + path: | + out/ + !out/**/*.iso retention-days: 1 + # do not upload any .iso files (exclude) # ------------------------------------------------------------------- # UEFI-bootable ISO - only for certain flavors (eg trixie-full) From a6b98620c97c8f3651874c9bfdcfa99bcf021b01 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Thu, 9 Apr 2026 16:37:56 +0200 Subject: [PATCH 70/93] flavor/gha: add `trixie-rockchip64-vendor` and `trixie-armbian-rpi` flavors - also build them - `trixie-armbian-rpi` strays from standard naming as I think we might need to build our own kernel for RPi to achieve small-enough-for-eeprom-netboot eventually Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 2 ++ .../flavors/trixie_armbian_rpi/__init__.py | 22 +++++++++++++++++++ .../trixie_rockchip64_vendor/__init__.py | 22 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 captain/flavors/trixie_armbian_rpi/__init__.py create mode 100644 captain/flavors/trixie_rockchip64_vendor/__init__.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da145cc..1ea951b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,7 +126,9 @@ jobs: - { arch: amd64, output_arch: x86_64, iso: true, FLAVOR_ID: "trixie-full" } - { arch: arm64, output_arch: aarch64, iso: true, FLAVOR_ID: "trixie-full" } - { arch: arm64, output_arch: aarch64, iso: false, FLAVOR_ID: "trixie-rockchip64" } + - { arch: arm64, output_arch: aarch64, iso: false, FLAVOR_ID: "trixie-rockchip64-vendor" } - { arch: arm64, output_arch: aarch64, iso: false, FLAVOR_ID: "trixie-meson64" } + - { arch: arm64, output_arch: aarch64, iso: false, FLAVOR_ID: "trixie-armbian-rpi" } env: ARCH: ${{ matrix.arch }} MKOSI_MODE: docker diff --git a/captain/flavors/trixie_armbian_rpi/__init__.py b/captain/flavors/trixie_armbian_rpi/__init__.py new file mode 100644 index 0000000..a5c4257 --- /dev/null +++ b/captain/flavors/trixie_armbian_rpi/__init__.py @@ -0,0 +1,22 @@ +import logging +from dataclasses import dataclass + +from captain.flavor import BaseFlavor +from captain.flavors.common_armbian import ArmbianCommonFlavor + +log: logging.Logger = logging.getLogger(__name__) + + +def create_flavor() -> BaseFlavor: + return TrixieArmbianRPiFlavor() + + +@dataclass +class TrixieArmbianRPiFlavor(ArmbianCommonFlavor): + id = "trixie-armbian-rpi" + name = "Trixie for Raspberry Pi - Armbian bcm2711-current Kernel" + description = "Debian Trixie based on Armbian's rockchip64-edge kernel" + supported_architectures = frozenset(["arm64"]) # does NOT support amd64 + + def kernel_packages(self) -> set[str]: + return {"linux-image-current-bcm2711"} diff --git a/captain/flavors/trixie_rockchip64_vendor/__init__.py b/captain/flavors/trixie_rockchip64_vendor/__init__.py new file mode 100644 index 0000000..0b9213f --- /dev/null +++ b/captain/flavors/trixie_rockchip64_vendor/__init__.py @@ -0,0 +1,22 @@ +import logging +from dataclasses import dataclass + +from captain.flavor import BaseFlavor +from captain.flavors.common_armbian import ArmbianCommonFlavor + +log: logging.Logger = logging.getLogger(__name__) + + +def create_flavor() -> BaseFlavor: + return TrixieRockchip64VendorFlavor() + + +@dataclass +class TrixieRockchip64VendorFlavor(ArmbianCommonFlavor): + id = "trixie-rockchip64-vendor" + name = "Trixie for Rockchip 64-bit ARM machines - Rockchip Vendor Kernel" + description = "Debian Trixie based with Armbian's rk35xx-vendor kernel" + supported_architectures = frozenset(["arm64"]) # does NOT support amd64 + + def kernel_packages(self) -> set[str]: + return {"linux-image-vendor-rk35xx"} From c0b899f7ff519c9f1eb4ff4b369e9cf5ae0cc4f6 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Sun, 12 Apr 2026 08:50:43 +0200 Subject: [PATCH 71/93] captain: tink-agent-setup Wants/After time-set.target - so time is set to something more reasonable, otherwise pulling fails with date-related TLS issues Signed-off-by: Ricardo Pardini --- .../mkosi.extra/etc/systemd/system/tink-agent-setup.service | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/tink-agent-setup.service b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/tink-agent-setup.service index 6954014..d219370 100644 --- a/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/tink-agent-setup.service +++ b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/tink-agent-setup.service @@ -1,7 +1,7 @@ [Unit] Description=Tink Agent Setup (pull image and extract binary) -After=containerd.service network-online.target systemd-resolved.service -Wants=network-online.target +After=containerd.service network-online.target systemd-resolved.service time-set.target +Wants=network-online.target time-set.target Requires=containerd.service # Only start when Tinkerbell parameters are on the kernel command line ConditionKernelCommandLine=|tink_worker_image From 9fed67fe0e7eda5ec085731f50279477a560b86d Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Sun, 12 Apr 2026 09:34:03 +0200 Subject: [PATCH 72/93] captain: move systemd enablement/disablement from postinst to systemd preset files - doing this in postinst has no effect, as mkosi applies presets _after_ postinst - it _could_ be done in finalize, but that would un-do the presets applied by mkosi - so instead do it all in two preset files: - one for generic OS-related enablement and disablement (20-captainos-base) - one for tink-agent related stuff (10-captainos-tink) Signed-off-by: Ricardo Pardini --- .../system-preset/10-captainos-tink.preset | 6 ++ .../system-preset/20-captainos-base.preset | 19 +++++++ .../common_debian/mkosi.postinst.sh.j2 | 55 ++----------------- 3 files changed, 30 insertions(+), 50 deletions(-) create mode 100644 captain/flavors/common_debian/mkosi.extra/etc/systemd/system-preset/10-captainos-tink.preset create mode 100644 captain/flavors/common_debian/mkosi.extra/etc/systemd/system-preset/20-captainos-base.preset diff --git a/captain/flavors/common_debian/mkosi.extra/etc/systemd/system-preset/10-captainos-tink.preset b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system-preset/10-captainos-tink.preset new file mode 100644 index 0000000..3970f43 --- /dev/null +++ b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system-preset/10-captainos-tink.preset @@ -0,0 +1,6 @@ +# CaptainOS preset: Tink-agent and related CaptainOS services +enable tink-agent.service +enable tink-agent-setup.service +enable captainos-static-network.service +enable captainos-banner.service + diff --git a/captain/flavors/common_debian/mkosi.extra/etc/systemd/system-preset/20-captainos-base.preset b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system-preset/20-captainos-base.preset new file mode 100644 index 0000000..8063245 --- /dev/null +++ b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system-preset/20-captainos-base.preset @@ -0,0 +1,19 @@ +# CaptainOS preset: Base system services and cleanup +enable systemd-networkd.service +enable systemd-resolved.service +enable systemd-timesyncd.service +enable systemd-time-wait-sync.service +enable containerd.service +enable rsyslog.service +enable rsyslog-hostname-reload.path + +disable apt-daily.timer +disable apt-daily-upgrade.timer +disable e2scrub_all.timer +disable e2scrub_reap.service +disable fstrim.timer +disable logrotate.timer +disable man-db.timer +disable remote-fs.target +disable systemd-firstboot.service + diff --git a/captain/flavors/common_debian/mkosi.postinst.sh.j2 b/captain/flavors/common_debian/mkosi.postinst.sh.j2 index e090e19..0dd8da0 100755 --- a/captain/flavors/common_debian/mkosi.postinst.sh.j2 +++ b/captain/flavors/common_debian/mkosi.postinst.sh.j2 @@ -16,59 +16,14 @@ mount || true log info "Generating fresh CA certificate bundle..." update-ca-certificates --fresh 2>/dev/null || true -# @TODO: this is the wrong place to do any systemd enablement/disablement/masking: mkosi will -# after this still apply systemd "presets" which will change the game completely. -# This is better done in a finalize.chroot script which would run after that. -# alternatively: adopt/introduce systemd presets, but that's a can of worms. - -log info "Listing all systemd units..." -systemctl list-unit-files --no-pager || true - -log info "CaptainOS post-install: configuring services..." -# Enable core services -declare -a units_to_enable=( - systemd-networkd systemd-resolved systemd-timesyncd containerd captainos-banner - systemd-time-wait-sync # wait for time sync - captainos-static-network tink-agent-setup tink-agent rsyslog rsyslog-hostname-reload.path -) -declare unit -for unit in "${units_to_enable[@]}"; do - log info "Enabling systemd unit ${unit}..." - if systemctl enable "${unit}"; then - log info " ${unit} enabled successfully." - else - log warning " Failed to enable ${unit}, but continuing anyway..." - fi -done +# Service enablement/disablement is handled via systemd preset files: +# - mkosi.extra/etc/systemd/system-preset/10-captainos-tink.preset (tink-agent and CaptainOS services) +# - mkosi.extra/etc/systemd/system-preset/20-captainos-base.preset (base system services and cleanup) +# mkosi applies presets automatically after post-install, so no manual +# systemctl enable/disable calls are needed here. # Root password is set via mkosi.conf RootPassword= setting -# Disable unnecessary systemd units to speed up boot -declare -a units_to_disable=( - apt-daily.timer - apt-daily-upgrade.timer - e2scrub_all.timer - e2scrub_reap.service - fstrim.timer - logrotate.timer - man-db.timer - remote-fs.target - systemd-firstboot.service -) -for unit in "${units_to_disable[@]}"; do - log info "Disabling and masking systemd unit ${unit}..." - if systemctl disable "${unit}"; then - log info " ${unit} disabled successfully." - else - log warning " Failed to disable ${unit}, but continuing anyway..." - fi - - if systemctl mask "${unit}"; then - log info " ${unit} masked successfully." - else - log warning " Failed to mask ${unit}, but continuing anyway..." - fi -done # Set default target to multi-user (no graphical) log info "Setting default systemd target to multi-user.target (no graphical)..." From 0ad40dbb88a60b8fc1030ef94bb4f8310f6c0edf Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Sun, 12 Apr 2026 09:59:09 +0200 Subject: [PATCH 73/93] captain: loosen systemd-networkd-wait-online to accept any interface and 15s timeout - otherwise hangs and delays on machines with multiple interfaces Signed-off-by: Ricardo Pardini --- .../systemd-networkd-wait-online.service.d/override.conf | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 captain/flavors/common_debian/mkosi.extra/etc/systemd/system/systemd-networkd-wait-online.service.d/override.conf diff --git a/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/systemd-networkd-wait-online.service.d/override.conf b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/systemd-networkd-wait-online.service.d/override.conf new file mode 100644 index 0000000..8437ae7 --- /dev/null +++ b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/systemd-networkd-wait-online.service.d/override.conf @@ -0,0 +1,5 @@ +[Service] +ExecStart= +ExecStart=/usr/lib/systemd/systemd-networkd-wait-online --any --timeout=15 +TimeoutStartSec=15 + From fd8dd4809ec017b583c1fc456b3638d83e7cb483 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Sun, 12 Apr 2026 11:32:34 +0200 Subject: [PATCH 74/93] captain: tink-agent-setup Wants/After time-sync.target - time-set.target is _not_ enough for machines without an RTC and working battery - time-sync.target requires timesyncd to be fully sync'ed - so time is set to something more reasonable, otherwise pulling fails with date-related TLS issues Signed-off-by: Ricardo Pardini --- .../mkosi.extra/etc/systemd/system/tink-agent-setup.service | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/tink-agent-setup.service b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/tink-agent-setup.service index d219370..d7242a3 100644 --- a/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/tink-agent-setup.service +++ b/captain/flavors/common_debian/mkosi.extra/etc/systemd/system/tink-agent-setup.service @@ -1,7 +1,7 @@ [Unit] Description=Tink Agent Setup (pull image and extract binary) -After=containerd.service network-online.target systemd-resolved.service time-set.target -Wants=network-online.target time-set.target +After=containerd.service network-online.target systemd-resolved.service time-sync.target +Wants=network-online.target time-sync.target Requires=containerd.service # Only start when Tinkerbell parameters are on the kernel command line ConditionKernelCommandLine=|tink_worker_image From f014bd76b1b38023b72c8ca67e7037fdafcde9c9 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 13 Apr 2026 19:12:37 +0200 Subject: [PATCH 75/93] bring back "some" kernel stuff Signed-off-by: Ricardo Pardini --- captain/cli/_kernel.py | 62 +++ captain/cli/_main.py | 2 +- captain/cli/_stages.py | 58 +- captain/config.py | 38 +- captain/docker.py | 9 + captain/kernel.py | 289 ++++++++++ captain/util.py | 28 +- kernel.configs/6.18.y.amd64 | 1032 +++++++++++++++++++++++++++++++++++ kernel.configs/6.18.y.arm64 | 515 +++++++++++++++++ 9 files changed, 2028 insertions(+), 5 deletions(-) create mode 100644 captain/cli/_kernel.py create mode 100644 captain/kernel.py create mode 100644 kernel.configs/6.18.y.amd64 create mode 100644 kernel.configs/6.18.y.arm64 diff --git a/captain/cli/_kernel.py b/captain/cli/_kernel.py new file mode 100644 index 0000000..e1dc795 --- /dev/null +++ b/captain/cli/_kernel.py @@ -0,0 +1,62 @@ +"""``captain tools`` — download tools (containerd, runc, nerdctl, CNI plugins).""" + +from __future__ import annotations + +import logging + +import click + +from captain import artifacts, config +from captain.cli._main import CliContext, cli +from captain.cli._stages import _build_kernel_stage + +log = logging.getLogger(__name__) + + +@cli.command( + "kernel", + short_help="Build Linux Kernel for CaptainOS trixie-slim flavor.", +) +@click.option( + "--kernel-mode", + envvar="KERNEL_MODE", + default="docker", + show_default=True, + type=click.Choice(["docker", "native", "skip"], case_sensitive=False), + metavar="MODE", + help="Kernel build stage execution mode (docker, native, skip).", +) +@click.option( + "--force-kernel", + envvar="FORCE_KERNEL", + is_flag=True, + default=False, + help="Build kernel even if outputs already exist.", +) +@click.option( + "--kernel-version", + envvar="KERNEL_VERSION", + default=config.DEFAULT_KERNEL_VERSION, + show_default=True, + help="Kernel version to build. Must match an official tarball.", +) +@click.pass_obj +def tools_cmd( + cli_ctx: CliContext, + *, + kernel_mode: str, + force_kernel: bool, + kernel_version: str, +) -> None: + log.warning("CLI kernel mode: %s", kernel_mode) + + cfg = cli_ctx.make_config( + force_kernel=force_kernel, + kernel_version=kernel_version, + kernel_mode=kernel_mode, + build_kernel=True, + ) + + _build_kernel_stage(cfg) + artifacts.collect_kernel(cfg) + log.info("Kernel build stage complete!") diff --git a/captain/cli/_main.py b/captain/cli/_main.py index 664007e..14b6168 100644 --- a/captain/cli/_main.py +++ b/captain/cli/_main.py @@ -182,6 +182,6 @@ def cli( def main() -> None: """Console-script entry point.""" # Import subcommand modules to register them on the group. - from captain.cli import _build, _builder, _iso, _release_publish, _tools # noqa: F401 + from captain.cli import _build, _builder, _iso, _kernel, _release_publish, _tools # noqa: F401 cli() diff --git a/captain/cli/_stages.py b/captain/cli/_stages.py index e7082e7..948bf6a 100644 --- a/captain/cli/_stages.py +++ b/captain/cli/_stages.py @@ -4,13 +4,67 @@ import logging -from captain import docker, iso, tools +from captain import docker, iso, kernel, tools from captain.config import Config -from captain.util import check_mkosi_dependencies, run +from captain.util import check_kernel_dependencies, check_mkosi_dependencies, run log = logging.getLogger(__name__) +def _build_kernel_stage(cfg: Config) -> None: + """Run the kernel build stage according to *cfg.kernel_mode*.""" + log.warning("Building kernel stage in mode %s", cfg.kernel_mode) + + # --- skip --------------------------------------------------------- + if cfg.kernel_mode == "skip": + log.info("KERNEL_MODE=skip — skipping kernel build") + return + + # --- idempotency -------------------------------------------------- + modules_dir = cfg.modules_output / "usr" / "lib" / "modules" + vmlinuz_dir = cfg.kernel_output + has_vmlinuz = vmlinuz_dir.is_dir() and any(vmlinuz_dir.glob("vmlinuz-*")) + log.debug("Kernel build idempotency check: modules_dir=%s exists=%s", modules_dir, + modules_dir.is_dir()) + log.debug("Kernel build idempotency check: vmlinuz_dir=%s exists=%s", vmlinuz_dir, + vmlinuz_dir.is_dir()) + log.debug( + "Checking kernel build idempotency: modules_dir=%s, has_vmlinuz=%s", + modules_dir, + has_vmlinuz, + ) + + if modules_dir.is_dir() and has_vmlinuz and not cfg.force_kernel: + log.info("Kernel already built (use --force-kernel to rebuild)") + return + + if modules_dir.is_dir() and not has_vmlinuz: + log.warning("Modules exist but vmlinuz is missing — rebuilding kernel") + + # --- native ------------------------------------------------------- + if cfg.kernel_mode == "native": + missing = check_kernel_dependencies(cfg.arch) + if missing: + log.error("Missing kernel build tools: %s", ", ".join(missing)) + log.error("Install them or set --kernel-mode=docker.") + raise SystemExit(1) + log.info("Building kernel (native)...") + kernel.build(cfg) + return + + # --- docker ------------------------------------------------------- + docker.obtain_builder(cfg) + log.info("Building kernel (docker relaunch)...") + docker.run_captain_in_builder(cfg, "kernel") + docker.fix_docker_ownership( + cfg, + [ + f"/work/mkosi.output/kernel/{cfg.kernel_version}/{cfg.arch}", + "/work/out", + ], + ) + + def _build_tools_stage(cfg: Config) -> None: """Run the tools download stage according to *cfg.tools_mode*.""" diff --git a/captain/config.py b/captain/config.py index fee1ad3..ddf39ec 100644 --- a/captain/config.py +++ b/captain/config.py @@ -11,13 +11,19 @@ log = logging.getLogger(__name__) -# Valid values for ISO_MODE and MKOSI_MODE. +# Valid values for KERNEL_MODE and MKOSI_MODE. VALID_MODES = ("docker", "native", "skip") # The single source of truth for the default flavor. # Override at runtime via --flavor-id or FLAVOR_ID env var. DEFAULT_FLAVOR_ID = "trixie-full" +# Fallback for the default kernel version. +# The latest version can be obtained and passed as --kernel-version/KERNEL_VERSION +# via <@TODO cmd for listing from cached releases.json +# Override at runtime via --kernel-version or KERNEL_VERSION env var. +DEFAULT_KERNEL_VERSION = "6.18.22" + @dataclass(slots=True) class Config: @@ -31,6 +37,13 @@ class Config: arch: str = "amd64" flavor_id: str = DEFAULT_FLAVOR_ID + # For kernel build + build_kernel: bool = False + kernel_version: str = DEFAULT_KERNEL_VERSION + kernel_config: str | None = None + kernel_src: str | None = None + force_kernel: bool = False + # Docker builder_registry: str | None = None builder_repository: str | None = None @@ -42,6 +55,7 @@ class Config: mkosi_mode: str = "docker" iso_mode: str = "docker" release_mode: str = "docker" + kernel_mode: str = "docker" # Force flags force_tools: bool = False @@ -69,6 +83,7 @@ def __post_init__(self) -> None: ("MKOSI_MODE", self.mkosi_mode), ("ISO_MODE", self.iso_mode), ("RELEASE_MODE", self.release_mode), + ("KERNEL_MODE", self.kernel_mode), ): if value not in VALID_MODES: log.error("%s=%r is invalid. Valid values: %s", name, value, ", ".join(VALID_MODES)) @@ -84,6 +99,27 @@ def tools_output(self) -> Path: """ return self.project_dir / "mkosi.output" / "tools" / self.arch + @property + def kernel_output(self) -> Path: + """Per-version, per-arch directory for all kernel build artifacts. + + Contains the vmlinuz image (loaded separately by iPXE) and + a ``modules/`` subtree that mirrors a root filesystem layout + (``usr/lib/modules/{kver}/``) so it can be passed directly + as an ``--extra-tree=`` to mkosi. + """ + return self.project_dir / "mkosi.output" / "kernel" / self.kernel_version / self.arch + + @property + def modules_output(self) -> Path: + """Per-version, per-arch root for kernel modules. + + Returns ``kernel/{version}/{arch}/modules`` which contains a + merged-usr tree (``usr/lib/modules/{kver}/``) suitable for + passing as ``--extra-tree=`` to mkosi. + """ + return self.kernel_output / "modules" + @property def mkosi_output(self) -> Path: return self.project_dir / "mkosi.output" diff --git a/captain/docker.py b/captain/docker.py index 5ce7fd8..254ac57 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -12,6 +12,7 @@ import captain from captain.config import Config +from captain.kernel import KERNEL_BUILD_BASE_PATH from captain.util import detect_current_machine_arch, run log = logging.getLogger(__name__) @@ -152,10 +153,13 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: "CAPTAIN_IN_DOCKER": "docker", "ARCH": cfg.arch, "FLAVOR_ID": cfg.flavor_id, + "KERNEL_VERSION": cfg.kernel_version, "FORCE_TOOLS": str(int(cfg.force_tools)), "FORCE_ISO": str(int(cfg.force_iso)), + "FORCE_KERNEL": f"{int(cfg.force_kernel)!s}", "BUILDAH_ISOLATION": "chroot", "BUILDAH_INSECURE": os.environ.get("BUILDAH_INSECURE", ""), + "KERNEL_MODE": "native", "RELEASE_MODE": "native", "TOOLS_MODE": "native", "MKOSI_MODE": "native", @@ -194,6 +198,7 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: for k, v in docker_envs.items(): docker_args += ["-e", f"{k}={v}"] + docker_args += ["-v", f"{cfg.project_dir}/kernel.configs:/work/kernel.configs"] docker_args += ["-v", f"{cfg.project_dir}/mkosi.output:/work/mkosi.output"] docker_args += ["-v", f"{cfg.project_dir}/out:/work/out"] @@ -210,6 +215,10 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: docker_args += ["-v", f"{cfg.project_dir}/build.py:/work/build.py"] docker_args += ["--mount", "type=volume,source=captain-cache-packages,target=/cache/packages"] + docker_args += [ + "--mount", + f"type=volume,source=captain-kernel-build,target={KERNEL_BUILD_BASE_PATH}", + ] docker_args.extend(extra_args) run(docker_args) diff --git a/captain/kernel.py b/captain/kernel.py new file mode 100644 index 0000000..0b307aa --- /dev/null +++ b/captain/kernel.py @@ -0,0 +1,289 @@ +"""Kernel download, configuration, compilation, and installation. + +Heavy lifting (make, strip) is still done via subprocess — only the +orchestration is in Python. Called directly by ``cli._build_kernel_stage`` +in both native and Docker modes (inside the container ``build.py kernel`` +re-enters via the CLI with all modes forced to native). +""" + +from __future__ import annotations + +import logging +import os +import re +import shutil +import tarfile +import urllib.error +import urllib.request +from pathlib import Path + +from rich.progress import ( + BarColumn, + DownloadColumn, + Progress, + TextColumn, + TimeRemainingColumn, + TransferSpeedColumn, +) + +from captain import console +from captain.config import Config +from captain.util import ensure_dir, run, safe_extractall + +KERNEL_BUILD_BASE_PATH = "/var/tmp/kernel-build" + +log = logging.getLogger(__name__) + +_DOWNLOAD_TIMEOUT = 60 # seconds + + +def _download_with_progress(url: str, filename: Path) -> None: + """Download *url* to *filename* with a Rich progress bar.""" + req = urllib.request.Request(url) + with urllib.request.urlopen(req, timeout=_DOWNLOAD_TIMEOUT) as resp: + total = int(resp.headers.get("Content-Length", 0)) or None + with Progress( + TextColumn("[progress.description]{task.description}"), + BarColumn(), + DownloadColumn(), + TransferSpeedColumn(), + TimeRemainingColumn(), + console=console, + ) as progress: + task = progress.add_task(" Downloading", total=total) + with open(filename, "wb") as out: + while True: + buf = resp.read(8192) + if not buf: + break + out.write(buf) + progress.update(task, advance=len(buf)) + + +def download_kernel(version: str, dest_dir: Path) -> Path: + """Download and extract a kernel tarball. Returns the source directory.""" + src_dir = dest_dir / f"linux-{version}" + if src_dir.is_dir(): + log.info("Using cached kernel source at %s", src_dir) + return src_dir + + major = version.split(".")[0] + url = f"https://cdn.kernel.org/pub/linux/kernel/v{major}.x/linux-{version}.tar.xz" + tarball = dest_dir / f"linux-{version}.tar.xz" + + log.info("Downloading kernel %s...", version) + log.info(" URL: %s", url) + ensure_dir(dest_dir) + try: + _download_with_progress(url, tarball) + except urllib.error.HTTPError as exc: + log.error("Download failed: %s — %s", exc, url) + raise SystemExit(1) from None + except urllib.error.URLError as exc: + log.error("Download failed: %s — %s", exc.reason, url) + raise SystemExit(1) from None + + log.info("Extracting kernel source...") + with tarfile.open(tarball, "r:xz") as tf: + safe_extractall(tf, path=dest_dir) + tarball.unlink() + + return src_dir + + +def _kernel_branch(version: str) -> str: + """Derive the stable branch prefix from a full kernel version.""" + parts = version.split(".") + if len(parts) < 2: + log.error("Invalid kernel version format: %s", version) + raise SystemExit(1) + return f"{parts[0]}.{parts[1]}.y" + + +def _find_defconfig(cfg: Config) -> Path: + """Locate the defconfig for the current kernel version and architecture.""" + if cfg.kernel_config: + explicit = Path(cfg.kernel_config) + if not explicit.is_absolute(): + explicit = cfg.project_dir / explicit + if explicit.is_file(): + return explicit + log.error("Kernel config not found: %s", explicit) + raise SystemExit(1) + + ai = cfg.arch_info + branch = _kernel_branch(cfg.kernel_version) + defconfig = cfg.project_dir / "kernel.configs" / f"{branch}.{ai.arch}" + if defconfig.is_file(): + return defconfig + + configs_dir = cfg.project_dir / "kernel.configs" + available = sorted( + { + p.name.rsplit(".", 1)[0] + for p in configs_dir.glob(f"*.{ai.arch}") + if not p.name.startswith(".") + } + ) + avail_str = ", ".join(available) if available else "(none)" + log.error( + "No kernel config found for %s on %s\n Expected: %s\n Available branches for %s: %s", + branch, + ai.arch, + defconfig, + ai.arch, + avail_str, + ) + raise SystemExit(1) + + +def configure_kernel(cfg: Config, src_dir: Path) -> None: + """Apply defconfig and run olddefconfig.""" + ai = cfg.arch_info + defconfig = _find_defconfig(cfg) + + make_env = {"ARCH": ai.kernel_arch} + if ai.cross_compile: + make_env["CROSS_COMPILE"] = ai.cross_compile + + log.info("Using defconfig: %s", defconfig) + shutil.copy2(defconfig, src_dir / ".config") + run(["make", "olddefconfig"], env=make_env, cwd=src_dir) + branch = _kernel_branch(cfg.kernel_version) + resolved = cfg.project_dir / "kernel.configs" / f".config.resolved.{branch}.{ai.arch}" + shutil.copy2(src_dir / ".config", resolved) + log.info("Resolved config saved to kernel.configs/.config.resolved.%s.%s", branch, ai.arch) + + if ai.kernel_arch == "x86_64": + log.info("Increasing COMMAND_LINE_SIZE to 4096 (x86_64)...") + setup_h = src_dir / "arch" / "x86" / "include" / "asm" / "setup.h" + text = setup_h.read_text() + new_text = re.sub( + r"#define COMMAND_LINE_SIZE\s+2048", + "#define COMMAND_LINE_SIZE 4096", + text, + ) + if new_text == text: + log.warning( + "COMMAND_LINE_SIZE patch did not match — the kernel default may have changed" + ) + setup_h.write_text(new_text) + + +def build_kernel(cfg: Config, src_dir: Path) -> str: + """Compile the kernel image and modules. Returns the built kernel version string.""" + ai = cfg.arch_info + nproc = os.cpu_count() or 1 + + make_env = {"ARCH": ai.kernel_arch} + if ai.cross_compile: + make_env["CROSS_COMPILE"] = ai.cross_compile + + log.info("Building kernel with %d jobs...", nproc) + run( + ["make", f"-j{nproc}", ai.image_target, "modules"], + env=make_env, + cwd=src_dir, + ) + + result = run( + ["make", "-s", "kernelrelease"], + env={"ARCH": ai.kernel_arch}, + capture=True, + cwd=src_dir, + ) + built_kver = result.stdout.strip() + log.info("Built kernel version: %s", built_kver) + return built_kver + + +def install_kernel(cfg: Config, src_dir: Path, built_kver: str) -> None: + """Install modules and vmlinuz into mkosi.output/kernel/{version}/{arch}/.""" + ai = cfg.arch_info + modules_root = cfg.modules_output + + make_env = {"ARCH": ai.kernel_arch} + if ai.cross_compile: + make_env["CROSS_COMPILE"] = ai.cross_compile + + log.info("Installing modules...") + run( + ["make", f"INSTALL_MOD_PATH={modules_root}", "modules_install"], + env=make_env, + cwd=src_dir, + ) + + log.info("Stripping debug symbols from modules...") + strip_cmd = f"{ai.strip_prefix}strip" + for ko in modules_root.rglob("*.ko"): + run([strip_cmd, "--strip-unneeded", str(ko)], check=False) + + log.info("Compressing kernel modules with zstd...") + for ko in modules_root.rglob("*.ko"): + run(["zstd", "--rm", "-q", "-19", str(ko)], check=True) + + mod_base = modules_root / "lib" / "modules" / built_kver + (mod_base / "build").unlink(missing_ok=True) + (mod_base / "source").unlink(missing_ok=True) + + usr_moddir = ensure_dir(modules_root / "usr" / "lib" / "modules" / built_kver) + if mod_base.is_dir(): + for item in mod_base.iterdir(): + dest = usr_moddir / item.name + if dest.exists(): + if dest.is_dir(): + shutil.rmtree(dest) + else: + dest.unlink() + shutil.move(str(item), str(dest)) + shutil.rmtree(modules_root / "lib", ignore_errors=True) + + log.info("Running depmod for compressed modules...") + run( + ["depmod", "-a", "-b", str(modules_root / "usr"), built_kver], + check=True, + ) + + kernel_image = src_dir / ai.kernel_image_path + vmlinuz_dir = ensure_dir(cfg.kernel_output) + + for old in vmlinuz_dir.glob("vmlinuz-*"): + old.unlink(missing_ok=True) + + shutil.copy2(kernel_image, vmlinuz_dir / f"vmlinuz-{built_kver}") + + log.info("Kernel build complete:") + vmlinuz = vmlinuz_dir / f"vmlinuz-{built_kver}" + vmlinuz_size = vmlinuz.stat().st_size / (1024 * 1024) + log.info(" Image: %s (%.1fM)", vmlinuz, vmlinuz_size) + log.info(" Modules: %s/", usr_moddir) + log.info(" Version: %s", built_kver) + log.info(" Output: %s", cfg.kernel_output) + + +def build(cfg: Config) -> None: + """Full kernel build pipeline — download, configure, build, install.""" + if cfg.kernel_output.exists(): + shutil.rmtree(cfg.kernel_output) + ensure_dir(cfg.kernel_output) + + log.info("Preparing kernel source...") + build_dir = Path(KERNEL_BUILD_BASE_PATH) + + if cfg.kernel_src and Path(cfg.kernel_src).is_dir(): + log.info("Using provided kernel source at %s", cfg.kernel_src) + src_dir = Path(cfg.kernel_src) + else: + src_dir = download_kernel(cfg.kernel_version, build_dir) + + log.debug("Kernel source directory: %s", src_dir) + + log.info("Configuring kernel...") + configure_kernel(cfg, src_dir) + + log.info("Building kernel...") + built_kver = build_kernel(cfg, src_dir) + log.debug("Built kernel version: %s", built_kver) + + log.info("Installing kernel...") + install_kernel(cfg, src_dir, built_kver) diff --git a/captain/util.py b/captain/util.py index 5ed8aea..5edf596 100644 --- a/captain/util.py +++ b/captain/util.py @@ -26,9 +26,14 @@ class ArchInfo: arch: str # canonical name: amd64 | arm64 output_arch: str # user-facing name in artifact filenames: x86_64 | aarch64 + kernel_arch: str # kernel ARCH value + cross_compile: str # CROSS_COMPILE prefix (empty for native) + image_target: str # kernel image make target + kernel_image_path: str # relative path to built kernel image dl_arch: str # architecture name in download URLs mkosi_arch: str # mkosi --architecture value qemu_binary: str # QEMU system emulator binary + strip_prefix: str # prefix for strip command def get_arch_info(arch: str) -> ArchInfo: @@ -38,17 +43,27 @@ def get_arch_info(arch: str) -> ArchInfo: return ArchInfo( arch="amd64", output_arch="x86_64", + kernel_arch="x86_64", + cross_compile="", + image_target="bzImage", + kernel_image_path="arch/x86/boot/bzImage", dl_arch="amd64", mkosi_arch="x86-64", qemu_binary="qemu-system-x86_64", + strip_prefix="", ) case "arm64" | "aarch64": return ArchInfo( arch="arm64", output_arch="aarch64", + kernel_arch="arm64", + cross_compile="aarch64-linux-gnu-", + image_target="Image", + kernel_image_path="arch/arm64/boot/Image", dl_arch="arm64", mkosi_arch="arm64", qemu_binary="qemu-system-aarch64", + strip_prefix="aarch64-linux-gnu-", ) case _: log.error("Unsupported architecture: %s", arch) @@ -162,6 +177,17 @@ def _missing(cmds: list[str]) -> list[str]: return [cmd for cmd in cmds if _shutil.which(cmd) is None] +def check_kernel_dependencies(arch: str) -> list[str]: + """Check host tools required for a native kernel build. + + Returns a list of missing command names (empty if all found). + """ + required = ["make", "gcc", "flex", "bison", "bc", "rsync", "strip", "zstd", "depmod"] + if arch in ("arm64", "aarch64"): + required += ["aarch64-linux-gnu-gcc", "aarch64-linux-gnu-strip"] + return _missing(required) + + def check_mkosi_dependencies() -> list[str]: """Check host tools required for a native mkosi image build. @@ -192,4 +218,4 @@ def check_dependencies(arch: str) -> list[str]: Returns a list of missing command names (empty if all found). """ - return check_mkosi_dependencies() + return check_kernel_dependencies(arch) + check_mkosi_dependencies() diff --git a/kernel.configs/6.18.y.amd64 b/kernel.configs/6.18.y.amd64 new file mode 100644 index 0000000..c6d243c --- /dev/null +++ b/kernel.configs/6.18.y.amd64 @@ -0,0 +1,1032 @@ +# CaptainOS kernel defconfig for x86_64 +# Comprehensive config for bare-metal provisioning with container/network support. +# Derived from HookOS generic-6.6.y-x86_64 proven config, adapted for kernel 6.18. +# +# Generate a full .config from this: +# cp 6.18.y.amd64 .config && make olddefconfig + +# --- Architecture --- +CONFIG_64BIT=y +CONFIG_X86_64=y +CONFIG_SMP=y +CONFIG_NR_CPUS=128 +# CONFIG_X86_EXTENDED_PLATFORM is not set +CONFIG_X86_INTEL_LPSS=y +CONFIG_HYPERVISOR_GUEST=y +CONFIG_PARAVIRT_SPINLOCKS=y +CONFIG_XEN=y +CONFIG_XEN_PVH=y +CONFIG_X86_REROUTE_FOR_BROKEN_BOOT_IRQS=y +# CONFIG_X86_MCE is not set +CONFIG_X86_MSR=y +CONFIG_X86_CPUID=y +# CONFIG_X86_5LEVEL is not set +CONFIG_HZ_1000=y +CONFIG_PHYSICAL_ALIGN=0x1000000 +CONFIG_LEGACY_VSYSCALL_NONE=y +# CONFIG_MODIFY_LDT_SYSCALL is not set +CONFIG_IA32_EMULATION=y + +# --- General --- +CONFIG_LOCALVERSION="-captainos" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_DEFAULT_HOSTNAME="captainos" +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_AUDIT=y +CONFIG_NO_HZ_IDLE=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_PREEMPT_VOLUNTARY=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_EXPERT=y +CONFIG_KEXEC=y +CONFIG_KEXEC_FILE=y +CONFIG_SCHED_AUTOGROUP=y +CONFIG_CHECKPOINT_RESTORE=y +CONFIG_KPROBES=y +CONFIG_JUMP_LABEL=y +CONFIG_BINFMT_MISC=y + +# --- Cgroups --- +CONFIG_CGROUPS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_RDMA=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_CGROUP_MISC=y +CONFIG_CGROUP_HUGETLB=y +CONFIG_CGROUP_NET_PRIO=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_RT_GROUP_SCHED=y +CONFIG_CGROUP_SCHED=y +CONFIG_CPUSETS=y + +# --- Namespaces --- +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_PID_NS=y +CONFIG_NET_NS=y +CONFIG_UTS_NS=y +CONFIG_IPC_NS=y + +# --- Init / Initramfs --- +CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="" +# CONFIG_RD_BZIP2 is not set +# CONFIG_RD_LZMA is not set +CONFIG_RD_GZIP=y +CONFIG_RD_XZ=y +# CONFIG_RD_LZO is not set +# CONFIG_RD_LZ4 is not set +CONFIG_RD_ZSTD=y + +# --- Memory management --- +CONFIG_SLAB_FREELIST_RANDOM=y +# CONFIG_COMPAT_BRK is not set +CONFIG_MEMORY_HOTPLUG=y +CONFIG_MEMORY_HOTREMOVE=y +CONFIG_KSM=y +CONFIG_DEFAULT_MMAP_MIN_ADDR=65536 +CONFIG_TRANSPARENT_HUGEPAGE=y +CONFIG_ZONE_DEVICE=y +CONFIG_HUGETLBFS=y + +# --- Power / Suspend --- +# CONFIG_SUSPEND is not set + +# --- ACPI --- +CONFIG_ACPI=y +# CONFIG_ACPI_REV_OVERRIDE_POSSIBLE is not set +CONFIG_ACPI_DOCK=y +CONFIG_ACPI_IPMI=y +CONFIG_ACPI_PROCESSOR_AGGREGATOR=y +CONFIG_ACPI_SBS=y +CONFIG_ACPI_NFIT=y +CONFIG_ACPI_APEI=y +CONFIG_ACPI_APEI_GHES=y + +# --- CPU frequency --- +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_X86_PCC_CPUFREQ=y +CONFIG_X86_ACPI_CPUFREQ=y +CONFIG_X86_POWERNOW_K8=y +CONFIG_X86_P4_CLOCKMOD=y +CONFIG_CPU_IDLE_GOV_LADDER=y +CONFIG_INTEL_IDLE=y + +# --- KVM --- +CONFIG_KVM=m +CONFIG_KVM_INTEL=m +CONFIG_KVM_AMD=m + +# --- Networking --- +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_PACKET_DIAG=y +CONFIG_UNIX=y +CONFIG_UNIX_DIAG=y +CONFIG_XFRM_USER=m +CONFIG_XFRM_SUB_POLICY=y +CONFIG_XFRM_STATISTICS=y +CONFIG_NET_KEY=m +CONFIG_NET_KEY_MIGRATE=y +CONFIG_XDP_SOCKETS=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_FIB_TRIE_STATS=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_NET_IPIP=y +CONFIG_NET_IPGRE_DEMUX=y +CONFIG_NET_IPGRE=m +CONFIG_NET_IPGRE_BROADCAST=y +CONFIG_IP_MROUTE=y +CONFIG_IP_MROUTE_MULTIPLE_TABLES=y +CONFIG_IP_PIMSM_V1=y +CONFIG_IP_PIMSM_V2=y +CONFIG_NET_IPVTI=m +CONFIG_NET_FOU_IP_TUNNELS=y +CONFIG_INET_AH=m +CONFIG_INET_ESP=m +CONFIG_INET_IPCOMP=m +CONFIG_INET_UDP_DIAG=y +CONFIG_TCP_MD5SIG=y +CONFIG_SYN_COOKIES=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_INET6_AH=m +CONFIG_INET6_ESP=m +CONFIG_INET6_IPCOMP=m +CONFIG_IPV6_MIP6=m +CONFIG_IPV6_ILA=m +CONFIG_IPV6_VTI=m +CONFIG_IPV6_SIT=m +CONFIG_IPV6_SIT_6RD=y +CONFIG_IPV6_GRE=m +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_NETLABEL=y +CONFIG_NETWORK_SECMARK=y +CONFIG_BRIDGE=y +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=y +CONFIG_BONDING=m +CONFIG_TUN=y +CONFIG_VETH=y +CONFIG_MACVLAN=y +CONFIG_MACVTAP=y +CONFIG_IPVLAN=y +CONFIG_VXLAN=y +CONFIG_GENEVE=m +CONFIG_NETCONSOLE=y +CONFIG_TAP=y +CONFIG_DUMMY=m +CONFIG_NLMON=y +CONFIG_IP_SCTP=m +CONFIG_L2TP=m +CONFIG_NET_TEAM=m +CONFIG_NET_TEAM_MODE_BROADCAST=m +CONFIG_NET_TEAM_MODE_ROUNDROBIN=m +CONFIG_NET_TEAM_MODE_RANDOM=m +CONFIG_NET_TEAM_MODE_ACTIVEBACKUP=m +CONFIG_NET_TEAM_MODE_LOADBALANCE=m +CONFIG_OPENVSWITCH=m +CONFIG_VSOCKETS=m +CONFIG_VIRTIO_VSOCKETS=m +CONFIG_HYPERV_VSOCKETS=m +CONFIG_NETLINK_DIAG=y +CONFIG_MPLS_ROUTING=m +CONFIG_MPLS_IPTUNNEL=m +CONFIG_NET_SWITCHDEV=y +CONFIG_NET_9P=y +CONFIG_NET_9P_VIRTIO=y + +# --- Traffic control --- +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=m +CONFIG_NET_SCH_HFSC=m +CONFIG_NET_SCH_PRIO=m +CONFIG_NET_SCH_MULTIQ=m +CONFIG_NET_SCH_RED=m +CONFIG_NET_SCH_SFB=m +CONFIG_NET_SCH_SFQ=m +CONFIG_NET_SCH_TEQL=m +CONFIG_NET_SCH_TBF=m +CONFIG_NET_SCH_GRED=m +CONFIG_NET_SCH_NETEM=m +CONFIG_NET_SCH_DRR=m +CONFIG_NET_SCH_MQPRIO=m +CONFIG_NET_SCH_CHOKE=m +CONFIG_NET_SCH_QFQ=m +CONFIG_NET_SCH_INGRESS=m +CONFIG_NET_CLS_BASIC=y +CONFIG_NET_CLS_ROUTE4=y +CONFIG_NET_CLS_FW=y +CONFIG_NET_CLS_U32=y +CONFIG_CLS_U32_PERF=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=y +CONFIG_NET_CLS_CGROUP=y +CONFIG_NET_CLS_BPF=y +CONFIG_NET_CLS_MATCHALL=y +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_CMP=y +CONFIG_NET_EMATCH_NBYTE=y +CONFIG_NET_EMATCH_U32=y +CONFIG_NET_EMATCH_META=y +CONFIG_NET_EMATCH_TEXT=y +CONFIG_NET_EMATCH_IPSET=y +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_POLICE=y +CONFIG_NET_ACT_GACT=y +CONFIG_GACT_PROB=y +CONFIG_NET_ACT_MIRRED=y +CONFIG_NET_ACT_IPT=y +CONFIG_NET_ACT_NAT=y +CONFIG_NET_ACT_PEDIT=y +CONFIG_NET_ACT_SIMP=y +CONFIG_NET_ACT_SKBEDIT=y +CONFIG_NET_ACT_CSUM=y +CONFIG_NET_ACT_BPF=y + +# --- Netfilter --- +CONFIG_NETFILTER=y +CONFIG_NETFILTER_ADVANCED=y +CONFIG_BRIDGE_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_ZONES=y +CONFIG_NF_CONNTRACK_PROCFS=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CONNTRACK_TIMEOUT=y +CONFIG_NF_CONNTRACK_TIMESTAMP=y +CONFIG_NF_CONNTRACK_AMANDA=y +CONFIG_NF_CONNTRACK_FTP=y +CONFIG_NF_CONNTRACK_H323=y +CONFIG_NF_CONNTRACK_IRC=y +CONFIG_NF_CONNTRACK_NETBIOS_NS=y +CONFIG_NF_CONNTRACK_SNMP=y +CONFIG_NF_CONNTRACK_PPTP=y +CONFIG_NF_CONNTRACK_SANE=y +CONFIG_NF_CONNTRACK_SIP=y +CONFIG_NF_CONNTRACK_TFTP=y +CONFIG_NF_CT_NETLINK=y +CONFIG_NF_CT_NETLINK_TIMEOUT=y +CONFIG_NF_CT_NETLINK_HELPER=y +CONFIG_NETFILTER_NETLINK_GLUE_CT=y +CONFIG_NF_NAT=y +CONFIG_NF_TABLES=y +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_CT=y +CONFIG_NFT_CONNLIMIT=y +CONFIG_NFT_LOG=y +CONFIG_NFT_LIMIT=y +CONFIG_NFT_MASQ=y +CONFIG_NFT_REDIR=y +CONFIG_NFT_NAT=y +CONFIG_NFT_TUNNEL=y +CONFIG_NFT_QUEUE=y +CONFIG_NFT_REJECT=y +CONFIG_NFT_COMPAT=y +CONFIG_NFT_HASH=y +CONFIG_NFT_OSF=y +CONFIG_NFT_TPROXY=y +CONFIG_NFT_DUP_NETDEV=y +CONFIG_NFT_FWD_NETDEV=y +CONFIG_NETFILTER_XT_SET=y +CONFIG_NETFILTER_XT_TARGET_CHECKSUM=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_DSCP=y +CONFIG_NETFILTER_XT_TARGET_HMARK=y +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=y +CONFIG_NETFILTER_XT_TARGET_LOG=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_TARGET_NOTRACK=y +CONFIG_NETFILTER_XT_TARGET_TEE=y +CONFIG_NETFILTER_XT_TARGET_TPROXY=y +CONFIG_NETFILTER_XT_TARGET_TRACE=y +CONFIG_NETFILTER_XT_TARGET_TCPMSS=y +CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=y +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y +CONFIG_NETFILTER_XT_MATCH_BPF=y +CONFIG_NETFILTER_XT_MATCH_CGROUP=y +CONFIG_NETFILTER_XT_MATCH_CLUSTER=y +CONFIG_NETFILTER_XT_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNBYTES=y +CONFIG_NETFILTER_XT_MATCH_CONNLABEL=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_CPU=y +CONFIG_NETFILTER_XT_MATCH_DCCP=y +CONFIG_NETFILTER_XT_MATCH_DEVGROUP=y +CONFIG_NETFILTER_XT_MATCH_DSCP=y +CONFIG_NETFILTER_XT_MATCH_ESP=y +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y +CONFIG_NETFILTER_XT_MATCH_HELPER=y +CONFIG_NETFILTER_XT_MATCH_IPCOMP=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_IPVS=y +CONFIG_NETFILTER_XT_MATCH_L2TP=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_NFACCT=y +CONFIG_NETFILTER_XT_MATCH_OSF=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PHYSDEV=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=y +CONFIG_NETFILTER_XT_MATCH_RATEEST=y +CONFIG_NETFILTER_XT_MATCH_REALM=y +CONFIG_NETFILTER_XT_MATCH_RECENT=y +CONFIG_NETFILTER_XT_MATCH_SCTP=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y +CONFIG_NETFILTER_XT_MATCH_STRING=y +CONFIG_NETFILTER_XT_MATCH_TCPMSS=y +CONFIG_NETFILTER_XT_MATCH_TIME=y +CONFIG_NETFILTER_XT_MATCH_U32=y +CONFIG_IP_SET=y +CONFIG_IP_SET_BITMAP_IP=y +CONFIG_IP_SET_BITMAP_IPMAC=y +CONFIG_IP_SET_BITMAP_PORT=y +CONFIG_IP_SET_HASH_IP=y +CONFIG_IP_SET_HASH_IPPORT=y +CONFIG_IP_SET_HASH_IPPORTIP=y +CONFIG_IP_SET_HASH_IPPORTNET=y +CONFIG_IP_SET_HASH_NET=y +CONFIG_IP_SET_HASH_NETPORT=y +CONFIG_IP_SET_HASH_NETIFACE=y +CONFIG_IP_SET_LIST_SET=y +CONFIG_IP_VS=y +CONFIG_IP_VS_IPV6=y +CONFIG_IP_VS_PROTO_TCP=y +CONFIG_IP_VS_PROTO_UDP=y +CONFIG_IP_VS_PROTO_ESP=y +CONFIG_IP_VS_PROTO_AH=y +CONFIG_IP_VS_PROTO_SCTP=y +CONFIG_IP_VS_RR=y +CONFIG_IP_VS_WRR=y +CONFIG_IP_VS_LC=y +CONFIG_IP_VS_WLC=y +CONFIG_IP_VS_FO=y +CONFIG_IP_VS_OVF=y +CONFIG_IP_VS_LBLC=y +CONFIG_IP_VS_LBLCR=y +CONFIG_IP_VS_DH=y +CONFIG_IP_VS_SH=y +CONFIG_IP_VS_MH=y +CONFIG_IP_VS_SED=y +CONFIG_IP_VS_NQ=y +CONFIG_IP_VS_FTP=y +CONFIG_NFT_DUP_IPV4=y +CONFIG_NF_TABLES_ARP=y +CONFIG_NF_LOG_ARP=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_RPFILTER=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_IP_NF_TARGET_SYNPROXY=y +CONFIG_IP_NF_NAT=y +CONFIG_IP_NF_TARGET_MASQUERADE=y +CONFIG_IP_NF_TARGET_NETMAP=y +CONFIG_IP_NF_TARGET_REDIRECT=y +CONFIG_IP_NF_MANGLE=y +CONFIG_IP_NF_TARGET_ECN=y +CONFIG_IP_NF_TARGET_TTL=y +CONFIG_IP_NF_RAW=y +CONFIG_IP_NF_SECURITY=y +CONFIG_IP_NF_ARPTABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARP_MANGLE=y +CONFIG_NFT_DUP_IPV6=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_MATCH_AH=y +CONFIG_IP6_NF_MATCH_EUI64=y +CONFIG_IP6_NF_MATCH_FRAG=y +CONFIG_IP6_NF_MATCH_OPTS=y +CONFIG_IP6_NF_MATCH_HL=y +CONFIG_IP6_NF_MATCH_IPV6HEADER=y +CONFIG_IP6_NF_MATCH_MH=y +CONFIG_IP6_NF_MATCH_RPFILTER=y +CONFIG_IP6_NF_MATCH_RT=y +CONFIG_IP6_NF_TARGET_HL=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_TARGET_REJECT=y +CONFIG_IP6_NF_TARGET_SYNPROXY=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_IP6_NF_RAW=y +CONFIG_IP6_NF_SECURITY=y +CONFIG_IP6_NF_NAT=y +CONFIG_IP6_NF_TARGET_MASQUERADE=y +CONFIG_IP6_NF_TARGET_NPT=y +CONFIG_NF_TABLES_BRIDGE=y +CONFIG_NFT_BRIDGE_REJECT=y +CONFIG_BRIDGE_NF_EBTABLES=y +CONFIG_BRIDGE_EBT_BROUTE=y +CONFIG_BRIDGE_EBT_T_FILTER=y +CONFIG_BRIDGE_EBT_T_NAT=y +CONFIG_BRIDGE_EBT_802_3=y +CONFIG_BRIDGE_EBT_AMONG=y +CONFIG_BRIDGE_EBT_ARP=y +CONFIG_BRIDGE_EBT_IP=y +CONFIG_BRIDGE_EBT_IP6=y +CONFIG_BRIDGE_EBT_LIMIT=y +CONFIG_BRIDGE_EBT_MARK=y +CONFIG_BRIDGE_EBT_PKTTYPE=y +CONFIG_BRIDGE_EBT_STP=y +CONFIG_BRIDGE_EBT_VLAN=y +CONFIG_BRIDGE_EBT_ARPREPLY=y +CONFIG_BRIDGE_EBT_DNAT=y +CONFIG_BRIDGE_EBT_MARK_T=y +CONFIG_BRIDGE_EBT_REDIRECT=y +CONFIG_BRIDGE_EBT_SNAT=y +CONFIG_BRIDGE_EBT_LOG=y +CONFIG_BRIDGE_EBT_NFLOG=y + +# --- Block devices --- +CONFIG_BLK_DEV=y +CONFIG_BLK_DEV_INTEGRITY=y +CONFIG_BLK_DEV_THROTTLING=y +CONFIG_BLK_CGROUP_IOLATENCY=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_LDM_PARTITION=y +CONFIG_CMDLINE_PARTITION=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_DRBD=m +CONFIG_BLK_DEV_NBD=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_SIZE=65536 +CONFIG_ATA_OVER_ETH=m +CONFIG_BLK_DEV_NVME=y + +# --- SCSI --- +CONFIG_SCSI=y +# CONFIG_SCSI_PROC_FS is not set +CONFIG_BLK_DEV_SD=y +CONFIG_BLK_DEV_SR=y +CONFIG_CHR_DEV_SG=y +CONFIG_ISCSI_TCP=m +CONFIG_SCSI_HPSA=y +CONFIG_MEGARAID_SAS=y +CONFIG_SCSI_MPT3SAS=y +CONFIG_SCSI_MPI3MR=m +CONFIG_SCSI_SMARTPQI=m +CONFIG_VMWARE_PVSCSI=y +CONFIG_XEN_SCSI_FRONTEND=y +CONFIG_SCSI_VIRTIO=y + +# --- ATA / SATA --- +CONFIG_ATA=y +# CONFIG_ATA_VERBOSE_ERROR is not set +# CONFIG_SATA_PMP is not set +CONFIG_SATA_AHCI=y +CONFIG_ATA_PIIX=y +CONFIG_SATA_MV=y +CONFIG_SATA_NV=y +CONFIG_SATA_PROMISE=y +CONFIG_SATA_SIL=y +CONFIG_SATA_SIS=y +CONFIG_SATA_SVW=y +CONFIG_SATA_ULI=y +CONFIG_SATA_VIA=y +CONFIG_SATA_VITESSE=y +CONFIG_ATA_GENERIC=y + +# --- Device mapper / MD --- +CONFIG_MD=y +CONFIG_BLK_DEV_MD=y +CONFIG_MD_LINEAR=y +CONFIG_MD_RAID0=y +CONFIG_MD_RAID1=y +CONFIG_MD_RAID10=y +CONFIG_MD_RAID456=y +CONFIG_MD_MULTIPATH=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_CRYPT=y +CONFIG_DM_SNAPSHOT=y +CONFIG_DM_THIN_PROVISIONING=y +CONFIG_DM_MULTIPATH=m +CONFIG_DM_MULTIPATH_QL=m +CONFIG_DM_MULTIPATH_ST=m + +# --- Fusion --- +CONFIG_FUSION=y +CONFIG_FUSION_SPI=y + +# --- Network drivers (built-in for reliable boot) --- +CONFIG_NETDEVICES=y +# CONFIG_NET_VENDOR_3COM is not set +# CONFIG_NET_VENDOR_ADAPTEC is not set +# CONFIG_NET_VENDOR_AGERE is not set +# CONFIG_NET_VENDOR_ALACRITECH is not set +# CONFIG_NET_VENDOR_ALTEON is not set +# CONFIG_NET_VENDOR_AMD is not set +# CONFIG_NET_VENDOR_AQUANTIA is not set +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_VENDOR_ATHEROS is not set +# CONFIG_NET_VENDOR_CADENCE is not set +# CONFIG_NET_VENDOR_CAVIUM is not set +# CONFIG_NET_VENDOR_CHELSIO is not set +# CONFIG_NET_VENDOR_CORTINA is not set +# CONFIG_NET_VENDOR_DEC is not set +# CONFIG_NET_VENDOR_DLINK is not set +# CONFIG_NET_VENDOR_EMULEX is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_HUAWEI is not set +# CONFIG_NET_VENDOR_I825XX is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROCHIP is not set +# CONFIG_NET_VENDOR_MYRI is not set +# CONFIG_NET_VENDOR_NI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NETERION is not set +# CONFIG_NET_VENDOR_NVIDIA is not set +# CONFIG_NET_VENDOR_OKI is not set +# CONFIG_NET_VENDOR_PACKET_ENGINES is not set +# CONFIG_NET_VENDOR_QLOGIC is not set +# CONFIG_NET_VENDOR_BROCADE is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_RDC is not set +# CONFIG_NET_VENDOR_RENESAS is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SILAN is not set +# CONFIG_NET_VENDOR_SIS is not set +# CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_SOCIONEXT is not set +# CONFIG_NET_VENDOR_STMICRO is not set +# CONFIG_NET_VENDOR_SUN is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set +# CONFIG_NET_VENDOR_TEHUTI is not set +# CONFIG_NET_VENDOR_TI is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WIZNET is not set +CONFIG_E1000=y +CONFIG_E1000E=y +CONFIG_IGB=y +CONFIG_IGBVF=y +CONFIG_IXGBE=y +CONFIG_IXGBEVF=y +CONFIG_I40E=y +CONFIG_I40EVF=y +CONFIG_ICE=y +CONFIG_IGC=y +CONFIG_MLX4_EN=y +CONFIG_MLX5_CORE=y +CONFIG_MLX5_CORE_EN=y +CONFIG_BNXT=y +CONFIG_CNIC=m +CONFIG_TIGON3=y +CONFIG_BNX2X=y +CONFIG_R8169=y +CONFIG_8139CP=y +CONFIG_8139TOO=y +CONFIG_VMXNET3=y +CONFIG_HYPERV_NET=y +CONFIG_ENA_ETHERNET=m +CONFIG_GVE=m +CONFIG_NFP=m +CONFIG_IONIC=y +CONFIG_VIRTIO_NET=y + +# --- USB network --- +CONFIG_USB_CATC=m +CONFIG_USB_KAWETH=m +CONFIG_USB_PEGASUS=m +CONFIG_USB_RTL8150=y +CONFIG_USB_RTL8152=y +CONFIG_USB_LAN78XX=m +CONFIG_USB_USBNET=y +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_AX88179_178A=m +CONFIG_USB_NET_CDC_EEM=m +CONFIG_USB_NET_CDC_NCM=m +CONFIG_USB_NET_NET1080=m +CONFIG_USB_NET_CDC_SUBSET=m +CONFIG_USB_NET_ZAURUS=m + +# --- WAN --- +CONFIG_WAN=y +CONFIG_HDLC=m +CONFIG_HDLC_CISCO=m + +# --- PPP --- +CONFIG_PPP=m +CONFIG_PPP_BSDCOMP=m +CONFIG_PPP_DEFLATE=m +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=m +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOE=m +CONFIG_PPTP=m +CONFIG_PPPOL2TP=m +CONFIG_PPP_ASYNC=m +CONFIG_PPP_SYNC_TTY=m + +# --- USB --- +CONFIG_USB_SUPPORT=y +CONFIG_USB=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_OHCI_HCD=y +CONFIG_USB_UHCI_HCD=y +CONFIG_USB_XHCI_PCI=y +CONFIG_USB_UAS=y +CONFIG_USB_STORAGE=y + +# --- USB serial (BMC/console access) --- +CONFIG_USB_SERIAL=y +CONFIG_USB_SERIAL_CONSOLE=y +CONFIG_USB_SERIAL_GENERIC=y +CONFIG_USB_SERIAL_CH341=y +CONFIG_USB_SERIAL_CP210X=y +CONFIG_USB_SERIAL_FTDI_SIO=y +CONFIG_USB_SERIAL_PL2303=y +CONFIG_USB_SERIAL_TI=y +CONFIG_USB_SERIAL_OPTION=y + +# --- Input --- +CONFIG_INPUT_FF_MEMLESS=y +CONFIG_INPUT_SPARSEKMAP=y +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_MOUSEDEV_PSAUX=y +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_MISC=y +CONFIG_INPUT_PCSPKR=y +CONFIG_INPUT_ATLAS_BTNS=y +CONFIG_INPUT_UINPUT=y +CONFIG_SERIO_PCIPS2=y +CONFIG_SERIO_RAW=y +# CONFIG_LEGACY_PTYS is not set +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TOUCHSCREEN is not set + +# --- MMC --- +CONFIG_MMC=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PCI=y +# CONFIG_MMC_RICOH_MMC is not set +CONFIG_MMC_SDHCI_ACPI=y +CONFIG_MMC_SDHCI_PLTFM=y + +# --- Filesystems --- +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_XFS_FS=y +CONFIG_XFS_QUOTA=y +CONFIG_XFS_POSIX_ACL=y +CONFIG_BTRFS_FS=m +CONFIG_BTRFS_FS_POSIX_ACL=y +CONFIG_FS_DAX=y +CONFIG_FS_ENCRYPTION=y +CONFIG_FANOTIFY=y +CONFIG_QUOTA=y +CONFIG_QUOTA_NETLINK_INTERFACE=y +CONFIG_VFAT_FS=y +CONFIG_MSDOS_FS=y +CONFIG_FAT_DEFAULT_IOCHARSET="utf8" +CONFIG_NTFS_FS=m +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_TMPFS_XATTR=y +CONFIG_PROC_FS=y +CONFIG_PROC_KCORE=y +CONFIG_SYSFS=y +CONFIG_EFIVAR_FS=y +CONFIG_OVERLAY_FS=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_XATTR=y +CONFIG_SQUASHFS_LZ4=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_FUSE_FS=y +CONFIG_CUSE=y +CONFIG_ISO9660_FS=y +CONFIG_JOLIET=y +CONFIG_ZISOFS=y +CONFIG_UDF_FS=y +CONFIG_FSCACHE=y +CONFIG_FSCACHE_STATS=y +CONFIG_CACHEFILES=y +CONFIG_NFS_FS=m +# CONFIG_NFS_V2 is not set +CONFIG_NFS_V4=m +CONFIG_NFS_V4_1=y +CONFIG_NFS_V4_2=y +CONFIG_NFS_FSCACHE=y +CONFIG_NFSD=m +CONFIG_NFSD_V4=y +CONFIG_CEPH_FS=m +CONFIG_CEPH_FSCACHE=y +CONFIG_CEPH_FS_POSIX_ACL=y +CONFIG_CIFS=y +# CONFIG_CIFS_ALLOW_INSECURE_LEGACY is not set +CONFIG_CIFS_XATTR=y +CONFIG_CIFS_DFS_UPCALL=y +CONFIG_CIFS_FSCACHE=y +CONFIG_9P_FS=y +CONFIG_9P_FSCACHE=y +CONFIG_9P_FS_POSIX_ACL=y +CONFIG_9P_FS_SECURITY=y + +# --- NLS --- +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_UTF8=y + +# --- Virtio (built-in for reliable boot) --- +CONFIG_VIRTIO=y +CONFIG_VIRTIO_PCI=y +CONFIG_VIRTIO_BLK=y +CONFIG_VIRTIO_CONSOLE=y +CONFIG_VIRTIO_BALLOON=y +CONFIG_VIRTIO_INPUT=y +CONFIG_VIRTIO_MMIO=y +CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y +CONFIG_HW_RANDOM_VIRTIO=y +CONFIG_VHOST_NET=m +CONFIG_VHOST_VSOCK=m + +# --- Console / Serial --- +CONFIG_VT=y +CONFIG_VT_CONSOLE=y +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_NR_UARTS=32 +# CONFIG_SERIAL_8250_MID is not set +CONFIG_SERIAL_DEV_BUS=y +CONFIG_TTY_PRINTK=y +CONFIG_HW_CONSOLE=y +CONFIG_VGA_CONSOLE=y +CONFIG_DUMMY_CONSOLE=y + +# --- IPMI --- +CONFIG_IPMI_HANDLER=y +CONFIG_IPMI_DMI_DECODE=y +CONFIG_IPMI_DEVICE_INTERFACE=y +CONFIG_IPMI_SI=y +CONFIG_IPMI_POWEROFF=y + +# --- Hardware RNG --- +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_TIMERIOMEM=y + +# --- I2C --- +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_MUX=y + +# --- TPM --- +CONFIG_TCG_TIS_I2C_ATMEL=m +CONFIG_TCG_TIS_I2C_INFINEON=m +CONFIG_TCG_TIS_I2C_NUVOTON=m +CONFIG_TCG_NSC=m +CONFIG_TCG_ATMEL=m +CONFIG_TCG_INFINEON=m +CONFIG_TCG_XEN=m +CONFIG_TCG_VTPM_PROXY=m +CONFIG_TCG_TIS_ST33ZP24_I2C=m + +# --- Platform / MFD --- +CONFIG_NVRAM=y +CONFIG_HPET=y +CONFIG_HANGCHECK_TIMER=y +CONFIG_LPC_ICH=y +CONFIG_LPC_SCH=y +CONFIG_MFD_INTEL_LPSS_ACPI=y +CONFIG_MFD_INTEL_LPSS_PCI=y +CONFIG_MFD_SM501=y +CONFIG_MFD_VX855=y +# CONFIG_THERMAL_HWMON is not set + +# --- RTC --- +CONFIG_RTC_CLASS=y + +# --- DMA --- +CONFIG_DMADEVICES=y + +# --- Security --- +CONFIG_SECCOMP=y +CONFIG_SECCOMP_FILTER=y +CONFIG_SECURITY=y +CONFIG_SECURITY_NETWORK=y +CONFIG_SECURITY_NETWORK_XFRM=y +CONFIG_SECURITY_PATH=y +CONFIG_SECURITY_DMESG_RESTRICT=y +CONFIG_KEYS=y +CONFIG_PERSISTENT_KEYRINGS=y +CONFIG_TRUSTED_KEYS=y +CONFIG_KEY_DH_OPERATIONS=y +CONFIG_HARDENED_USERCOPY=y +CONFIG_FORTIFY_SOURCE=y +CONFIG_STATIC_USERMODEHELPER=y +CONFIG_SECURITY_YAMA=y +CONFIG_INTEGRITY_SIGNATURE=y +CONFIG_INTEGRITY_ASYMMETRIC_KEYS=y +CONFIG_IMA=y +CONFIG_IMA_DEFAULT_HASH_SHA256=y +CONFIG_IMA_READ_POLICY=y +CONFIG_IMA_APPRAISE=y +CONFIG_EVM=y +CONFIG_LSM="yama,loadpin,safesetid,integrity" + +# --- Crypto --- +CONFIG_CRYPTO=y +CONFIG_CRYPTO_USER=y +CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_SHA512=y +CONFIG_CRYPTO_XTS=y +CONFIG_CRYPTO_ANUBIS=y +CONFIG_CRYPTO_BLOWFISH=y +CONFIG_CRYPTO_CAMELLIA=y +CONFIG_CRYPTO_DES=y +CONFIG_CRYPTO_FCRYPT=y +CONFIG_CRYPTO_KHAZAD=y +CONFIG_CRYPTO_SEED=y +CONFIG_CRYPTO_TEA=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRYPTO_ARC4=y +CONFIG_CRYPTO_KEYWRAP=y +CONFIG_CRYPTO_LRW=y +CONFIG_CRYPTO_PCBC=y +CONFIG_CRYPTO_CHACHA20POLY1305=y +CONFIG_CRYPTO_SEQIV=y +CONFIG_CRYPTO_ECHAINIV=y +CONFIG_CRYPTO_MICHAEL_MIC=y +CONFIG_CRYPTO_RMD160=y +CONFIG_CRYPTO_VMAC=y +CONFIG_CRYPTO_WP512=y +CONFIG_CRYPTO_XCBC=y +CONFIG_CRYPTO_CRC32=y +CONFIG_CRYPTO_LZO=y +CONFIG_CRYPTO_842=y +CONFIG_CRYPTO_LZ4=y +CONFIG_CRYPTO_LZ4HC=y +CONFIG_CRYPTO_ANSI_CPRNG=y +CONFIG_CRYPTO_USER_API_HASH=y +CONFIG_CRYPTO_USER_API_SKCIPHER=y +CONFIG_CRYPTO_USER_API_RNG=y +CONFIG_CRYPTO_USER_API_AEAD=y + +# --- x86_64 hardware crypto acceleration --- +CONFIG_CRYPTO_AES_NI_INTEL=y +CONFIG_CRYPTO_BLOWFISH_X86_64=y +CONFIG_CRYPTO_CAMELLIA_AESNI_AVX2_X86_64=y +CONFIG_CRYPTO_CAST5_AVX_X86_64=y +CONFIG_CRYPTO_CAST6_AVX_X86_64=y +CONFIG_CRYPTO_DES3_EDE_X86_64=y +CONFIG_CRYPTO_SERPENT_SSE2_X86_64=y +CONFIG_CRYPTO_SERPENT_AVX2_X86_64=y +CONFIG_CRYPTO_TWOFISH_AVX_X86_64=y +CONFIG_CRYPTO_CHACHA20_X86_64=y +CONFIG_CRYPTO_POLY1305_X86_64=y +CONFIG_CRYPTO_SHA1_SSSE3=y +CONFIG_CRYPTO_SHA256_SSSE3=y +CONFIG_CRYPTO_SHA512_SSSE3=y +CONFIG_CRYPTO_GHASH_CLMUL_NI_INTEL=y +CONFIG_CRYPTO_CRC32C_INTEL=y +CONFIG_CRYPTO_CRC32_PCLMUL=y +CONFIG_CRYPTO_DEV_PADLOCK=y +CONFIG_CRYPTO_DEV_PADLOCK_AES=y +CONFIG_CRYPTO_DEV_PADLOCK_SHA=y +CONFIG_CRYPTO_DEV_VIRTIO=m +CONFIG_PKCS7_MESSAGE_PARSER=y + +# --- BPF --- +CONFIG_BPF=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_BPF_JIT_ALWAYS_ON=y + +# --- PCI --- +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_HOTPLUG_PCI_PCIE=y +CONFIG_PCI_STUB=y +CONFIG_PCI_IOV=y +# CONFIG_VGA_ARB is not set +CONFIG_HOTPLUG_PCI=y +CONFIG_HOTPLUG_PCI_ACPI=y +CONFIG_HOTPLUG_PCI_SHPC=y +CONFIG_PCI_HYPERV_INTERFACE=m + +# --- IOMMU --- +CONFIG_AMD_IOMMU=y +CONFIG_INTEL_IOMMU=y +CONFIG_IRQ_REMAP=y + +# --- Xen --- +# CONFIG_XEN_BACKEND is not set +CONFIG_XEN_GNTDEV=y +CONFIG_XEN_GRANT_DEV_ALLOC=y +CONFIG_XEN_PVCALLS_FRONTEND=y +CONFIG_XEN_ACPI_PROCESSOR=y +# CONFIG_XEN_SYMS is not set + +# --- Intel platform --- +CONFIG_INTEL_IPS=y + +# --- DEVFREQ --- +CONFIG_PM_DEVFREQ=y +CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y + +# --- Reset --- +CONFIG_RESET_CONTROLLER=y + +# --- DAX --- +CONFIG_DEV_DAX=y +CONFIG_DEV_DAX_PMEM=m + +# --- Misc --- +CONFIG_FW_LOADER=y +CONFIG_PRINTK=y +CONFIG_PRINTK_TIME=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_EARLY_PRINTK=y +CONFIG_PANIC_TIMEOUT=10 +CONFIG_PANIC_ON_OOPS=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_UEVENT_HELPER=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_CONNECTOR=y +CONFIG_DMI_SYSFS=y +CONFIG_SYSFB_SIMPLEFB=y +CONFIG_RESET_ATTACK_MITIGATION=y +# CONFIG_PNP_DEBUG_MESSAGES is not set +CONFIG_HYPERV=y +CONFIG_HYPERV_UTILS=y +CONFIG_HYPERV_BALLOON=y + +# --- Module compression --- +CONFIG_MODULE_COMPRESS_ZSTD=y +CONFIG_MODULE_DECOMPRESS=y + +# --- EFI boot support --- +CONFIG_EFI=y +CONFIG_EFI_STUB=y +CONFIG_EFI_MIXED=y + +# --- Framebuffer/Console (required for EFI boot video output) --- +CONFIG_DRM=y +CONFIG_DRM_I915=y +CONFIG_DRM_SIMPLEDRM=y +CONFIG_FB=y +CONFIG_FB_EFI=y +CONFIG_FRAMEBUFFER_CONSOLE=y + +# --- Debugging / Diagnostics --- +CONFIG_FRAME_WARN=1024 +CONFIG_BUG_ON_DATA_CORRUPTION=y +CONFIG_HARDLOCKUP_DETECTOR=y +CONFIG_WQ_WATCHDOG=y +CONFIG_DEBUG_NOTIFIERS=y +CONFIG_RCU_CPU_STALL_TIMEOUT=60 +# CONFIG_RCU_TRACE is not set +CONFIG_IO_STRICT_DEVMEM=y + +# --- Disable unnecessary subsystems --- +# CONFIG_SOUND is not set +# CONFIG_WIRELESS is not set +# CONFIG_WLAN is not set +# CONFIG_BLUETOOTH is not set +# CONFIG_NFC is not set +# CONFIG_INFINIBAND is not set +# CONFIG_MEDIA_SUPPORT is not set diff --git a/kernel.configs/6.18.y.arm64 b/kernel.configs/6.18.y.arm64 new file mode 100644 index 0000000..795c91e --- /dev/null +++ b/kernel.configs/6.18.y.arm64 @@ -0,0 +1,515 @@ +# CaptainOS kernel defconfig for arm64 (aarch64) +# Minimal config for bare-metal provisioning with container/network support. +# Based on 6.12.y config with targeted additions for CaptainOS use case, adapted for kernel 6.18. +# +# Generate a full .config from this: +# cp 6.18.y.arm64 .config && make ARCH=arm64 olddefconfig + +# --- Architecture --- +CONFIG_ARM64=y +CONFIG_SMP=y +CONFIG_NR_CPUS=128 +CONFIG_NUMA=y +CONFIG_ARM64_VA_BITS_48=y +CONFIG_SCHED_MC=y +CONFIG_SCHED_SMT=y +CONFIG_HZ_1000=y +CONFIG_XEN=y +CONFIG_COMPAT=y +CONFIG_RANDOMIZE_BASE=y +CONFIG_ARM64_ACPI_PARKING_PROTOCOL=y + +# --- General --- +CONFIG_LOCALVERSION="-captainos" +CONFIG_DEFAULT_HOSTNAME="captainos" +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_AUDIT=y +CONFIG_NO_HZ_IDLE=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_PREEMPT_VOLUNTARY=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_KEXEC=y +CONFIG_KEXEC_FILE=y +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_CGROUPS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_CGROUP_MISC=y +CONFIG_CGROUP_HUGETLB=y +CONFIG_CGROUP_RDMA=y +CONFIG_CPUSETS=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_BLK_CGROUP_IOLATENCY=y +CONFIG_BLK_DEV_THROTTLING=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_RT_GROUP_SCHED=y +CONFIG_CGROUP_SCHED=y +CONFIG_CGROUP_NET_PRIO=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_PID_NS=y +CONFIG_NET_NS=y +CONFIG_UTS_NS=y +CONFIG_IPC_NS=y +CONFIG_CHECKPOINT_RESTORE=y +# Memory management +CONFIG_TRANSPARENT_HUGEPAGE=y +CONFIG_KSM=y +CONFIG_MEMORY_HOTPLUG=y +CONFIG_MEMORY_HOTREMOVE=y +CONFIG_HUGETLBFS=y + +# --- Init / Initramfs --- +CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="" + +# --- Networking --- +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_NET_IPIP=m +CONFIG_NET_IPGRE_DEMUX=m +CONFIG_NET_IPGRE=m +CONFIG_SYN_COOKIES=y +CONFIG_IPV6=y +CONFIG_BRIDGE=y +CONFIG_VLAN_8021Q=m +CONFIG_BONDING=m +CONFIG_TUN=y +CONFIG_VETH=y +CONFIG_MACVLAN=y +CONFIG_MACVTAP=y +CONFIG_IPVLAN=y +CONFIG_TAP=y +CONFIG_DUMMY=m +CONFIG_VXLAN=y +CONFIG_GENEVE=m +CONFIG_NLMON=y +# Traffic control / QoS (needed by CNI plugins) +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=m +CONFIG_NET_SCH_PRIO=m +CONFIG_NET_SCH_SFQ=m +CONFIG_NET_SCH_TBF=m +CONFIG_NET_SCH_NETEM=m +CONFIG_NET_SCH_INGRESS=m +CONFIG_NET_CLS_BASIC=y +CONFIG_NET_CLS_FW=y +CONFIG_NET_CLS_U32=y +CONFIG_NET_CLS_CGROUP=y +CONFIG_NET_CLS_BPF=y +CONFIG_NET_CLS_MATCHALL=y +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_POLICE=y +CONFIG_NET_ACT_GACT=y +CONFIG_NET_ACT_MIRRED=y +CONFIG_NET_ACT_BPF=y +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_U32=y + +# --- Netfilter --- +CONFIG_NETFILTER=y +CONFIG_NETFILTER_ADVANCED=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_NAT=y +CONFIG_NF_TABLES=y +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_IPV4=y +CONFIG_NF_TABLES_IPV6=y +CONFIG_NFT_NAT=y +CONFIG_NFT_MASQ=y +CONFIG_NFT_CT=y +CONFIG_NFT_COMPAT=y +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_NAT=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_NAT=y +CONFIG_BRIDGE_NETFILTER=y +CONFIG_BRIDGE_NF_EBTABLES=y +CONFIG_BRIDGE_EBT_BROUTE=y +CONFIG_BRIDGE_EBT_T_FILTER=y +CONFIG_BRIDGE_EBT_T_NAT=y +CONFIG_BRIDGE_EBT_ARP=y +CONFIG_BRIDGE_EBT_IP=y +CONFIG_BRIDGE_EBT_IP6=y +CONFIG_BRIDGE_EBT_MARK=y +CONFIG_BRIDGE_EBT_MARK_T=y +CONFIG_BRIDGE_EBT_VLAN=y +# IPVS (container load balancing / kube-proxy) +CONFIG_IP_VS=y +CONFIG_IP_VS_IPV6=y +CONFIG_IP_VS_PROTO_TCP=y +CONFIG_IP_VS_PROTO_UDP=y +CONFIG_IP_VS_RR=y +CONFIG_IP_VS_WRR=y +CONFIG_IP_VS_SH=y +CONFIG_IP_VS_NFCT=y +# IP sets (used by iptables/nftables for efficient matching) +CONFIG_IP_SET=y +CONFIG_IP_SET_HASH_IP=y +CONFIG_IP_SET_HASH_IPPORT=y +CONFIG_IP_SET_HASH_IPPORTNET=y +CONFIG_IP_SET_HASH_NET=y +CONFIG_IP_SET_HASH_NETPORT=y +CONFIG_IP_SET_BITMAP_IP=y +CONFIG_IP_SET_BITMAP_PORT=y +CONFIG_IP_SET_LIST_SET=y +CONFIG_NETFILTER_XT_SET=y + +# --- Block devices --- +CONFIG_BLK_DEV=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_NBD=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_SIZE=65536 +CONFIG_BLK_DEV_NVME=y +CONFIG_ATA=y +CONFIG_SATA_AHCI=y +CONFIG_SATA_AHCI_PLATFORM=y + +# --- SCSI --- +CONFIG_SCSI=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_MEGARAID_SAS=y +CONFIG_SCSI_MPT3SAS=y +CONFIG_SCSI_MPI3MR=m +CONFIG_SCSI_HPSA=y +CONFIG_SCSI_HISI_SAS=y +CONFIG_SCSI_HISI_SAS_PCI=y +CONFIG_XEN_SCSI_FRONTEND=y +CONFIG_SCSI_VIRTIO=y + +# --- Device mapper --- +CONFIG_MD=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_CRYPT=y +CONFIG_DM_THIN_PROVISIONING=y +CONFIG_DM_SNAPSHOT=y +CONFIG_DM_MULTIPATH=m +CONFIG_MD_RAID0=m +CONFIG_MD_RAID1=m + +# --- Network drivers (built-in for reliable boot) --- +CONFIG_NETDEVICES=y +CONFIG_VIRTIO_NET=y +CONFIG_E1000E=y +CONFIG_IGB=y +CONFIG_IXGBE=y +CONFIG_I40E=y +CONFIG_ICE=y +CONFIG_IGC=y +CONFIG_MLX4_EN=y +CONFIG_MLX5_CORE=y +CONFIG_MLX5_CORE_EN=y +CONFIG_BNXT=y +CONFIG_TIGON3=y +CONFIG_R8169=y +CONFIG_THUNDER_NIC_BGX=m +CONFIG_THUNDER_NIC_PF=m +CONFIG_THUNDER_NIC_VF=m +CONFIG_MACB=m +CONFIG_HNS3=m +CONFIG_NET_XGENE=y +CONFIG_MVNETA=y +CONFIG_MVPP2=y +CONFIG_STMMAC_ETH=m +CONFIG_ENA_ETHERNET=m +CONFIG_GVE=m +CONFIG_VMXNET3=y +CONFIG_AMD_XGBE=y +# Broadcom GENET (Raspberry Pi 4B built-in Ethernet) +CONFIG_BCMGENET=y +# PHY subsystem (required by GENET and other MAC drivers) +CONFIG_PHYLIB=y +CONFIG_MDIO_BUS=y +CONFIG_BROADCOM_PHY=y +CONFIG_BCM7XXX_PHY=y +CONFIG_MDIO_BCM_UNIMAC=y +CONFIG_MARVELL_PHY=y +CONFIG_MARVELL_10G_PHY=y +CONFIG_MICREL_PHY=y +CONFIG_ROCKCHIP_PHY=y +CONFIG_REALTEK_PHY=y +CONFIG_MESON_GXL_PHY=y +CONFIG_AMD_PHY=y + +# --- USB --- +CONFIG_USB_SUPPORT=y +CONFIG_USB=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_OHCI_HCD=y +CONFIG_USB_XHCI_PCI=y +CONFIG_USB_UAS=y +CONFIG_USB_STORAGE=y +CONFIG_USB_DWC3=y +CONFIG_USB_DWC3_HOST=y +CONFIG_USB_DWC2=y +CONFIG_USB_ISP1760=y + +# --- MMC/SD (Raspberry Pi, Rockchip, and other SBCs) --- +CONFIG_MMC=y +CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_PCI=y +CONFIG_MMC_SDHCI_ACPI=y +CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_OF_ARASAN=y +CONFIG_MMC_SDHCI_OF_DWCMSHC=y +CONFIG_MMC_SDHCI_CADENCE=y +CONFIG_MMC_SDHCI_TEGRA=y +CONFIG_MMC_SDHCI_MSM=y +CONFIG_MMC_SDHCI_XENON=y +CONFIG_MMC_SDHCI_F_SDH30=y +CONFIG_MMC_ARMMMCI=y +CONFIG_MMC_DW=y +CONFIG_MMC_DW_ROCKCHIP=y +CONFIG_MMC_DW_K3=y +CONFIG_MMC_MESON_GX=y +CONFIG_MMC_SUNXI=m +CONFIG_MMC_SPI=y +CONFIG_MMC_HSQ=y + +# --- Filesystems --- +CONFIG_EXT4_FS=y +CONFIG_XFS_FS=m +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_PROC_FS=y +CONFIG_SYSFS=y +CONFIG_OVERLAY_FS=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_XATTR=y +CONFIG_SQUASHFS_LZ4=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_NFS_FS=m +CONFIG_NFS_V4=m +CONFIG_NFS_V4_1=y +CONFIG_NFS_V4_2=y +CONFIG_FUSE_FS=y +CONFIG_CUSE=y +CONFIG_EFIVAR_FS=y +CONFIG_ISO9660_FS=y +CONFIG_JOLIET=y +CONFIG_UDF_FS=y +CONFIG_MSDOS_FS=y +CONFIG_FAT_DEFAULT_IOCHARSET="utf8" +CONFIG_NTFS_FS=m +CONFIG_TMPFS_XATTR=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_UTF8=y + +# --- Virtio (built-in for reliable boot) --- +CONFIG_VIRTIO=y +CONFIG_VIRTIO_PCI=y +CONFIG_VIRTIO_BLK=y +CONFIG_VIRTIO_CONSOLE=y +CONFIG_VIRTIO_BALLOON=y +CONFIG_VIRTIO_INPUT=y +CONFIG_VIRTIO_MMIO=y +CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y +CONFIG_HW_RANDOM_VIRTIO=y + +# --- Console / Serial --- +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_VT=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +CONFIG_DUMMY_CONSOLE=y + +# --- Security --- +CONFIG_SECCOMP=y +CONFIG_SECCOMP_FILTER=y +CONFIG_SECURITY=y +CONFIG_KEYS=y + +# --- Crypto --- +CONFIG_CRYPTO=y +CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_SHA512=y +CONFIG_CRYPTO_XTS=y +CONFIG_ARM64_CRYPTO=y +CONFIG_CRYPTO_AES_ARM64_CE=y +CONFIG_CRYPTO_SHA2_ARM64_CE=y + +# --- BPF --- +CONFIG_BPF=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_BPF_JIT_ALWAYS_ON=y +CONFIG_XDP_SOCKETS=y + +# --- ARM64 SoC/platform support --- +CONFIG_ARCH_BCM2835=y +CONFIG_ARCH_ROCKCHIP=y +CONFIG_ARCH_MESON=y +CONFIG_ARCH_SUNXI=y +CONFIG_ARCH_TEGRA=y +CONFIG_ARCH_MVEBU=y +CONFIG_ARCH_HISI=y +CONFIG_ARCH_QCOM=y +CONFIG_ARCH_EXYNOS=y +CONFIG_ARCH_VEXPRESS=y +CONFIG_ARCH_XGENE=y +CONFIG_ARCH_THUNDER=y +CONFIG_ARCH_THUNDER2=y +CONFIG_ARCH_SEATTLE=y +CONFIG_ARCH_SYNQUACER=y +CONFIG_ARCH_UNIPHIER=y +# ACPI / UEFI +CONFIG_ACPI=y +CONFIG_ACPI_DOCK=y +CONFIG_ACPI_IPMI=y +CONFIG_ACPI_APEI=y +CONFIG_ACPI_APEI_GHES=y +CONFIG_IPMI_HANDLER=m +CONFIG_IPMI_DMI_DECODE=y +CONFIG_IPMI_DEVICE_INTERFACE=m +CONFIG_IPMI_SI=m +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_HOTPLUG_PCI=y +CONFIG_PCI_HOST_GENERIC=y +CONFIG_PCIE_ROCKCHIP_HOST=y +CONFIG_PCIE_ARMADA_8K=y +CONFIG_PCIE_TEGRA194_HOST=y +# Broadcom STB PCIe controller (Raspberry Pi 4B — needed for VL805 USB 3.0) +CONFIG_PCIE_BRCMSTB=y +CONFIG_PCIE_ALTERA=y +CONFIG_PCI_AARDVARK=y +CONFIG_PCI_TEGRA=y +CONFIG_PCI_XGENE=y +CONFIG_PCI_HOST_THUNDER_PEM=y +CONFIG_PCI_HOST_THUNDER_ECAM=y +CONFIG_PCIE_QCOM=y +CONFIG_PCIE_AL=y +CONFIG_PCI_MESON=y +CONFIG_PCI_HISI=y +CONFIG_PCIE_KIRIN=y +CONFIG_PCIE_HISI_STB=y +CONFIG_PCI_IOV=y +CONFIG_PCI_PASID=y +CONFIG_HOTPLUG_PCI_ACPI=y +# IOMMU +CONFIG_ARM_SMMU=y +CONFIG_ARM_SMMU_V3=y +CONFIG_FW_LOADER=y +# Raspberry Pi firmware mailbox (required for GENET MAC address, clocks, etc.) +CONFIG_RASPBERRYPI_FIRMWARE=y +CONFIG_RASPBERRYPI_POWER=y +CONFIG_ARM_SCMI_PROTOCOL=y +CONFIG_ARM_SCPI_PROTOCOL=y +CONFIG_OF=y +CONFIG_USE_OF=y +# RTC +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_EFI=y +# DMA +CONFIG_DMADEVICES=y +CONFIG_PL330_DMA=y +CONFIG_PRINTK=y +CONFIG_PRINTK_TIME=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_PANIC_TIMEOUT=10 +CONFIG_PANIC_ON_OOPS=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_RD_GZIP=y +CONFIG_RD_XZ=y +CONFIG_RD_ZSTD=y +CONFIG_UEVENT_HELPER=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_CONNECTOR=y +CONFIG_DMI_SYSFS=y +CONFIG_EFI=y +CONFIG_EFI_STUB=y + +# --- Framebuffer/Console (required for EFI boot video output) --- +CONFIG_DRM=y +CONFIG_DRM_SIMPLEDRM=y +CONFIG_DRM_VIRTIO_GPU=y +CONFIG_FB=y +CONFIG_FB_EFI=y +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_SYSFB_SIMPLEFB=y +# Virtualization +CONFIG_VIRTUALIZATION=y +CONFIG_KVM=y +CONFIG_VFIO=y +CONFIG_VFIO_PCI=y +CONFIG_VHOST_NET=m +CONFIG_VHOST_VSOCK=m + +# --- Module compression --- +CONFIG_MODULE_COMPRESS_ZSTD=y +CONFIG_MODULE_DECOMPRESS=y + +# --- I2C (SBC board management, EEPROM, sensors) --- +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_MV64XXX=y +CONFIG_I2C_RK3X=y +CONFIG_I2C_TEGRA=y +CONFIG_I2C_DESIGNWARE_PLATFORM=y +# SPI +CONFIG_SPI=y +CONFIG_SPI_ROCKCHIP=y +CONFIG_SPI_PL022=y +CONFIG_SPI_ORION=y +# GPIO +CONFIG_GPIOLIB=y +# Hardware watchdog +CONFIG_WATCHDOG=y +CONFIG_SOFTLOCKUP_DETECTOR=y +CONFIG_WQ_WATCHDOG=y +# Hardware RNG +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_TIMERIOMEM=y +# TPM +CONFIG_TCG_TIS=y +CONFIG_TCG_FTPM_TEE=y +CONFIG_TEE=y +CONFIG_OPTEE=y + +# --- Disable unnecessary subsystems --- +# CONFIG_SOUND is not set +# CONFIG_WIRELESS is not set +# CONFIG_WLAN is not set +# CONFIG_BLUETOOTH is not set +# CONFIG_NFC is not set +# CONFIG_INFINIBAND is not set +# CONFIG_MEDIA_SUPPORT is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_JOYSTICK is not set +# CONFIG_INPUT_TOUCHSCREEN is not set From 1cf3f9c3b2345ce08dd55ab914ae65b7aac79406 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 13 Apr 2026 20:57:22 +0200 Subject: [PATCH 76/93] 6.18.y.arm64: defconfig Signed-off-by: Ricardo Pardini --- kernel.configs/6.18.y.arm64 | 602 ++++++++++++++---------------------- 1 file changed, 224 insertions(+), 378 deletions(-) diff --git a/kernel.configs/6.18.y.arm64 b/kernel.configs/6.18.y.arm64 index 795c91e..5f7cff8 100644 --- a/kernel.configs/6.18.y.arm64 +++ b/kernel.configs/6.18.y.arm64 @@ -1,25 +1,3 @@ -# CaptainOS kernel defconfig for arm64 (aarch64) -# Minimal config for bare-metal provisioning with container/network support. -# Based on 6.12.y config with targeted additions for CaptainOS use case, adapted for kernel 6.18. -# -# Generate a full .config from this: -# cp 6.18.y.arm64 .config && make ARCH=arm64 olddefconfig - -# --- Architecture --- -CONFIG_ARM64=y -CONFIG_SMP=y -CONFIG_NR_CPUS=128 -CONFIG_NUMA=y -CONFIG_ARM64_VA_BITS_48=y -CONFIG_SCHED_MC=y -CONFIG_SCHED_SMT=y -CONFIG_HZ_1000=y -CONFIG_XEN=y -CONFIG_COMPAT=y -CONFIG_RANDOMIZE_BASE=y -CONFIG_ARM64_ACPI_PARKING_PROTOCOL=y - -# --- General --- CONFIG_LOCALVERSION="-captainos" CONFIG_DEFAULT_HOSTNAME="captainos" CONFIG_SYSVIPC=y @@ -27,52 +5,74 @@ CONFIG_POSIX_MQUEUE=y CONFIG_AUDIT=y CONFIG_NO_HZ_IDLE=y CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_BPF_JIT_ALWAYS_ON=y CONFIG_PREEMPT_VOLUNTARY=y CONFIG_BSD_PROCESS_ACCT=y -CONFIG_KEXEC=y -CONFIG_KEXEC_FILE=y -CONFIG_CC_OPTIMIZE_FOR_SIZE=y CONFIG_CGROUPS=y -CONFIG_CGROUP_FREEZER=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_RT_GROUP_SCHED=y CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_RDMA=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_HUGETLB=y +CONFIG_CPUSETS=y CONFIG_CGROUP_DEVICE=y CONFIG_CGROUP_CPUACCT=y -CONFIG_CGROUP_PERF=y CONFIG_CGROUP_BPF=y CONFIG_CGROUP_MISC=y -CONFIG_CGROUP_HUGETLB=y -CONFIG_CGROUP_RDMA=y -CONFIG_CPUSETS=y -CONFIG_MEMCG=y -CONFIG_BLK_CGROUP=y -CONFIG_BLK_CGROUP_IOLATENCY=y -CONFIG_BLK_DEV_THROTTLING=y -CONFIG_CFS_BANDWIDTH=y -CONFIG_RT_GROUP_SCHED=y -CONFIG_CGROUP_SCHED=y -CONFIG_CGROUP_NET_PRIO=y -CONFIG_NAMESPACES=y CONFIG_USER_NS=y -CONFIG_PID_NS=y -CONFIG_NET_NS=y -CONFIG_UTS_NS=y -CONFIG_IPC_NS=y CONFIG_CHECKPOINT_RESTORE=y -# Memory management -CONFIG_TRANSPARENT_HUGEPAGE=y -CONFIG_KSM=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_KEXEC=y +CONFIG_KEXEC_FILE=y +CONFIG_ARCH_SUNXI=y +CONFIG_ARCH_EXYNOS=y +CONFIG_ARCH_HISI=y +CONFIG_ARCH_MESON=y +CONFIG_ARCH_MVEBU=y +CONFIG_ARCH_QCOM=y +CONFIG_ARCH_ROCKCHIP=y +CONFIG_ARCH_SEATTLE=y +CONFIG_ARCH_SYNQUACER=y +CONFIG_ARCH_TEGRA=y +CONFIG_ARCH_THUNDER=y +CONFIG_ARCH_THUNDER2=y +CONFIG_ARCH_UNIPHIER=y +CONFIG_ARCH_VEXPRESS=y +CONFIG_ARCH_XGENE=y +CONFIG_ARM64_VA_BITS_48=y +CONFIG_NR_CPUS=128 +CONFIG_NUMA=y +CONFIG_HZ_1000=y +CONFIG_XEN=y +CONFIG_COMPAT=y +CONFIG_RANDOMIZE_BASE=y +CONFIG_ARM64_ACPI_PARKING_PROTOCOL=y +CONFIG_ACPI=y +CONFIG_ACPI_DOCK=y +CONFIG_ACPI_IPMI=m +CONFIG_ACPI_APEI=y +CONFIG_ACPI_APEI_GHES=y +CONFIG_VIRTUALIZATION=y +CONFIG_KVM=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_BLK_DEV_THROTTLING=y +CONFIG_BLK_CGROUP_IOLATENCY=y CONFIG_MEMORY_HOTPLUG=y CONFIG_MEMORY_HOTREMOVE=y -CONFIG_HUGETLBFS=y - -# --- Init / Initramfs --- -CONFIG_BLK_DEV_INITRD=y -CONFIG_INITRAMFS_SOURCE="" - -# --- Networking --- +CONFIG_KSM=y +CONFIG_TRANSPARENT_HUGEPAGE=y CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y +CONFIG_XDP_SOCKETS=y CONFIG_INET=y CONFIG_IP_MULTICAST=y CONFIG_IP_ADVANCED_ROUTER=y @@ -85,21 +85,49 @@ CONFIG_NET_IPIP=m CONFIG_NET_IPGRE_DEMUX=m CONFIG_NET_IPGRE=m CONFIG_SYN_COOKIES=y -CONFIG_IPV6=y +CONFIG_NETFILTER=y +CONFIG_BRIDGE_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_TABLES=y +CONFIG_NF_TABLES_INET=y +CONFIG_NFT_CT=y +CONFIG_NFT_MASQ=y +CONFIG_NFT_NAT=y +CONFIG_NFT_COMPAT=y +CONFIG_NETFILTER_XT_SET=y +CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y +CONFIG_NETFILTER_XT_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_IP_SET=y +CONFIG_IP_SET_BITMAP_IP=y +CONFIG_IP_SET_BITMAP_PORT=y +CONFIG_IP_SET_HASH_IP=y +CONFIG_IP_SET_HASH_IPPORT=y +CONFIG_IP_SET_HASH_IPPORTNET=y +CONFIG_IP_SET_HASH_NET=y +CONFIG_IP_SET_HASH_NETPORT=y +CONFIG_IP_SET_LIST_SET=y +CONFIG_IP_VS=y +CONFIG_IP_VS_IPV6=y +CONFIG_IP_VS_PROTO_TCP=y +CONFIG_IP_VS_PROTO_UDP=y +CONFIG_IP_VS_RR=y +CONFIG_IP_VS_WRR=y +CONFIG_IP_VS_SH=y +CONFIG_IP_VS_NFCT=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_BRIDGE_NF_EBTABLES=y +CONFIG_BRIDGE_EBT_ARP=y +CONFIG_BRIDGE_EBT_IP=y +CONFIG_BRIDGE_EBT_IP6=y +CONFIG_BRIDGE_EBT_MARK=y +CONFIG_BRIDGE_EBT_VLAN=y +CONFIG_BRIDGE_EBT_MARK_T=y CONFIG_BRIDGE=y CONFIG_VLAN_8021Q=m -CONFIG_BONDING=m -CONFIG_TUN=y -CONFIG_VETH=y -CONFIG_MACVLAN=y -CONFIG_MACVTAP=y -CONFIG_IPVLAN=y -CONFIG_TAP=y -CONFIG_DUMMY=m -CONFIG_VXLAN=y -CONFIG_GENEVE=m -CONFIG_NLMON=y -# Traffic control / QoS (needed by CNI plugins) CONFIG_NET_SCHED=y CONFIG_NET_SCH_HTB=m CONFIG_NET_SCH_PRIO=m @@ -113,165 +141,151 @@ CONFIG_NET_CLS_U32=y CONFIG_NET_CLS_CGROUP=y CONFIG_NET_CLS_BPF=y CONFIG_NET_CLS_MATCHALL=y +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_U32=y CONFIG_NET_CLS_ACT=y CONFIG_NET_ACT_POLICE=y CONFIG_NET_ACT_GACT=y CONFIG_NET_ACT_MIRRED=y CONFIG_NET_ACT_BPF=y -CONFIG_NET_EMATCH=y -CONFIG_NET_EMATCH_U32=y - -# --- Netfilter --- -CONFIG_NETFILTER=y -CONFIG_NETFILTER_ADVANCED=y -CONFIG_NF_CONNTRACK=y -CONFIG_NF_NAT=y -CONFIG_NF_TABLES=y -CONFIG_NF_TABLES_INET=y -CONFIG_NF_TABLES_IPV4=y -CONFIG_NF_TABLES_IPV6=y -CONFIG_NFT_NAT=y -CONFIG_NFT_MASQ=y -CONFIG_NFT_CT=y -CONFIG_NFT_COMPAT=y -CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y -CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y -CONFIG_NETFILTER_XT_MATCH_COMMENT=y -CONFIG_NETFILTER_XT_MATCH_MARK=y -CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y -CONFIG_IP_NF_IPTABLES=y -CONFIG_IP_NF_FILTER=y -CONFIG_IP_NF_NAT=y -CONFIG_IP6_NF_IPTABLES=y -CONFIG_IP6_NF_FILTER=y -CONFIG_IP6_NF_NAT=y -CONFIG_BRIDGE_NETFILTER=y -CONFIG_BRIDGE_NF_EBTABLES=y -CONFIG_BRIDGE_EBT_BROUTE=y -CONFIG_BRIDGE_EBT_T_FILTER=y -CONFIG_BRIDGE_EBT_T_NAT=y -CONFIG_BRIDGE_EBT_ARP=y -CONFIG_BRIDGE_EBT_IP=y -CONFIG_BRIDGE_EBT_IP6=y -CONFIG_BRIDGE_EBT_MARK=y -CONFIG_BRIDGE_EBT_MARK_T=y -CONFIG_BRIDGE_EBT_VLAN=y -# IPVS (container load balancing / kube-proxy) -CONFIG_IP_VS=y -CONFIG_IP_VS_IPV6=y -CONFIG_IP_VS_PROTO_TCP=y -CONFIG_IP_VS_PROTO_UDP=y -CONFIG_IP_VS_RR=y -CONFIG_IP_VS_WRR=y -CONFIG_IP_VS_SH=y -CONFIG_IP_VS_NFCT=y -# IP sets (used by iptables/nftables for efficient matching) -CONFIG_IP_SET=y -CONFIG_IP_SET_HASH_IP=y -CONFIG_IP_SET_HASH_IPPORT=y -CONFIG_IP_SET_HASH_IPPORTNET=y -CONFIG_IP_SET_HASH_NET=y -CONFIG_IP_SET_HASH_NETPORT=y -CONFIG_IP_SET_BITMAP_IP=y -CONFIG_IP_SET_BITMAP_PORT=y -CONFIG_IP_SET_LIST_SET=y -CONFIG_NETFILTER_XT_SET=y - -# --- Block devices --- -CONFIG_BLK_DEV=y +CONFIG_CGROUP_NET_PRIO=y +# CONFIG_WIRELESS is not set +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_PCI_IOV=y +CONFIG_PCI_PASID=y +CONFIG_HOTPLUG_PCI=y +CONFIG_HOTPLUG_PCI_ACPI=y +CONFIG_PCI_AARDVARK=y +CONFIG_PCIE_ALTERA=y +CONFIG_PCI_HOST_THUNDER_PEM=y +CONFIG_PCI_HOST_THUNDER_ECAM=y +CONFIG_PCI_HOST_GENERIC=y +CONFIG_PCI_TEGRA=y +CONFIG_PCIE_ROCKCHIP_HOST=y +CONFIG_PCI_XGENE=y +CONFIG_PCIE_AL=y +CONFIG_PCI_MESON=y +CONFIG_PCI_HISI=y +CONFIG_PCIE_KIRIN=y +CONFIG_PCIE_HISI_STB=y +CONFIG_PCIE_ARMADA_8K=y +CONFIG_PCIE_QCOM=y +CONFIG_UEVENT_HELPER=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_CONNECTOR=y +CONFIG_ARM_SCMI_PROTOCOL=y +CONFIG_DMI_SYSFS=y +CONFIG_SYSFB_SIMPLEFB=y CONFIG_BLK_DEV_LOOP=y CONFIG_BLK_DEV_NBD=y CONFIG_BLK_DEV_RAM=y CONFIG_BLK_DEV_RAM_SIZE=65536 +CONFIG_VIRTIO_BLK=y CONFIG_BLK_DEV_NVME=y -CONFIG_ATA=y -CONFIG_SATA_AHCI=y -CONFIG_SATA_AHCI_PLATFORM=y - -# --- SCSI --- -CONFIG_SCSI=y CONFIG_BLK_DEV_SD=y CONFIG_CHR_DEV_SG=y -CONFIG_MEGARAID_SAS=y -CONFIG_SCSI_MPT3SAS=y -CONFIG_SCSI_MPI3MR=m CONFIG_SCSI_HPSA=y CONFIG_SCSI_HISI_SAS=y CONFIG_SCSI_HISI_SAS_PCI=y +CONFIG_MEGARAID_SAS=y +CONFIG_SCSI_MPT3SAS=y +CONFIG_SCSI_MPI3MR=m CONFIG_XEN_SCSI_FRONTEND=y CONFIG_SCSI_VIRTIO=y - -# --- Device mapper --- +CONFIG_ATA=y +CONFIG_SATA_AHCI=y +CONFIG_SATA_AHCI_PLATFORM=y CONFIG_MD=y CONFIG_BLK_DEV_DM=y CONFIG_DM_CRYPT=y -CONFIG_DM_THIN_PROVISIONING=y CONFIG_DM_SNAPSHOT=y +CONFIG_DM_THIN_PROVISIONING=y CONFIG_DM_MULTIPATH=m -CONFIG_MD_RAID0=m -CONFIG_MD_RAID1=m - -# --- Network drivers (built-in for reliable boot) --- CONFIG_NETDEVICES=y +CONFIG_BONDING=m +CONFIG_DUMMY=m +CONFIG_MACVLAN=y +CONFIG_MACVTAP=y +CONFIG_IPVLAN=y +CONFIG_VXLAN=y +CONFIG_GENEVE=m +CONFIG_TUN=y +CONFIG_VETH=y CONFIG_VIRTIO_NET=y +CONFIG_NLMON=y +CONFIG_ENA_ETHERNET=m +CONFIG_AMD_XGBE=y +CONFIG_NET_XGENE=y +CONFIG_BCMGENET=y +CONFIG_TIGON3=y +CONFIG_BNXT=y +CONFIG_MACB=m +CONFIG_THUNDER_NIC_PF=m +CONFIG_THUNDER_NIC_VF=m +CONFIG_GVE=m +CONFIG_HNS3=m CONFIG_E1000E=y CONFIG_IGB=y CONFIG_IXGBE=y CONFIG_I40E=y CONFIG_ICE=y CONFIG_IGC=y +CONFIG_MVNETA=y +CONFIG_MVPP2=y CONFIG_MLX4_EN=y CONFIG_MLX5_CORE=y CONFIG_MLX5_CORE_EN=y -CONFIG_BNXT=y -CONFIG_TIGON3=y CONFIG_R8169=y -CONFIG_THUNDER_NIC_BGX=m -CONFIG_THUNDER_NIC_PF=m -CONFIG_THUNDER_NIC_VF=m -CONFIG_MACB=m -CONFIG_HNS3=m -CONFIG_NET_XGENE=y -CONFIG_MVNETA=y -CONFIG_MVPP2=y CONFIG_STMMAC_ETH=m -CONFIG_ENA_ETHERNET=m -CONFIG_GVE=m -CONFIG_VMXNET3=y -CONFIG_AMD_XGBE=y -# Broadcom GENET (Raspberry Pi 4B built-in Ethernet) -CONFIG_BCMGENET=y -# PHY subsystem (required by GENET and other MAC drivers) -CONFIG_PHYLIB=y -CONFIG_MDIO_BUS=y +CONFIG_AMD_PHY=y +CONFIG_MESON_GXL_PHY=y CONFIG_BROADCOM_PHY=y -CONFIG_BCM7XXX_PHY=y -CONFIG_MDIO_BCM_UNIMAC=y CONFIG_MARVELL_PHY=y CONFIG_MARVELL_10G_PHY=y CONFIG_MICREL_PHY=y CONFIG_ROCKCHIP_PHY=y -CONFIG_REALTEK_PHY=y -CONFIG_MESON_GXL_PHY=y -CONFIG_AMD_PHY=y - -# --- USB --- -CONFIG_USB_SUPPORT=y +# CONFIG_WLAN is not set +CONFIG_VMXNET3=y +CONFIG_INPUT_EVDEV=y +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_VIRTIO_CONSOLE=y +CONFIG_IPMI_HANDLER=m +CONFIG_IPMI_DEVICE_INTERFACE=m +CONFIG_IPMI_SI=m +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_TIMERIOMEM=y +CONFIG_HW_RANDOM_VIRTIO=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_MV64XXX=y +CONFIG_I2C_RK3X=y +CONFIG_I2C_TEGRA=y +CONFIG_SPI=y +CONFIG_SPI_ORION=y +CONFIG_SPI_PL022=y +CONFIG_SPI_ROCKCHIP=y +CONFIG_WATCHDOG=y +CONFIG_DRM=y +CONFIG_DRM_SIMPLEDRM=y +CONFIG_DRM_VIRTIO_GPU=y +CONFIG_FB=y +CONFIG_FB_EFI=y CONFIG_USB=y CONFIG_USB_XHCI_HCD=y CONFIG_USB_EHCI_HCD=y CONFIG_USB_OHCI_HCD=y -CONFIG_USB_XHCI_PCI=y -CONFIG_USB_UAS=y CONFIG_USB_STORAGE=y +CONFIG_USB_UAS=y CONFIG_USB_DWC3=y -CONFIG_USB_DWC3_HOST=y CONFIG_USB_DWC2=y CONFIG_USB_ISP1760=y - -# --- MMC/SD (Raspberry Pi, Rockchip, and other SBCs) --- CONFIG_MMC=y CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_MMC_ARMMMCI=y CONFIG_MMC_SDHCI=y CONFIG_MMC_SDHCI_PCI=y CONFIG_MMC_SDHCI_ACPI=y @@ -280,27 +294,48 @@ CONFIG_MMC_SDHCI_OF_ARASAN=y CONFIG_MMC_SDHCI_OF_DWCMSHC=y CONFIG_MMC_SDHCI_CADENCE=y CONFIG_MMC_SDHCI_TEGRA=y -CONFIG_MMC_SDHCI_MSM=y -CONFIG_MMC_SDHCI_XENON=y CONFIG_MMC_SDHCI_F_SDH30=y -CONFIG_MMC_ARMMMCI=y +CONFIG_MMC_MESON_GX=y +CONFIG_MMC_SDHCI_MSM=y +CONFIG_MMC_SPI=y CONFIG_MMC_DW=y -CONFIG_MMC_DW_ROCKCHIP=y CONFIG_MMC_DW_K3=y -CONFIG_MMC_MESON_GX=y +CONFIG_MMC_DW_ROCKCHIP=y CONFIG_MMC_SUNXI=m -CONFIG_MMC_SPI=y CONFIG_MMC_HSQ=y - -# --- Filesystems --- +CONFIG_MMC_SDHCI_XENON=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_EFI=y +CONFIG_DMADEVICES=y +CONFIG_PL330_DMA=y +CONFIG_VFIO=y +CONFIG_VFIO_PCI=y +CONFIG_VIRTIO_PCI=y +CONFIG_VIRTIO_BALLOON=y +CONFIG_VIRTIO_INPUT=y +CONFIG_VIRTIO_MMIO=y +CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y +CONFIG_VHOST_NET=m +CONFIG_ARM_SMMU=y +CONFIG_ARM_SMMU_V3=y +CONFIG_TEE=y +CONFIG_OPTEE=y CONFIG_EXT4_FS=y CONFIG_XFS_FS=m +CONFIG_FUSE_FS=y +CONFIG_CUSE=y +CONFIG_OVERLAY_FS=y +CONFIG_ISO9660_FS=y +CONFIG_JOLIET=y +CONFIG_UDF_FS=y +CONFIG_MSDOS_FS=y CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_IOCHARSET="utf8" +CONFIG_NTFS_FS=m CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y -CONFIG_PROC_FS=y -CONFIG_SYSFS=y -CONFIG_OVERLAY_FS=y +CONFIG_HUGETLBFS=y +CONFIG_EFIVAR_FS=y CONFIG_SQUASHFS=y CONFIG_SQUASHFS_XATTR=y CONFIG_SQUASHFS_LZ4=y @@ -310,206 +345,17 @@ CONFIG_NFS_FS=m CONFIG_NFS_V4=m CONFIG_NFS_V4_1=y CONFIG_NFS_V4_2=y -CONFIG_FUSE_FS=y -CONFIG_CUSE=y -CONFIG_EFIVAR_FS=y -CONFIG_ISO9660_FS=y -CONFIG_JOLIET=y -CONFIG_UDF_FS=y -CONFIG_MSDOS_FS=y -CONFIG_FAT_DEFAULT_IOCHARSET="utf8" -CONFIG_NTFS_FS=m -CONFIG_TMPFS_XATTR=y CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_ASCII=y CONFIG_NLS_ISO8859_1=y CONFIG_NLS_UTF8=y - -# --- Virtio (built-in for reliable boot) --- -CONFIG_VIRTIO=y -CONFIG_VIRTIO_PCI=y -CONFIG_VIRTIO_BLK=y -CONFIG_VIRTIO_CONSOLE=y -CONFIG_VIRTIO_BALLOON=y -CONFIG_VIRTIO_INPUT=y -CONFIG_VIRTIO_MMIO=y -CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y -CONFIG_HW_RANDOM_VIRTIO=y - -# --- Console / Serial --- -CONFIG_SERIAL_AMBA_PL011=y -CONFIG_SERIAL_AMBA_PL011_CONSOLE=y -CONFIG_SERIAL_8250=y -CONFIG_SERIAL_8250_CONSOLE=y -CONFIG_VT=y -CONFIG_VT_CONSOLE=y -CONFIG_HW_CONSOLE=y -CONFIG_DUMMY_CONSOLE=y - -# --- Security --- -CONFIG_SECCOMP=y -CONFIG_SECCOMP_FILTER=y CONFIG_SECURITY=y -CONFIG_KEYS=y - -# --- Crypto --- -CONFIG_CRYPTO=y CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_XTS=y CONFIG_CRYPTO_SHA256=y CONFIG_CRYPTO_SHA512=y -CONFIG_CRYPTO_XTS=y -CONFIG_ARM64_CRYPTO=y CONFIG_CRYPTO_AES_ARM64_CE=y -CONFIG_CRYPTO_SHA2_ARM64_CE=y - -# --- BPF --- -CONFIG_BPF=y -CONFIG_BPF_SYSCALL=y -CONFIG_BPF_JIT=y -CONFIG_BPF_JIT_ALWAYS_ON=y -CONFIG_XDP_SOCKETS=y - -# --- ARM64 SoC/platform support --- -CONFIG_ARCH_BCM2835=y -CONFIG_ARCH_ROCKCHIP=y -CONFIG_ARCH_MESON=y -CONFIG_ARCH_SUNXI=y -CONFIG_ARCH_TEGRA=y -CONFIG_ARCH_MVEBU=y -CONFIG_ARCH_HISI=y -CONFIG_ARCH_QCOM=y -CONFIG_ARCH_EXYNOS=y -CONFIG_ARCH_VEXPRESS=y -CONFIG_ARCH_XGENE=y -CONFIG_ARCH_THUNDER=y -CONFIG_ARCH_THUNDER2=y -CONFIG_ARCH_SEATTLE=y -CONFIG_ARCH_SYNQUACER=y -CONFIG_ARCH_UNIPHIER=y -# ACPI / UEFI -CONFIG_ACPI=y -CONFIG_ACPI_DOCK=y -CONFIG_ACPI_IPMI=y -CONFIG_ACPI_APEI=y -CONFIG_ACPI_APEI_GHES=y -CONFIG_IPMI_HANDLER=m -CONFIG_IPMI_DMI_DECODE=y -CONFIG_IPMI_DEVICE_INTERFACE=m -CONFIG_IPMI_SI=m -CONFIG_PCI=y -CONFIG_PCIEPORTBUS=y -CONFIG_HOTPLUG_PCI=y -CONFIG_PCI_HOST_GENERIC=y -CONFIG_PCIE_ROCKCHIP_HOST=y -CONFIG_PCIE_ARMADA_8K=y -CONFIG_PCIE_TEGRA194_HOST=y -# Broadcom STB PCIe controller (Raspberry Pi 4B — needed for VL805 USB 3.0) -CONFIG_PCIE_BRCMSTB=y -CONFIG_PCIE_ALTERA=y -CONFIG_PCI_AARDVARK=y -CONFIG_PCI_TEGRA=y -CONFIG_PCI_XGENE=y -CONFIG_PCI_HOST_THUNDER_PEM=y -CONFIG_PCI_HOST_THUNDER_ECAM=y -CONFIG_PCIE_QCOM=y -CONFIG_PCIE_AL=y -CONFIG_PCI_MESON=y -CONFIG_PCI_HISI=y -CONFIG_PCIE_KIRIN=y -CONFIG_PCIE_HISI_STB=y -CONFIG_PCI_IOV=y -CONFIG_PCI_PASID=y -CONFIG_HOTPLUG_PCI_ACPI=y -# IOMMU -CONFIG_ARM_SMMU=y -CONFIG_ARM_SMMU_V3=y -CONFIG_FW_LOADER=y -# Raspberry Pi firmware mailbox (required for GENET MAC address, clocks, etc.) -CONFIG_RASPBERRYPI_FIRMWARE=y -CONFIG_RASPBERRYPI_POWER=y -CONFIG_ARM_SCMI_PROTOCOL=y -CONFIG_ARM_SCPI_PROTOCOL=y -CONFIG_OF=y -CONFIG_USE_OF=y -# RTC -CONFIG_RTC_CLASS=y -CONFIG_RTC_DRV_EFI=y -# DMA -CONFIG_DMADEVICES=y -CONFIG_PL330_DMA=y -CONFIG_PRINTK=y CONFIG_PRINTK_TIME=y -CONFIG_MODULES=y -CONFIG_MODULE_UNLOAD=y -CONFIG_PANIC_TIMEOUT=10 -CONFIG_PANIC_ON_OOPS=y CONFIG_MAGIC_SYSRQ=y -CONFIG_RD_GZIP=y -CONFIG_RD_XZ=y -CONFIG_RD_ZSTD=y -CONFIG_UEVENT_HELPER=y -CONFIG_DEVTMPFS=y -CONFIG_DEVTMPFS_MOUNT=y -CONFIG_CONNECTOR=y -CONFIG_DMI_SYSFS=y -CONFIG_EFI=y -CONFIG_EFI_STUB=y - -# --- Framebuffer/Console (required for EFI boot video output) --- -CONFIG_DRM=y -CONFIG_DRM_SIMPLEDRM=y -CONFIG_DRM_VIRTIO_GPU=y -CONFIG_FB=y -CONFIG_FB_EFI=y -CONFIG_FRAMEBUFFER_CONSOLE=y -CONFIG_SYSFB_SIMPLEFB=y -# Virtualization -CONFIG_VIRTUALIZATION=y -CONFIG_KVM=y -CONFIG_VFIO=y -CONFIG_VFIO_PCI=y -CONFIG_VHOST_NET=m -CONFIG_VHOST_VSOCK=m - -# --- Module compression --- -CONFIG_MODULE_COMPRESS_ZSTD=y -CONFIG_MODULE_DECOMPRESS=y - -# --- I2C (SBC board management, EEPROM, sensors) --- -CONFIG_I2C=y -CONFIG_I2C_CHARDEV=y -CONFIG_I2C_MV64XXX=y -CONFIG_I2C_RK3X=y -CONFIG_I2C_TEGRA=y -CONFIG_I2C_DESIGNWARE_PLATFORM=y -# SPI -CONFIG_SPI=y -CONFIG_SPI_ROCKCHIP=y -CONFIG_SPI_PL022=y -CONFIG_SPI_ORION=y -# GPIO -CONFIG_GPIOLIB=y -# Hardware watchdog -CONFIG_WATCHDOG=y -CONFIG_SOFTLOCKUP_DETECTOR=y -CONFIG_WQ_WATCHDOG=y -# Hardware RNG -CONFIG_HW_RANDOM=y -CONFIG_HW_RANDOM_TIMERIOMEM=y -# TPM -CONFIG_TCG_TIS=y -CONFIG_TCG_FTPM_TEE=y -CONFIG_TEE=y -CONFIG_OPTEE=y - -# --- Disable unnecessary subsystems --- -# CONFIG_SOUND is not set -# CONFIG_WIRELESS is not set -# CONFIG_WLAN is not set -# CONFIG_BLUETOOTH is not set -# CONFIG_NFC is not set -# CONFIG_INFINIBAND is not set -# CONFIG_MEDIA_SUPPORT is not set -CONFIG_INPUT_EVDEV=y -# CONFIG_INPUT_JOYSTICK is not set -# CONFIG_INPUT_TOUCHSCREEN is not set +CONFIG_PANIC_ON_OOPS=y +CONFIG_PANIC_TIMEOUT=10 From 8c89fe0fdcf937ce2f81b82f540aa532a1f3b205 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 13 Apr 2026 21:00:43 +0200 Subject: [PATCH 77/93] 6.18.y.arm64: defconfig: `CONFIG_MODULE_COMPRESS_ZSTD=y` Signed-off-by: Ricardo Pardini --- kernel.configs/6.18.y.arm64 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel.configs/6.18.y.arm64 b/kernel.configs/6.18.y.arm64 index 5f7cff8..0df517a 100644 --- a/kernel.configs/6.18.y.arm64 +++ b/kernel.configs/6.18.y.arm64 @@ -63,6 +63,8 @@ CONFIG_VIRTUALIZATION=y CONFIG_KVM=y CONFIG_MODULES=y CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_COMPRESS=y +CONFIG_MODULE_COMPRESS_ZSTD=y CONFIG_BLK_DEV_THROTTLING=y CONFIG_BLK_CGROUP_IOLATENCY=y CONFIG_MEMORY_HOTPLUG=y From 4dd076c068e160dabbeefbd562d978dfe7706635 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 13 Apr 2026 22:30:32 +0200 Subject: [PATCH 78/93] util: show `run()`'s env vars in a Rich Table Signed-off-by: Ricardo Pardini --- captain/util.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/captain/util.py b/captain/util.py index 5edf596..040af17 100644 --- a/captain/util.py +++ b/captain/util.py @@ -14,6 +14,7 @@ from rich.panel import Panel from rich.rule import Rule from rich.syntax import Syntax +from rich.table import Table import captain @@ -104,6 +105,15 @@ def run( width=captain.console.width, ) captain.console.print(panel) + if env: + # add a Rich Table with the env vars, two columns + table = Table(show_header=True, header_style="bold magenta") + table.add_column("Env Var", style="cyan", no_wrap=True) + table.add_column("Value", style="white") + for k, v in env.items(): + table.add_row(k, v) + captain.console.print(table) + captain.console.print(Rule(f"⮕ Starting subprocess: {cmd} ⮕", style="green")) proc = subprocess.run( From 5f8757e8f2699c9bba300f2aee940c54ffb6677d Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 13 Apr 2026 22:32:48 +0200 Subject: [PATCH 79/93] docker: rework `run_in_builder()` with both `command_and_args` and `extra_docker_args` - also introduce `cfg.verbose_docker` for setting env `CAPTAIN_VERBOSE=1` when relaunching Signed-off-by: Ricardo Pardini --- captain/cli/_main.py | 3 +++ captain/cli/_release_publish.py | 16 ++++++++------- captain/docker.py | 35 ++++++++++++++++++++++----------- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/captain/cli/_main.py b/captain/cli/_main.py index 14b6168..2fef687 100644 --- a/captain/cli/_main.py +++ b/captain/cli/_main.py @@ -33,6 +33,7 @@ class CliContext: builder_registry: str | None builder_repository: str | None builder_image: str + verbose_docker: bool def make_config(self, **overrides: Any) -> Config: """Build a :class:`Config` from the common options plus per-command *overrides*.""" @@ -44,6 +45,7 @@ def make_config(self, **overrides: Any) -> Config: builder_registry=self.builder_registry, builder_repository=self.builder_repository, builder_image=self.builder_image, + verbose_docker=self.verbose_docker, **overrides, ) @@ -171,6 +173,7 @@ def cli( builder_registry=builder_registry, builder_repository=builder_repository, builder_image=builder_image, + verbose_docker=verbose, ) diff --git a/captain/cli/_release_publish.py b/captain/cli/_release_publish.py index daa4b66..6561cd0 100644 --- a/captain/cli/_release_publish.py +++ b/captain/cli/_release_publish.py @@ -151,13 +151,15 @@ def release_publish_cmd( try: docker.run_in_builder( cfg, - *env_args, - "--entrypoint", - "/usr/bin/uv", - cfg.builder_image, - *(["--verbose"] if cfg.verbose_uv else ["--quiet"]), - "run", - *inner_cmd, + command_and_args=[ + "--entrypoint", + "/usr/bin/uv", + cfg.builder_image, + *(["--verbose"] if cfg.verbose_uv else ["--quiet"]), + "run", + *inner_cmd, + ], + extra_docker_args=env_args, ) except subprocess.CalledProcessError as exc: raise SystemExit(exc.returncode) from None diff --git a/captain/docker.py b/captain/docker.py index 254ac57..a54b9b7 100644 --- a/captain/docker.py +++ b/captain/docker.py @@ -143,7 +143,9 @@ def obtain_builder(cfg: Config) -> None: run(["docker", "push", remote_tagged_image]) -def run_in_builder(cfg: Config, *extra_args: str) -> None: +def run_in_builder( + cfg: Config, command_and_args: list[str], extra_docker_args: list[str] | None = None +) -> None: """Run a command inside the Docker builder container. *extra_args* are appended after the docker run flags and image name. @@ -174,6 +176,7 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: "REGISTRY_AUTH_FILE": os.environ.get("REGISTRY_AUTH_FILE", ""), "REGISTRY_USERNAME": os.environ.get("REGISTRY_USERNAME", ""), "REGISTRY_PASSWORD": os.environ.get("REGISTRY_PASSWORD", ""), + "CAPTAIN_VERBOSE": "1" if cfg.verbose_docker else "0", } docker_args: list[str] = [ @@ -185,6 +188,10 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: "/work", ] + if extra_docker_args is not None: + log.debug("Adding extra Docker args: %s", extra_docker_args) + docker_args.extend(extra_docker_args) + if log.isEnabledFor(logging.DEBUG): table = Table( title="Docker Environment Variables", show_header=True, header_style="bold cyan" @@ -219,8 +226,9 @@ def run_in_builder(cfg: Config, *extra_args: str) -> None: "--mount", f"type=volume,source=captain-kernel-build,target={KERNEL_BUILD_BASE_PATH}", ] + docker_args += [cfg.builder_image] - docker_args.extend(extra_args) + docker_args.extend(command_and_args) run(docker_args) @@ -228,12 +236,14 @@ def run_captain_in_builder(cfg: Config, *extra_args: str): log.debug("Running 'captain %s' in builder container...", extra_args) run_in_builder( cfg, - cfg.builder_image, - "/usr/bin/uv", - *(["--verbose"] if cfg.verbose_uv else ["--quiet"]), - "run", - "captain", - *extra_args, + [ + "/usr/bin/uv", + *(["--verbose"] if cfg.verbose_uv else ["--quiet"]), + "run", + "captain", + *extra_args, + ], + extra_docker_args=[], ) @@ -242,10 +252,11 @@ def run_mkosi_in_builder(cfg: Config, *mkosi_args: str) -> None: ensure_binfmt(cfg) run_in_builder( cfg, - cfg.builder_image, - "/usr/local/bin/mkosi", - f"--architecture={cfg.arch_info.mkosi_arch}", - *mkosi_args, + command_and_args=[ + "/usr/local/bin/mkosi", + f"--architecture={cfg.arch_info.mkosi_arch}", + *mkosi_args, + ], ) From f17541085b6fa3e04510f8a2eee4905723b28da6 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 13 Apr 2026 22:33:39 +0200 Subject: [PATCH 80/93] captain/cli: `--verbose` sets root level as well as current logger to DEBUG Signed-off-by: Ricardo Pardini --- captain/cli/_main.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/captain/cli/_main.py b/captain/cli/_main.py index 2fef687..303d115 100644 --- a/captain/cli/_main.py +++ b/captain/cli/_main.py @@ -159,7 +159,11 @@ def cli( ) -> None: """CaptainOS build system — click CLI.""" # Configure log level based on --verbose. - logging.getLogger().setLevel(logging.DEBUG if verbose else logging.INFO) + # Configure the root logger (configged via basicLogging() in captain __init__) + # since subcommands and imported modules will use it. + log_level = logging.DEBUG if verbose else logging.INFO + logging.getLogger().setLevel(log_level) + logging.getLogger("captain").setLevel(log_level) if ctx.invoked_subcommand is None: click.echo(ctx.get_help()) From 8cc3f44fa01f3db9feaee226475bfe49cdf1f0a2 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 13 Apr 2026 22:34:49 +0200 Subject: [PATCH 81/93] cli: re-introduce `shell` subcommand - which runs bash interactively under Docker - using same envs / volumes / mounts as regular run_in_builder() Signed-off-by: Ricardo Pardini --- captain/cli/_main.py | 10 +++++++++- captain/cli/_shell.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 captain/cli/_shell.py diff --git a/captain/cli/_main.py b/captain/cli/_main.py index 303d115..3a43357 100644 --- a/captain/cli/_main.py +++ b/captain/cli/_main.py @@ -189,6 +189,14 @@ def cli( def main() -> None: """Console-script entry point.""" # Import subcommand modules to register them on the group. - from captain.cli import _build, _builder, _iso, _kernel, _release_publish, _tools # noqa: F401 + from captain.cli import ( # noqa: F401 + _build, + _builder, + _iso, + _kernel, + _release_publish, + _shell, + _tools, + ) cli() diff --git a/captain/cli/_shell.py b/captain/cli/_shell.py new file mode 100644 index 0000000..9dca067 --- /dev/null +++ b/captain/cli/_shell.py @@ -0,0 +1,28 @@ +"""``captain tools`` — download tools (containerd, runc, nerdctl, CNI plugins).""" + +from __future__ import annotations + +import logging + +import click + +import captain.docker as docker +from captain.cli._main import CliContext, cli + +log = logging.getLogger(__name__) + + +@cli.command( + "shell", + short_help="Get a shell in the builder environment.", +) +@click.pass_obj +def tools_cmd(cli_ctx: CliContext) -> None: + log.warning("Starting shell in docker...") + + cfg = cli_ctx.make_config() + + docker.obtain_builder(cfg) + docker.run_in_builder(cfg, command_and_args=["bash"], extra_docker_args=["-i", "-t"]) + + log.info("Shell stage complete!") From fb7846fcafb48cfe51dce546be0d4f03b4cc17a6 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 13 Apr 2026 22:38:43 +0200 Subject: [PATCH 82/93] kernel: rework with kernel's own `make bindeb-pkg` into a .deb output - kernel packaging code already knows how to: - strip modules - zstd modules - handle Image name differences - handle depmod - produce a .deb package - but we gotta go 2-deep as .deb's are output in parent directory by kbuild - Dockerfile: add layers with deps: - rust compiler layer (inevitable after 7.0.y) - debhelper + etc needed for kernel deb packaging Signed-off-by: Ricardo Pardini --- Dockerfile | 6 ++ captain/cli/_kernel.py | 7 +- captain/cli/_stages.py | 20 +----- captain/config.py | 16 +---- captain/kernel.py | 146 ++++++++++++++++------------------------- 5 files changed, 72 insertions(+), 123 deletions(-) diff --git a/Dockerfile b/Dockerfile index b02bcdb..bd561c6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -111,6 +111,12 @@ RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends buildah # This is just to appease mkosi's later stages. RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends python3 python3-pip python3-pefile +# Rust stuff for kernel build. +RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends rustc rust-src bindgen rustfmt rust-clippy + +# For kernel's bindeb-pkg and menuconfig +RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends debhelper libdw-dev lsb-release libncurses-dev + RUN <<-CONFIG_FRAG ## A few small config fragments to make life easier # git: Ignore owner mismatches in /work, which will be bind-mounted from the host diff --git a/captain/cli/_kernel.py b/captain/cli/_kernel.py index e1dc795..0d386c0 100644 --- a/captain/cli/_kernel.py +++ b/captain/cli/_kernel.py @@ -1,12 +1,10 @@ -"""``captain tools`` — download tools (containerd, runc, nerdctl, CNI plugins).""" - from __future__ import annotations import logging import click -from captain import artifacts, config +from captain import config from captain.cli._main import CliContext, cli from captain.cli._stages import _build_kernel_stage @@ -41,7 +39,7 @@ help="Kernel version to build. Must match an official tarball.", ) @click.pass_obj -def tools_cmd( +def kernel_cmd( cli_ctx: CliContext, *, kernel_mode: str, @@ -58,5 +56,4 @@ def tools_cmd( ) _build_kernel_stage(cfg) - artifacts.collect_kernel(cfg) log.info("Kernel build stage complete!") diff --git a/captain/cli/_stages.py b/captain/cli/_stages.py index 948bf6a..ffac59f 100644 --- a/captain/cli/_stages.py +++ b/captain/cli/_stages.py @@ -21,26 +21,12 @@ def _build_kernel_stage(cfg: Config) -> None: return # --- idempotency -------------------------------------------------- - modules_dir = cfg.modules_output / "usr" / "lib" / "modules" - vmlinuz_dir = cfg.kernel_output - has_vmlinuz = vmlinuz_dir.is_dir() and any(vmlinuz_dir.glob("vmlinuz-*")) - log.debug("Kernel build idempotency check: modules_dir=%s exists=%s", modules_dir, - modules_dir.is_dir()) - log.debug("Kernel build idempotency check: vmlinuz_dir=%s exists=%s", vmlinuz_dir, - vmlinuz_dir.is_dir()) - log.debug( - "Checking kernel build idempotency: modules_dir=%s, has_vmlinuz=%s", - modules_dir, - has_vmlinuz, - ) - - if modules_dir.is_dir() and has_vmlinuz and not cfg.force_kernel: + image_deb_package = kernel.obtain_target_artifact_path(cfg) + log.debug("Checking for existing kernel artifact at %s", image_deb_package) + if image_deb_package.is_file() and not cfg.force_kernel: log.info("Kernel already built (use --force-kernel to rebuild)") return - if modules_dir.is_dir() and not has_vmlinuz: - log.warning("Modules exist but vmlinuz is missing — rebuilding kernel") - # --- native ------------------------------------------------------- if cfg.kernel_mode == "native": missing = check_kernel_dependencies(cfg.arch) diff --git a/captain/config.py b/captain/config.py index ddf39ec..35a573c 100644 --- a/captain/config.py +++ b/captain/config.py @@ -40,15 +40,15 @@ class Config: # For kernel build build_kernel: bool = False kernel_version: str = DEFAULT_KERNEL_VERSION - kernel_config: str | None = None - kernel_src: str | None = None force_kernel: bool = False + kernel_clean: bool = False # Docker builder_registry: str | None = None builder_repository: str | None = None builder_image: str = "captainos-builder" builder_push: bool = False + verbose_docker: bool = False # Pass --verbose to captain when re-launched in Docker # Per-stage mode: "docker" | "native" | "skip" tools_mode: str = "docker" @@ -108,17 +108,7 @@ def kernel_output(self) -> Path: (``usr/lib/modules/{kver}/``) so it can be passed directly as an ``--extra-tree=`` to mkosi. """ - return self.project_dir / "mkosi.output" / "kernel" / self.kernel_version / self.arch - - @property - def modules_output(self) -> Path: - """Per-version, per-arch root for kernel modules. - - Returns ``kernel/{version}/{arch}/modules`` which contains a - merged-usr tree (``usr/lib/modules/{kver}/``) suitable for - passing as ``--extra-tree=`` to mkosi. - """ - return self.kernel_output / "modules" + return self.project_dir / "mkosi.output" / "kernel" / self.arch @property def mkosi_output(self) -> Path: diff --git a/captain/kernel.py b/captain/kernel.py index 0b307aa..e7022f9 100644 --- a/captain/kernel.py +++ b/captain/kernel.py @@ -30,7 +30,7 @@ from captain.config import Config from captain.util import ensure_dir, run, safe_extractall -KERNEL_BUILD_BASE_PATH = "/var/tmp/kernel-build" +KERNEL_BUILD_BASE_PATH = "/work/kernel-build" log = logging.getLogger(__name__) @@ -60,18 +60,18 @@ def _download_with_progress(url: str, filename: Path) -> None: progress.update(task, advance=len(buf)) -def download_kernel(version: str, dest_dir: Path) -> Path: +def download_kernel(cfg: Config, dest_dir: Path) -> Path: """Download and extract a kernel tarball. Returns the source directory.""" - src_dir = dest_dir / f"linux-{version}" + src_dir = dest_dir / f"linux-{cfg.kernel_version}" # predict kernel tarball 1st-level dir name if src_dir.is_dir(): log.info("Using cached kernel source at %s", src_dir) return src_dir - major = version.split(".")[0] - url = f"https://cdn.kernel.org/pub/linux/kernel/v{major}.x/linux-{version}.tar.xz" - tarball = dest_dir / f"linux-{version}.tar.xz" + major = cfg.kernel_version.split(".")[0] + url = f"https://cdn.kernel.org/pub/linux/kernel/v{major}.x/linux-{cfg.kernel_version}.tar.xz" + tarball = dest_dir / f"linux-{cfg.kernel_version}.tar.xz" - log.info("Downloading kernel %s...", version) + log.info("Downloading kernel %s...", cfg.kernel_version) log.info(" URL: %s", url) ensure_dir(dest_dir) try: @@ -102,15 +102,6 @@ def _kernel_branch(version: str) -> str: def _find_defconfig(cfg: Config) -> Path: """Locate the defconfig for the current kernel version and architecture.""" - if cfg.kernel_config: - explicit = Path(cfg.kernel_config) - if not explicit.is_absolute(): - explicit = cfg.project_dir / explicit - if explicit.is_file(): - return explicit - log.error("Kernel config not found: %s", explicit) - raise SystemExit(1) - ai = cfg.arch_info branch = _kernel_branch(cfg.kernel_version) defconfig = cfg.project_dir / "kernel.configs" / f"{branch}.{ai.arch}" @@ -178,87 +169,69 @@ def build_kernel(cfg: Config, src_dir: Path) -> str: make_env = {"ARCH": ai.kernel_arch} if ai.cross_compile: make_env["CROSS_COMPILE"] = ai.cross_compile + make_env["DPKG_DEB_COMPRESSOR_TYPE"] = "none" # don't waste time compressing .deb + + if cfg.kernel_clean: + log.info("Cleaning kernel...") + run( + ["make", f"-j{nproc}", "clean"], + env=make_env, + cwd=src_dir, + ) - log.info("Building kernel with %d jobs...", nproc) - run( - ["make", f"-j{nproc}", ai.image_target, "modules"], - env=make_env, - cwd=src_dir, - ) - - result = run( + built_kver = run( ["make", "-s", "kernelrelease"], env={"ARCH": ai.kernel_arch}, capture=True, cwd=src_dir, - ) - built_kver = result.stdout.strip() - log.info("Built kernel version: %s", built_kver) - return built_kver - - -def install_kernel(cfg: Config, src_dir: Path, built_kver: str) -> None: - """Install modules and vmlinuz into mkosi.output/kernel/{version}/{arch}/.""" - ai = cfg.arch_info - modules_root = cfg.modules_output + ).stdout.strip() - make_env = {"ARCH": ai.kernel_arch} - if ai.cross_compile: - make_env["CROSS_COMPILE"] = ai.cross_compile - - log.info("Installing modules...") + log.info("Building kernel '%s' with %d jobs...", built_kver, nproc) run( - ["make", f"INSTALL_MOD_PATH={modules_root}", "modules_install"], + ["make", f"-j{nproc}", "bindeb-pkg"], env=make_env, cwd=src_dir, ) - log.info("Stripping debug symbols from modules...") - strip_cmd = f"{ai.strip_prefix}strip" - for ko in modules_root.rglob("*.ko"): - run([strip_cmd, "--strip-unneeded", str(ko)], check=False) - - log.info("Compressing kernel modules with zstd...") - for ko in modules_root.rglob("*.ko"): - run(["zstd", "--rm", "-q", "-19", str(ko)], check=True) - - mod_base = modules_root / "lib" / "modules" / built_kver - (mod_base / "build").unlink(missing_ok=True) - (mod_base / "source").unlink(missing_ok=True) - - usr_moddir = ensure_dir(modules_root / "usr" / "lib" / "modules" / built_kver) - if mod_base.is_dir(): - for item in mod_base.iterdir(): - dest = usr_moddir / item.name - if dest.exists(): - if dest.is_dir(): - shutil.rmtree(dest) - else: - dest.unlink() - shutil.move(str(item), str(dest)) - shutil.rmtree(modules_root / "lib", ignore_errors=True) - - log.info("Running depmod for compressed modules...") - run( - ["depmod", "-a", "-b", str(modules_root / "usr"), built_kver], - check=True, + log.info("Built kernel version: %s", built_kver) + return built_kver + + +def obtain_target_artifact_path(cfg, ensure_parent: bool = False) -> Path: + if ensure_parent: + ensure_dir(cfg.kernel_output) + return ( + cfg.kernel_output / f"linux-image-{cfg.kernel_version}-captainos" + f"_{cfg.kernel_version}-1_" + f"{cfg.arch_info.kernel_arch}.deb" ) - kernel_image = src_dir / ai.kernel_image_path - vmlinuz_dir = ensure_dir(cfg.kernel_output) - for old in vmlinuz_dir.glob("vmlinuz-*"): - old.unlink(missing_ok=True) +def deploy_deb_to_output(cfg: Config, build_dir: Path, built_kver: str) -> None: + # Find the newest file in build_dir matching linux-image-{built_kver}*.deb + image_deb_package = build_dir.glob(f"linux-image-{built_kver}*.deb") + image_deb_package = sorted(image_deb_package, key=lambda p: p.stat().st_mtime, reverse=True) + if not image_deb_package: + log.error("No linux-image-*.deb package found in %s", build_dir) + raise SystemExit(1) + image_deb_package = image_deb_package[0] + log.info("Found built kernel package: %s", image_deb_package) + + # copy the deb package to the output directory + target_deb_output = obtain_target_artifact_path(cfg, ensure_parent=True) + log.debug("Copying built kernel package from %s to %s", image_deb_package, target_deb_output) + shutil.copy2(image_deb_package, target_deb_output) - shutil.copy2(kernel_image, vmlinuz_dir / f"vmlinuz-{built_kver}") + # Show dpkg info for the copied package + log.info("Kernel package info:") + run(["dpkg", "-I", str(target_deb_output)]) log.info("Kernel build complete:") - vmlinuz = vmlinuz_dir / f"vmlinuz-{built_kver}" - vmlinuz_size = vmlinuz.stat().st_size / (1024 * 1024) - log.info(" Image: %s (%.1fM)", vmlinuz, vmlinuz_size) - log.info(" Modules: %s/", usr_moddir) - log.info(" Version: %s", built_kver) - log.info(" Output: %s", cfg.kernel_output) + log.info( + " linux-image: %s (%.1fM)", + target_deb_output, + (target_deb_output.stat().st_size / (1024 * 1024)), + ) def build(cfg: Config) -> None: @@ -268,13 +241,10 @@ def build(cfg: Config) -> None: ensure_dir(cfg.kernel_output) log.info("Preparing kernel source...") - build_dir = Path(KERNEL_BUILD_BASE_PATH) - - if cfg.kernel_src and Path(cfg.kernel_src).is_dir(): - log.info("Using provided kernel source at %s", cfg.kernel_src) - src_dir = Path(cfg.kernel_src) - else: - src_dir = download_kernel(cfg.kernel_version, build_dir) + # We've a 2-deep because make bindeb-pkg outputs to parent directory of source proper + # Also because one can't share cache between architectures -- full rebuild everytime otherwise + build_dir = Path(KERNEL_BUILD_BASE_PATH) / f"build-{cfg.arch_info.kernel_arch}" + src_dir = download_kernel(cfg, build_dir) # this is one down from build_dir log.debug("Kernel source directory: %s", src_dir) @@ -286,4 +256,4 @@ def build(cfg: Config) -> None: log.debug("Built kernel version: %s", built_kver) log.info("Installing kernel...") - install_kernel(cfg, src_dir, built_kver) + deploy_deb_to_output(cfg, build_dir, built_kver) From d4b25f2a10cc88f58821e755fbbd3cb01b487faf Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 13 Apr 2026 22:52:35 +0200 Subject: [PATCH 83/93] gha: reintroduce kernel build jobs - using the Docker builder, so depend on that - abuse GHA caches and artifacts - the whole matrix will wait on kernel builds - but only a few will actually download and use the artifact Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 75 +++++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ea951b..999e075 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,22 +113,72 @@ jobs: - name: Build Dockerfile and push run: uv run captain builder --push + # ------------------------------------------------------------------- + # Download tools (containerd, runc, nerdctl, CNI plugins) + # ------------------------------------------------------------------- + build-captainos-kernel: + needs: [ build-dockerfile ] + runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} + strategy: + fail-fast: false + matrix: + arch: [ amd64, arm64 ] + env: + ARCH: ${{ matrix.arch }} + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Restore kernel cache + id: kernel-cache + uses: actions/cache/restore@v5 + with: + path: | + mkosi.output/kernel/${{ matrix.arch }} + key: kernel-${{ matrix.arch }}-${{ hashFiles('**/*.py', 'kernel.configs/*', 'Dockerfile', 'pyproject.toml') }} + # any "previous" cache can also be useful; we'll just churn cache misses more often, but that's better than a complete cache miss and rebuild every time + restore-keys: | + kernel-${{ matrix.arch }} + + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Build kernel + run: uv run captain --verbose kernel + + - name: Save kernel cache + if: github.ref == 'refs/heads/main' && steps.tools-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: | + mkosi.output/kernel/${{ matrix.arch }} + key: kernel-${{ matrix.arch }}-${{ hashFiles('**/*.py', 'kernel.configs/*', 'Dockerfile', 'pyproject.toml') }} + + - name: Upload kernel artifacts + uses: actions/upload-artifact@v6 + with: + name: kernel-${{ matrix.arch }} + path: | + mkosi.output/kernel/${{ matrix.arch }} + retention-days: 1 + + # ------------------------------------------------------------------- # Build initramfs via mkosi (depends on tools) # ------------------------------------------------------------------- - build-all: + build-captainos: runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} - needs: [ download-tools, build-dockerfile ] + needs: [ download-tools, build-dockerfile, build-captainos-kernel ] strategy: fail-fast: false matrix: include: - - { arch: amd64, output_arch: x86_64, iso: true, FLAVOR_ID: "trixie-full" } - - { arch: arm64, output_arch: aarch64, iso: true, FLAVOR_ID: "trixie-full" } - - { arch: arm64, output_arch: aarch64, iso: false, FLAVOR_ID: "trixie-rockchip64" } - - { arch: arm64, output_arch: aarch64, iso: false, FLAVOR_ID: "trixie-rockchip64-vendor" } - - { arch: arm64, output_arch: aarch64, iso: false, FLAVOR_ID: "trixie-meson64" } - - { arch: arm64, output_arch: aarch64, iso: false, FLAVOR_ID: "trixie-armbian-rpi" } + - { arch: amd64, output_arch: x86_64, captainos_kernel: true, iso: true, FLAVOR_ID: "trixie-full" } + - { arch: arm64, output_arch: aarch64, captainos_kernel: true, iso: true, FLAVOR_ID: "trixie-full" } + - { arch: arm64, output_arch: aarch64, captainos_kernel: false, iso: false, FLAVOR_ID: "trixie-rockchip64" } + - { arch: arm64, output_arch: aarch64, captainos_kernel: false, iso: false, FLAVOR_ID: "trixie-rockchip64-vendor" } + - { arch: arm64, output_arch: aarch64, captainos_kernel: false, iso: false, FLAVOR_ID: "trixie-meson64" } + - { arch: arm64, output_arch: aarch64, captainos_kernel: false, iso: false, FLAVOR_ID: "trixie-armbian-rpi" } env: ARCH: ${{ matrix.arch }} MKOSI_MODE: docker @@ -144,6 +194,13 @@ jobs: name: tools-${{ matrix.arch }} path: mkosi.output/tools/${{ matrix.arch }} + - name: Download kernel artifacts + uses: actions/download-artifact@v8 + if: ${{ matrix.captainos_kernel }} # only if matrix entry had captainos_kernel: true + with: + name: kernel-${{ matrix.arch }} + path: mkosi.output/kernel/${{ matrix.arch }} + - name: Restore tool binary permissions run: | # GitHub Actions artifact upload/download strips execute permissions. @@ -205,7 +262,7 @@ jobs: if: github.ref == 'refs/heads/main' name: "publish-combined" runs-on: ubuntu-latest - needs: [ build-all ] + needs: [ build-captainos ] env: ARCH: amd64 TARGET: combined From 907492a6e63811e2d09ad2be965091fd6224ae49 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 14 Apr 2026 00:42:15 +0200 Subject: [PATCH 84/93] flavor: introduce BaseFlavor::pre_mkosi_stage() - so flavors can assert their requirements are built/met Signed-off-by: Ricardo Pardini --- captain/cli/_build.py | 3 +++ captain/flavor.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/captain/cli/_build.py b/captain/cli/_build.py index b18dfe6..85a2d59 100644 --- a/captain/cli/_build.py +++ b/captain/cli/_build.py @@ -115,6 +115,9 @@ def build_cmd( # Tools stage. _build_tools_stage(cfg) + # Delegate to flavor, for eg obtaining it's own dependencies. + flavor.pre_mkosi_stage() + # Initramfs (mkosi) stage + artifact collection. _build_mkosi_stage(cfg, list(cfg.mkosi_args)) artifacts.collect_initramfs(cfg) diff --git a/captain/flavor.py b/captain/flavor.py index ea248df..b02c08a 100644 --- a/captain/flavor.py +++ b/captain/flavor.py @@ -124,6 +124,9 @@ def copy_static_files(self, project_dir): def has_iso(self) -> bool: return False + def pre_mkosi_stage(self): + pass + def list_available_flavors() -> list[str]: import importlib From 1836fc333fce540553e042d175d85be1b5168449 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 14 Apr 2026 00:43:17 +0200 Subject: [PATCH 85/93] mkosi: show Rich.Syntax'ed mkosi.conf before running mkosi - ... since we already paid the price of Pygments... Signed-off-by: Ricardo Pardini --- captain/cli/_stages.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/captain/cli/_stages.py b/captain/cli/_stages.py index ffac59f..7e1dd23 100644 --- a/captain/cli/_stages.py +++ b/captain/cli/_stages.py @@ -82,6 +82,22 @@ def _build_mkosi_stage(cfg: Config, extra_args: list[str]) -> None: mkosi_args = list(cfg.mkosi_args) + list(extra_args) + # Use a Rich Syntax and Rich Panel to display mkosi.conf before running it if debugging: + if log.isEnabledFor(logging.DEBUG): + from rich import print + from rich.panel import Panel + from rich.syntax import Syntax + + mkosi_conf_path = cfg.project_dir / "mkosi.conf" + if mkosi_conf_path.is_file(): + mkosi_conf_content = mkosi_conf_path.read_text() + syntax = Syntax(mkosi_conf_content, "ini", theme="monokai", line_numbers=True, + background_color="default") + panel = Panel(syntax, title="mkosi.conf", border_style="blue") + print(panel) + else: + log.debug("No mkosi.conf found at %s", mkosi_conf_path) + # --- native ------------------------------------------------------- if cfg.mkosi_mode == "native": missing = check_mkosi_dependencies() From 24a6d6f7424c9a45a198bfb2a5380aa231edaf14 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 14 Apr 2026 01:00:51 +0200 Subject: [PATCH 86/93] captain: allow building amd64 kernel on arm64 - specify a CROSS_COMPILE for x86_64 Signed-off-by: Ricardo Pardini --- captain/util.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/captain/util.py b/captain/util.py index 040af17..97d81a1 100644 --- a/captain/util.py +++ b/captain/util.py @@ -34,7 +34,6 @@ class ArchInfo: dl_arch: str # architecture name in download URLs mkosi_arch: str # mkosi --architecture value qemu_binary: str # QEMU system emulator binary - strip_prefix: str # prefix for strip command def get_arch_info(arch: str) -> ArchInfo: @@ -45,13 +44,12 @@ def get_arch_info(arch: str) -> ArchInfo: arch="amd64", output_arch="x86_64", kernel_arch="x86_64", - cross_compile="", + cross_compile="x86_64-linux-gnu-", image_target="bzImage", kernel_image_path="arch/x86/boot/bzImage", dl_arch="amd64", mkosi_arch="x86-64", qemu_binary="qemu-system-x86_64", - strip_prefix="", ) case "arm64" | "aarch64": return ArchInfo( @@ -64,7 +62,6 @@ def get_arch_info(arch: str) -> ArchInfo: dl_arch="arm64", mkosi_arch="arm64", qemu_binary="qemu-system-aarch64", - strip_prefix="aarch64-linux-gnu-", ) case _: log.error("Unsupported architecture: %s", arch) From e1126592a3fb66638a718a88f4cd202b64c6a944 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 14 Apr 2026 01:02:48 +0200 Subject: [PATCH 87/93] common_debian: introduce `package_directories()` for mkosi's `PackageDirectories=` - this allows flavors to add .deb's in directories as a local apt repo - .deb's can then be installed by name - mkosi handles the repo creation and usage directly (via apt-utils) Signed-off-by: Ricardo Pardini --- captain/flavors/common_debian/__init__.py | 3 +++ captain/flavors/common_debian/mkosi.conf.j2 | 3 +++ 2 files changed, 6 insertions(+) diff --git a/captain/flavors/common_debian/__init__.py b/captain/flavors/common_debian/__init__.py index 051db43..e81da68 100644 --- a/captain/flavors/common_debian/__init__.py +++ b/captain/flavors/common_debian/__init__.py @@ -41,3 +41,6 @@ def setup(self, cfg: Config, flavor_dir: Path) -> None: @abstractmethod def kernel_packages(self) -> set[str]: pass + + def package_directories(self) -> set[str]: + return set({}) diff --git a/captain/flavors/common_debian/mkosi.conf.j2 b/captain/flavors/common_debian/mkosi.conf.j2 index ce128aa..a957732 100644 --- a/captain/flavors/common_debian/mkosi.conf.j2 +++ b/captain/flavors/common_debian/mkosi.conf.j2 @@ -19,6 +19,9 @@ CleanPackageMetadata=yes Autologin=yes # Skips installing documentation/man pages WithDocs=no +# Adds .deb's found in those directories to a local apt repo that can be installed from. +PackageDirectories= + {{ ' '.join(flavor.package_directories()) }} # Do NOT set MakeInitrd=yes — that adds /etc/initrd-release which causes # systemd to enter initrd mode and try switch-root to a real rootfs. From b67d433c728f80fb69d146cb8e4bfd425e8fbc6e Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 14 Apr 2026 01:04:26 +0200 Subject: [PATCH 88/93] builder: Dockerfile: add cross-arch libssl-dev and native apt-utils - cross libssl-dev needed for `make bindeb-pkg` to work cross-arch - apt-utils needed for mkosi's PackageDirectories= Signed-off-by: Ricardo Pardini --- Dockerfile | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index bd561c6..ea532c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,7 @@ apt-get -o "Dpkg::Use-Pty=0" update apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends \ "grub-efi-${NATIVE_ARCH}-bin" \ "grub-efi-${FOREIGN_ARCH}-bin:${FOREIGN_ARCH}" \ + "libssl-dev:${FOREIGN_ARCH}" \ grub-common \ apt \ dpkg \ @@ -108,14 +109,21 @@ RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends \ # Buildah is pretty huge, gets its own layer. RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends buildah -# This is just to appease mkosi's later stages. -RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends python3 python3-pip python3-pefile +# FYI: Rust stuff for kernel build is about 1.3gb +# RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends rustc rust-src bindgen rustfmt rust-clippy -# Rust stuff for kernel build. -RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends rustc rust-src bindgen rustfmt rust-clippy +RUN <<-EXTRA_FRAG +# Extra packages for mkosi and kernel builds + +# This is just to appease mkosi's later stages, as it re-launches itself using the system Python +apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends python3 python3-pip python3-pefile # For kernel's bindeb-pkg and menuconfig -RUN apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends debhelper libdw-dev lsb-release libncurses-dev +apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends debhelper libdw-dev lsb-release libncurses-dev + +# For mkosi using Content.PackageDirectories (creating local repo) +apt-get -o "Dpkg::Use-Pty=0" install -y --no-install-recommends apt-utils +EXTRA_FRAG RUN <<-CONFIG_FRAG ## A few small config fragments to make life easier From dc185deff532a04afb3c52404c87168f9ed60a6b Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 14 Apr 2026 01:06:19 +0200 Subject: [PATCH 89/93] cli: expose `stages` (nee `_stages`) module - this skews package protection to allow flavors to reuse code - also expose function `build_kernel_stage()` Signed-off-by: Ricardo Pardini --- captain/cli/_build.py | 2 +- captain/cli/_iso.py | 2 +- captain/cli/_kernel.py | 4 ++-- captain/cli/_tools.py | 2 +- captain/cli/{_stages.py => stages.py} | 11 ++++++++--- 5 files changed, 13 insertions(+), 8 deletions(-) rename captain/cli/{_stages.py => stages.py} (96%) diff --git a/captain/cli/_build.py b/captain/cli/_build.py index 85a2d59..2a25194 100644 --- a/captain/cli/_build.py +++ b/captain/cli/_build.py @@ -9,7 +9,7 @@ import captain.flavor from captain import artifacts from captain.cli._main import CliContext, cli -from captain.cli._stages import ( +from captain.cli.stages import ( _build_iso_stage, _build_mkosi_stage, _build_tools_stage, diff --git a/captain/cli/_iso.py b/captain/cli/_iso.py index 2e72b44..631d240 100644 --- a/captain/cli/_iso.py +++ b/captain/cli/_iso.py @@ -9,7 +9,7 @@ import captain.flavor from captain import artifacts from captain.cli._main import CliContext, cli -from captain.cli._stages import ( +from captain.cli.stages import ( _build_iso_stage, ) diff --git a/captain/cli/_kernel.py b/captain/cli/_kernel.py index 0d386c0..ab83285 100644 --- a/captain/cli/_kernel.py +++ b/captain/cli/_kernel.py @@ -6,7 +6,7 @@ from captain import config from captain.cli._main import CliContext, cli -from captain.cli._stages import _build_kernel_stage +from captain.cli.stages import build_kernel_stage log = logging.getLogger(__name__) @@ -55,5 +55,5 @@ def kernel_cmd( build_kernel=True, ) - _build_kernel_stage(cfg) + build_kernel_stage(cfg) log.info("Kernel build stage complete!") diff --git a/captain/cli/_tools.py b/captain/cli/_tools.py index 16e0fd3..d5211f3 100644 --- a/captain/cli/_tools.py +++ b/captain/cli/_tools.py @@ -7,7 +7,7 @@ import click from captain.cli._main import CliContext, cli -from captain.cli._stages import _build_tools_stage +from captain.cli.stages import _build_tools_stage log = logging.getLogger(__name__) diff --git a/captain/cli/_stages.py b/captain/cli/stages.py similarity index 96% rename from captain/cli/_stages.py rename to captain/cli/stages.py index 7e1dd23..e317545 100644 --- a/captain/cli/_stages.py +++ b/captain/cli/stages.py @@ -11,7 +11,7 @@ log = logging.getLogger(__name__) -def _build_kernel_stage(cfg: Config) -> None: +def build_kernel_stage(cfg: Config) -> None: """Run the kernel build stage according to *cfg.kernel_mode*.""" log.warning("Building kernel stage in mode %s", cfg.kernel_mode) @@ -91,8 +91,13 @@ def _build_mkosi_stage(cfg: Config, extra_args: list[str]) -> None: mkosi_conf_path = cfg.project_dir / "mkosi.conf" if mkosi_conf_path.is_file(): mkosi_conf_content = mkosi_conf_path.read_text() - syntax = Syntax(mkosi_conf_content, "ini", theme="monokai", line_numbers=True, - background_color="default") + syntax = Syntax( + mkosi_conf_content, + "ini", + theme="monokai", + line_numbers=True, + background_color="default", + ) panel = Panel(syntax, title="mkosi.conf", border_style="blue") print(panel) else: From 0fcc7a2eb0ed3251000bc3bb7e6fcfbb6576c4c7 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 14 Apr 2026 01:12:15 +0200 Subject: [PATCH 90/93] flavors: introduce `trixie-slim` flavor, using captainos kernel - this one uses the captain-build-from-source kernel .deb - let's violate some package boundaries: - cli acrobatics: delegates to kernel build stage before initramfs build - impls package_directories() by calling kernel Signed-off-by: Ricardo Pardini --- captain/flavors/trixie_slim/__init__.py | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 captain/flavors/trixie_slim/__init__.py diff --git a/captain/flavors/trixie_slim/__init__.py b/captain/flavors/trixie_slim/__init__.py new file mode 100644 index 0000000..9af4a51 --- /dev/null +++ b/captain/flavors/trixie_slim/__init__.py @@ -0,0 +1,32 @@ +import logging +from dataclasses import dataclass + +from captain.cli import stages +from captain.flavor import BaseFlavor +from captain.flavors.common_acpi import TrixieACPIFlavor +from captain.kernel import obtain_target_artifact_path + +log: logging.Logger = logging.getLogger(__name__) + + +def create_flavor() -> BaseFlavor: + return TrixieSlimFlavor() + + +@dataclass +class TrixieSlimFlavor(TrixieACPIFlavor): + id = "trixie-slim" + name = "Trixie Slim" + description = "Debian Trixie based with captainos slim kernel" + supported_architectures = frozenset(["amd64", "arm64"]) + + def pre_mkosi_stage(self): + log.debug("Flavor delegating to build_kernel_stage to ensure kernel .deb is present") + # call the kernel build stage, to ensure kernel .deb is in mkosi.output/kernel/ + stages.build_kernel_stage(self.cfg) + + def kernel_packages(self) -> set[str]: + return {f"linux-image-{self.cfg.kernel_version}-captainos"} + + def package_directories(self) -> set[str]: + return {str(obtain_target_artifact_path(self.cfg).parent.relative_to(self.cfg.project_dir))} From 8d070a655f9aeb85cc64c0698c2a413050dfe38c Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Mon, 13 Apr 2026 23:44:27 +0200 Subject: [PATCH 91/93] gha: split matrix to add kernel job dependency for trixie-slim jobs - add gha builds for `trixie-slim` flavors (using captainos-kernel) - we've two kinds of build-captainos jobs: - ones that depend on kernel-build (trixie-slim) - ones that don't (all the others) - use YAML anchor trick to avoid duplicating 95% of code (envs: and steps: are defined only once) - matrix and needs (dependencies) are the only difference - artifact download is common but conditional on matrix values Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 999e075..d5b4d01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,28 +163,21 @@ jobs: retention-days: 1 - # ------------------------------------------------------------------- - # Build initramfs via mkosi (depends on tools) - # ------------------------------------------------------------------- - build-captainos: + build-captainos-with-kernel: # only for flavors that require the custom CaptainOS kernel; otherwise, just build-captainos runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} needs: [ download-tools, build-dockerfile, build-captainos-kernel ] strategy: fail-fast: false matrix: include: - - { arch: amd64, output_arch: x86_64, captainos_kernel: true, iso: true, FLAVOR_ID: "trixie-full" } - - { arch: arm64, output_arch: aarch64, captainos_kernel: true, iso: true, FLAVOR_ID: "trixie-full" } - - { arch: arm64, output_arch: aarch64, captainos_kernel: false, iso: false, FLAVOR_ID: "trixie-rockchip64" } - - { arch: arm64, output_arch: aarch64, captainos_kernel: false, iso: false, FLAVOR_ID: "trixie-rockchip64-vendor" } - - { arch: arm64, output_arch: aarch64, captainos_kernel: false, iso: false, FLAVOR_ID: "trixie-meson64" } - - { arch: arm64, output_arch: aarch64, captainos_kernel: false, iso: false, FLAVOR_ID: "trixie-armbian-rpi" } - env: + - { arch: amd64, output_arch: x86_64, captainos_kernel: true, iso: true, FLAVOR_ID: "trixie-slim" } + - { arch: arm64, output_arch: aarch64, captainos_kernel: true, iso: true, FLAVOR_ID: "trixie-slim" } + env: &build_env ARCH: ${{ matrix.arch }} MKOSI_MODE: docker ISO_MODE: docker FLAVOR_ID: ${{ matrix.FLAVOR_ID }} - steps: + steps: &build_steps - name: Checkout code uses: actions/checkout@v6 @@ -194,7 +187,7 @@ jobs: name: tools-${{ matrix.arch }} path: mkosi.output/tools/${{ matrix.arch }} - - name: Download kernel artifacts + - name: Download kernel artifacts == ${{ matrix.captainos_kernel }} uses: actions/download-artifact@v8 if: ${{ matrix.captainos_kernel }} # only if matrix entry had captainos_kernel: true with: @@ -255,6 +248,22 @@ jobs: TARGET: ${{ matrix.arch }} run: uv run captain release-publish + build-captainos: # those use regular Debian apt repos for kernels + steps: *build_steps + env: *build_env + runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} + needs: [ download-tools, build-dockerfile ] + strategy: + fail-fast: false + matrix: + include: + - { arch: amd64, output_arch: x86_64, captainos_kernel: false, iso: true, FLAVOR_ID: "trixie-full" } + - { arch: arm64, output_arch: aarch64, captainos_kernel: false, iso: true, FLAVOR_ID: "trixie-full" } + - { arch: arm64, output_arch: aarch64, captainos_kernel: false, iso: false, FLAVOR_ID: "trixie-rockchip64" } + - { arch: arm64, output_arch: aarch64, captainos_kernel: false, iso: false, FLAVOR_ID: "trixie-rockchip64-vendor" } + - { arch: arm64, output_arch: aarch64, captainos_kernel: false, iso: false, FLAVOR_ID: "trixie-meson64" } + - { arch: arm64, output_arch: aarch64, captainos_kernel: false, iso: false, FLAVOR_ID: "trixie-armbian-rpi" } + # ------------------------------------------------------------------- # Publish combined multi-arch image (reuses per-arch registry blobs) # ------------------------------------------------------------------- From b130d30e2c150d74a9b9ffbf0b6cce32bea4c694 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 14 Apr 2026 01:56:38 +0200 Subject: [PATCH 92/93] gha: make `trixie-slim` the default flavor - so it is the one included in the combined images - this does not change the CLI defaults, ofc Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5b4d01..ba97975 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ permissions: packages: write env: - DEFAULT_FLAVOR_ID: "trixie-full" + DEFAULT_FLAVOR_ID: "trixie-slim" FORCE_COLOR: "1" jobs: From f2f6080f13e264f2df0887f6870d245158f51f79 Mon Sep 17 00:00:00 2001 From: Ricardo Pardini Date: Tue, 14 Apr 2026 02:02:24 +0200 Subject: [PATCH 93/93] gha: don't push OCI images for pull request workflows - (Docker) builder will end up being built multiple times for PR workflows, but such is life - it's also too huge for GHA artifact or GHA cache - don't push any captainos artifacts either (simple or combined) - also: only push those when building `main` branch Signed-off-by: Ricardo Pardini --- .github/workflows/ci.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba97975..e0f1c5e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,8 +110,15 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Build Dockerfile + run: uv run captain builder + # only if a PR, since we don't want to push images for PRs from forks + if: github.event_name == 'pull_request' + - name: Build Dockerfile and push run: uv run captain builder --push + # only if not a PR, since we don't want to push images for PRs from forks (no perms to push) + if: github.event_name != 'pull_request' # ------------------------------------------------------------------- # Download tools (containerd, runc, nerdctl, CNI plugins) @@ -243,7 +250,8 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Publish artifacts to GHCR - if: github.ref == 'refs/heads/main' + # only pushes to main, and not PR workflows, should try to push to OCI registry + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' env: TARGET: ${{ matrix.arch }} run: uv run captain release-publish @@ -319,6 +327,8 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Publish combined image to GHCR + # only pushes to main, and not PR workflows, should try to push to OCI registry + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' env: FLAVOR_ID: "${{ env.DEFAULT_FLAVOR_ID }}" run: uv run captain release-publish