diff --git a/README.md b/README.md index 6c3b687..2029655 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,13 @@ Notes: `-b ` to `build-img`. The Makefile's `base-image` target always adds it; to use `make` without it, override both variables: `make base-image pkg_bin= build_img_args="-a x86_64"`. +- Extra packages can be added on top of the default ones (see `BASE_PACKAGES` + in `src/lib.rs`) with `-P`/`--add-package`, which can be repeated. Names are + [nixpkgs](https://search.nixos.org/packages) attribute names, e.g. + `cargo run --bin build-img -- -a x86_64 -P tshark -P python3`. With `make`, + append them to `build_img_args`: + `make base-image build_img_args="-a x86_64 -b -P tshark"`. + Inside `dive`, `pkg list` shows them as built-in packages. - `--features embedded_image` lets `dive` install the base image from its own binary. Without it, `dive` builds the base image on first run, which requires network access. diff --git a/src/bin/build-img.rs b/src/bin/build-img.rs index 21a4bbc..05b9342 100644 --- a/src/bin/build-img.rs +++ b/src/bin/build-img.rs @@ -16,14 +16,14 @@ struct Args { #[arg(short, long, env)] persistent_base_dir: Option, - /// Alternative flake directory - #[arg(short, long, env)] - flake_dir: Option, - /// Add binary into base image #[arg(short = 'b', long = "add-binary")] binaries: Vec, + /// Add package into base image (on top of the default packages) + #[arg(short = 'P', long = "add-package")] + packages: Vec, + /// Architecture #[arg(short, long, env)] arch: Option, @@ -111,15 +111,12 @@ fn main() -> Result<()> { let base_dir = BaseDir::new(args.persistent_base_dir)?; let mut base_builder = BaseImageBuilder::new(base_dir.path()); base_builder.binaries(args.binaries); + base_builder.extra_packages(args.packages); if !args.unpackaged { base_builder.package(args.output, !args.uncompressed); } - if let Some(flake_dir) = args.flake_dir { - base_builder.flake_dir(flake_dir); - } - if args.shell_exec { base_builder.shell_exec(true); } diff --git a/src/image_builder.rs b/src/image_builder.rs index 978aae0..b886f68 100644 --- a/src/image_builder.rs +++ b/src/image_builder.rs @@ -43,8 +43,8 @@ static STATIC_FILES: &[(&str, &str)] = pub struct BaseImageBuilder { nix_dir: PathBuf, - flake_dir: Option, binaries: Vec, + extra_packages: Vec, shell_exec: bool, package_output: Option, compress: bool, @@ -61,27 +61,37 @@ impl BaseImageBuilder { { BaseImageBuilder { nix_dir: nix_dir.as_ref().to_owned(), - flake_dir: None, binaries: Vec::new(), + extra_packages: Vec::new(), shell_exec: false, package_output: None, compress: false, } } - pub fn flake_dir

(&mut self, flake_dir: P) -> &mut Self - where - P: AsRef, - { - self.flake_dir.replace(flake_dir.as_ref().to_owned()); + pub fn binaries(&mut self, binaries: Vec) -> &mut Self { + self.binaries = binaries; self } - pub fn binaries(&mut self, binaries: Vec) -> &mut Self { - self.binaries = binaries; + /// Packages added to the default package list + pub fn extra_packages(&mut self, packages: Vec) -> &mut Self { + self.extra_packages = packages; self } + /// Default and extra packages, sorted and deduplicated + fn packages(&self) -> Vec<&str> { + let mut packages: Vec<&str> = crate::BASE_PACKAGES + .iter() + .copied() + .chain(self.extra_packages.iter().map(String::as_str)) + .collect(); + packages.sort(); + packages.dedup(); + packages + } + pub fn shell_exec(&mut self, shell_exec: bool) -> &mut Self { self.shell_exec = shell_exec; self @@ -136,6 +146,14 @@ impl BaseImageBuilder { return Self::POST_PROCESS_FAILED; } + // package list, listed as built-in by `pkg` + let packages = self.packages().join("\n") + "\n"; + hasher.update(&packages); + if let Err(err) = fs::write(crate::BASE_PACKAGES_FILE, packages) { + log::error!("failed to write package list: {err}"); + return Self::POST_PROCESS_FAILED; + } + if let Err(err) = self.binaries.iter().try_for_each(|p| { // TODO: hasher.update(content); fs::copy( @@ -217,15 +235,12 @@ impl BaseImageBuilder { // build base flake let flake_dir = Path::new(crate::CACHE_HOME).join("base-flake"); - let store_path = match &self.flake_dir { - None => nixos::build_flake_from_package_list( - "debug-shell", - "A debug shell", - crate::BASE_PACKAGES, - &flake_dir, - )?, - Some(flake_dir) => nixos::build_flake(flake_dir)?, - }; + let store_path = nixos::build_flake_from_package_list( + "debug-shell", + "A debug shell", + &self.packages(), + &flake_dir, + )?; symlink_base(&store_path)?; log::debug!("built base image"); @@ -280,6 +295,7 @@ impl BaseImageBuilder { ".bin", ".base", ".base.paths", + ".base.packages", ".base.sha256", ".base.reginfo", "etc", diff --git a/src/lib.rs b/src/lib.rs index a1fb833..188e9f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ pub(crate) const CONFIG_HOME: &str = "/nix/.config"; pub(crate) const USER_ENV_DIR: &str = "/nix/.env"; pub(crate) const BASE_DIR: &str = "/nix/.base"; +pub(crate) const BASE_PACKAGES_FILE: &str = "/nix/.base.packages"; pub(crate) const ETC_DIR: &str = "/nix/etc"; pub(crate) const SSL_CERTS: &str = "/nix/.base/etc/ssl/certs/ca-bundle.crt"; diff --git a/src/nixpkgs.rs b/src/nixpkgs.rs index 1208036..ad87ada 100644 --- a/src/nixpkgs.rs +++ b/src/nixpkgs.rs @@ -240,16 +240,21 @@ pub fn installed_packages() -> Result { } } -pub fn builtin_packages() -> Packages { - crate::BASE_PACKAGES - .iter() - .map(|s| Package::new_builtin(s)) - .collect() +pub fn builtin_packages() -> Result { + let content = match fs::read_to_string(crate::BASE_PACKAGES_FILE) { + Ok(content) => content, + // base images built before the package list was recorded + Err(err) if err.kind() == ErrorKind::NotFound => { + crate::BASE_PACKAGES.join("\n") + } + Err(err) => return Err(err.into()), + }; + Ok(content.lines().map(Package::new_builtin).collect()) } pub fn all_packages_sorted() -> Result { let installed_packages = installed_packages()?; - let builtin_packages = builtin_packages(); + let builtin_packages = builtin_packages()?; Ok(BinaryHeap::from_iter( builtin_packages.into_iter().chain(installed_packages),