diff --git a/Makefile b/Makefile index 751564bf7..c77f2d5cb 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,9 @@ all-test: cargo build --release --all-features install: - install -D -t $(DESTDIR)$(prefix)/bin target/release/bootc - install -D -t $(DESTDIR)$(prefix)/lib/bootc/install lib/src/install/*.toml + install -D -m 0755 -t $(DESTDIR)$(prefix)/bin target/release/bootc + install -D -m 0644 -t $(DESTDIR)$(prefix)/lib/bootc/install lib/src/install/*.toml + if test -d man; then install -D -m 0644 -t $(DESTDIR)$(prefix)/share/man/man8 man/*.8; fi bin-archive: all $(MAKE) install DESTDIR=tmp-install && tar --zstd -C tmp-install -cf target/bootc.tar.zst . && rm tmp-install -rf diff --git a/contrib/packaging/bootc.spec b/contrib/packaging/bootc.spec index 1adbd0f42..63c717454 100644 --- a/contrib/packaging/bootc.spec +++ b/contrib/packaging/bootc.spec @@ -7,8 +7,8 @@ Summary: Boot containers License: ASL 2.0 URL: https://github.com/containers/bootc -Source0: https://github.com/containers/bootc/releases/download/v%{version}/bootc-%{version}.tar.zstd -Source1: https://github.com/containers/bootc/releases/download/v%{version}/bootc-%{version}-vendor.tar.zstd +Source0: https://github.com/containers/bootc/releases/download/v%{version}/bootc-%{version}.tar.zst +Source1: https://github.com/containers/bootc/releases/download/v%{version}/bootc-%{version}-vendor.tar.zst BuildRequires: make BuildRequires: openssl-devel @@ -29,6 +29,7 @@ BuildRequires: systemd-devel %doc README.md %{_bindir}/bootc %{_prefix}/lib/bootc +%{_mandir}/man8/bootc* %prep %autosetup -p1 -Sgit diff --git a/lib/src/cli.rs b/lib/src/cli.rs index 91708f2a7..f60d21220 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -111,7 +111,16 @@ pub(crate) enum TestingOpts { }, } -/// Deploy and upgrade via bootable container images. +/// Deploy and transactionally in-place with bootable container images. +/// +/// The `bootc` project currently uses ostree-containers as a backend +/// to support a model of bootable container images. Once installed, +/// whether directly via `bootc install` (executed as part of a container) +/// or via another mechanism such as an OS installer tool, further +/// updates can be pulled via e.g. `bootc upgrade`. +/// +/// Changes in `/etc` and `/var` persist. +/// #[derive(Debug, Parser)] #[clap(name = "bootc")] #[clap(rename_all = "kebab-case")] diff --git a/xtask/src/xtask.rs b/xtask/src/xtask.rs index 9fa41cf01..43e6b407b 100644 --- a/xtask/src/xtask.rs +++ b/xtask/src/xtask.rs @@ -20,6 +20,7 @@ fn main() { #[allow(clippy::type_complexity)] const TASKS: &[(&str, fn(&Shell) -> Result<()>)] = &[ ("vendor", vendor), + ("manpages", manpages), ("package", package), ("package-srpm", package_srpm), ("custom-lints", custom_lints), @@ -57,15 +58,31 @@ fn gitrev_to_version(v: &str) -> String { #[context("Finding gitrev")] fn gitrev(sh: &Shell) -> Result { - if let Ok(rev) = cmd!(sh, "git describe --tags").ignore_stderr().read() { + if let Ok(rev) = cmd!(sh, "git describe --tags --exact-match") + .ignore_stderr() + .read() + { Ok(gitrev_to_version(&rev)) } else { let mut desc = cmd!(sh, "git describe --tags --always").read()?; desc.insert_str(0, "0."); - Ok(desc) + let timestamp = git_timestamp(sh)?; + // We always inject the timestamp first to ensure that newer is better. + Ok(format!("{timestamp}.{desc}")) } } +#[context("Manpages")] +fn manpages(sh: &Shell) -> Result<()> { + sh.create_dir("target/man")?; + cmd!( + sh, + "cargo run --features=docgen -- man --directory target/man" + ) + .run()?; + Ok(()) +} + /// Return a string formatted version of the git commit timestamp, up to the minute /// but not second because, well, we're not going to build more than once a second. #[context("Finding git timestamp")] @@ -82,14 +99,28 @@ struct Package { srcpath: Utf8PathBuf, } +/// Return the timestamp of the latest git commit in seconds since the Unix epoch. +fn git_source_date_epoch(dir: &Utf8Path) -> Result { + let o = Command::new("git") + .args(["log", "-1", "--pretty=%ct"]) + .current_dir(dir) + .output()?; + if !o.status.success() { + anyhow::bail!("git exited with an error: {:?}", o); + } + let buf = String::from_utf8(o.stdout).context("Failed to parse git log output")?; + let r = buf.trim().parse()?; + Ok(r) +} + #[context("Packaging")] fn impl_package(sh: &Shell) -> Result { + let source_date_epoch = git_source_date_epoch(".".into())?; + manpages(sh)?; let v = gitrev(sh)?; - let timestamp = git_timestamp(sh)?; - // We always inject the timestamp first to ensure that newer is better. - let v = format!("{timestamp}.{v}"); + let namev = format!("{NAME}-{v}"); - let p = Utf8Path::new("target").join(format!("{namev}.tar.zstd")); + let p = Utf8Path::new("target").join(format!("{namev}.tar")); let o = File::create(&p)?; let prefix = format!("{namev}/"); let st = Command::new("git") @@ -105,9 +136,29 @@ fn impl_package(sh: &Shell) -> Result { if !st.success() { anyhow::bail!("Failed to run {st:?}"); } + let st = Command::new("tar") + .args([ + "-r", + "-C", + "target", + "--sort=name", + "--owner=0", + "--group=0", + "--numeric-owner", + "--pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime", + ]) + .arg(format!("--transform=s,^,{prefix},")) + .arg(format!("--mtime=@{source_date_epoch}")) + .args(["-f", p.as_str(), "man"]) + .status() + .context("Failed to execute tar")?; + if !st.success() { + anyhow::bail!("Failed to run {st:?}"); + } + cmd!(sh, "zstd -f {p}").run()?; Ok(Package { version: v, - srcpath: p, + srcpath: format!("{p}.zst").into(), }) } @@ -118,6 +169,16 @@ fn package(sh: &Shell) -> Result<()> { } fn impl_srpm(sh: &Shell) -> Result { + { + let _g = sh.push_dir("target"); + for name in sh.read_dir(".")? { + if let Some(name) = name.to_str() { + if name.ends_with(".src.rpm") { + sh.remove_path(name)?; + } + } + } + } let pkg = impl_package(sh)?; vendor(sh)?; let td = tempfile::tempdir_in("target").context("Allocating tmpdir")?; @@ -126,7 +187,7 @@ fn impl_srpm(sh: &Shell) -> Result { let srcpath = td.join(pkg.srcpath.file_name().unwrap()); std::fs::rename(pkg.srcpath, srcpath)?; let v = pkg.version; - let vendorpath = td.join(format!("{NAME}-{v}-vendor.tar.zstd")); + let vendorpath = td.join(format!("{NAME}-{v}-vendor.tar.zst")); std::fs::rename(VENDORPATH, vendorpath)?; { let specin = File::open(format!("contrib/packaging/{NAME}.spec"))