#!/usr/bin/env nu # install/assemble-bundle.nu — assemble a self-contained ontoref distribution # bundle (ADR-038) from an ALREADY cross-compiled binary plus the data layer. # # This NEVER recompiles. It consumes target//release/ontoref-daemon and # packages it with the bootstrapper, the CLI wrapper, a target-arch nickel, the # data layer (reflection/ ontology/ domains/ templates/ public/ install/) and a # config skeleton into dist/ontoref--.tar.gz (+ .sha256). The curl|sh # installer (install/install.sh) extracts this and mirrors install.nu's layout. # # nu install/assemble-bundle.nu --target x86_64-unknown-linux-musl # nu install/assemble-bundle.nu smoke --target x86_64-unknown-linux-musl # # Config is taken ONLY from install/resources/ — never from the (possibly # corrupt) per-project .ontoref/config.ncl. # Map a cargo target triple to the bundle's os/arch suffix. def target-to-osarch [target: string]: nothing -> record { match $target { "x86_64-unknown-linux-musl" => { os: "linux", arch: "amd64" } "aarch64-unknown-linux-musl" => { os: "linux", arch: "arm64" } "x86_64-unknown-linux-gnu" => { os: "linux", arch: "amd64" } "aarch64-unknown-linux-gnu" => { os: "linux", arch: "arm64" } _ => { error make { msg: $"unsupported target triple: ($target)" } } } } # Resolve the cargo target directory (honours .cargo/config.toml target-dir). def cargo-target-dir [root: string]: nothing -> string { let meta = (do { cd $root; ^cargo metadata --format-version 1 --no-deps } | complete) if $meta.exit_code == 0 { $meta.stdout | from json | get target_directory } else { $"($root)/target" } } # Recursively copy every file under src into dest, preserving the relative tree. def copy-tree [src: string, dest: string]: nothing -> int { if not ($src | path exists) { return 0 } mkdir $dest let files = (glob $"($src)/**/*" | where { |f| ($f | path type) == "file" }) for src_file in $files { let rel = ($src_file | str replace $"($src)/" "") let dest_file = $"($dest)/($rel)" let dest_parent = ($dest_file | path dirname) mkdir $dest_parent cp $src_file $dest_file } $files | length } # Repo-side reflection source: .ontoref/reflection (ADR-032) or legacy reflection/. def reflection-src [root: string]: nothing -> string { if ($"($root)/.ontoref/reflection" | path exists) { $"($root)/.ontoref/reflection" } else { $"($root)/reflection" } } # Locate a target-arch nickel binary. Explicit --nickel wins; otherwise look for # a pre-fetched per-arch binary under dist/nickel//nickel; otherwise fall # back to the host nickel with a loud warning (arch may not match the target). def resolve-nickel [root: string, target: string, explicit: string]: nothing -> string { if ($explicit | is-not-empty) { if not ($explicit | path exists) { error make { msg: $"--nickel path does not exist: ($explicit)" } } return $explicit } let prefetched = $"($root)/dist/nickel/($target)/nickel" if ($prefetched | path exists) { return $prefetched } let host = (which nickel) if ($host | is-empty) { error make { msg: "nickel not found — pass --nickel or place it at dist/nickel//nickel" } } let host_path = ($host | first | get path) print $" (ansi yellow)warn(ansi reset) bundling host nickel ($host_path) — arch may differ from target ($target); pass --nickel for cross bundles" $host_path } # Assemble the bundle. Returns the tarball path. def assemble [target: string, root: string, bin_dir: string, version: string, nickel: string, out_dir: string]: nothing -> string { let osarch = (target-to-osarch $target) let resolved_bin_dir = if ($bin_dir | is-not-empty) { $bin_dir } else { $"((cargo-target-dir $root))/($target)/release" } let daemon_src = $"($resolved_bin_dir)/ontoref-daemon" if not ($daemon_src | path exists) { error make { msg: $"binary not found: ($daemon_src)\n run: cross build --target ($target) --release" } } let tier_src = $"($resolved_bin_dir)/ontoref-tier" let nickel_bin = (resolve-nickel $root $target $nickel) let stage = $"($out_dir)/.stage-($osarch.os)-($osarch.arch)" if ($stage | path exists) { rm -rf $stage } mkdir $"($stage)/bin" mkdir $"($stage)/data" mkdir $"($stage)/config" # ── bin/ ─────────────────────────────────────────────────────────────────── cp $daemon_src $"($stage)/bin/ontoref-daemon.bin" if ($tier_src | path exists) { cp $tier_src $"($stage)/bin/ontoref-tier" } cp $"($root)/install/ontoref-daemon-boot" $"($stage)/bin/ontoref-daemon-boot" cp $"($root)/install/ontoref-global" $"($stage)/bin/ontoref-global" cp $nickel_bin $"($stage)/bin/nickel" for f in (glob $"($stage)/bin/*" | where { |p| ($p | path type) == "file" }) { chmod +x $f } # ── data/ — reflection, protocol ontology, domains, templates, UI, install ── let refl_n = (copy-tree (reflection-src $root) $"($stage)/data/reflection") let ont_n = (copy-tree $"($root)/ontology" $"($stage)/data/ontology") let dom_n = (copy-tree $"($root)/domains" $"($stage)/data/domains") let tpl_n = (copy-tree $"($root)/templates" $"($stage)/data/templates") let ui_n = (copy-tree $"($root)/crates/ontoref-daemon/templates" $"($stage)/data/templates") let pub_n = (copy-tree $"($root)/crates/ontoref-daemon/public" $"($stage)/data/public") mkdir $"($stage)/data/install" for f in (glob $"($root)/install/*.nu") { cp $f $"($stage)/data/install/(($f | path basename))" } # ── config/ — skeleton ONLY from install/resources (never .ontoref/config.ncl) ── let res = $"($root)/install/resources" for name in ["config.ncl", "projects.ncl", "remote-projects.ncl", "streams.json"] { let src = $"($res)/($name)" if ($src | path exists) { cp $src $"($stage)/config/($name)" } } copy-tree $"($res)/schemas" $"($stage)/config/schemas" | ignore # ── BUNDLE_INFO.json — version/target provenance for the installer ────────── let info = { schema: "ontoref-bundle/1", version: $version, target: $target, os: $osarch.os, arch: $osarch.arch, components: { reflection: $refl_n, ontology: $ont_n, domains: $dom_n, templates: ($tpl_n + $ui_n), public: $pub_n }, } $info | to json | save --force $"($stage)/BUNDLE_INFO.json" # ── tar.gz + sha256 ────────────────────────────────────────────────────────── mkdir $out_dir let tarball = $"($out_dir)/ontoref-($osarch.os)-($osarch.arch).tar.gz" let tar_res = (do { ^tar -czf $tarball -C $stage "." } | complete) if $tar_res.exit_code != 0 { error make { msg: $"tar failed: ($tar_res.stderr)" } } let digest = (open --raw $tarball | hash sha256) $"($digest) (($tarball | path basename))\n" | save --force $"($tarball).sha256" rm -rf $stage print $"✓ bundle ($tarball)" print $" sha256 ($digest)" print $" layout bin/{ontoref-daemon.bin,ontoref-daemon-boot,ontoref-global,nickel} data/ config/" $tarball } # Assemble a bundle for one target triple. def main [ --target: string = "x86_64-unknown-linux-musl" # cargo target triple --bin-dir: string = "" # override binary dir (default: //release) --root: string = "" # repo root (default: PWD) --version: string = "" # version stamp (default: workspace.package.version) --nickel: string = "" # target-arch nickel binary to bundle --out-dir: string = "" # output dir (default: /dist) ]: nothing -> nothing { let root = if ($root | is-empty) { $env.PWD } else { $root } let out = if ($out_dir | is-empty) { $"($root)/dist" } else { $out_dir } let ver = if ($version | is-not-empty) { $version } else { let cargo = (open $"($root)/Cargo.toml") $cargo.workspace.package.version } assemble $target $root $bin_dir $ver $nickel $out | ignore } # Smoke gate (ADR-038): assemble the native bundle, extract it, run the daemon # binary, and confirm the expected layout. Fails the release pipeline BEFORE any # distribution step. Native-arch only — an arm64 binary cannot run on amd64. def "main smoke" [ --target: string = "x86_64-unknown-linux-musl" --root: string = "" ]: nothing -> nothing { let root = if ($root | is-empty) { $env.PWD } else { $root } let osarch = (target-to-osarch $target) let out = $"($root)/dist" let cargo = (open $"($root)/Cargo.toml") let ver = $cargo.workspace.package.version let tarball = (assemble $target $root "" $ver "" $out) let probe = $"($out)/.smoke-($osarch.os)-($osarch.arch)" if ($probe | path exists) { rm -rf $probe } mkdir $probe let untar = (do { ^tar -xzf $tarball -C $probe } | complete) if $untar.exit_code != 0 { error make { msg: $"smoke: extraction failed: ($untar.stderr)" } } for entry in ["bin/ontoref-daemon.bin", "bin/ontoref-daemon-boot", "bin/ontoref-global", "bin/nickel", "BUNDLE_INFO.json"] { if not ($"($probe)/($entry)" | path exists) { error make { msg: $"smoke: bundle missing ($entry)" } } } let ver_res = (do { ^$"($probe)/bin/ontoref-daemon.bin" --version } | complete) if $ver_res.exit_code != 0 { error make { msg: $"smoke: daemon binary failed to run --version:\n($ver_res.stderr)" } } print $"✓ smoke daemon runs: (($ver_res.stdout | str trim))" rm -rf $probe }