Merge nucleic/olive-ember-seal-q7vk into dev

This commit is contained in:
2026-07-18 15:14:54 -07:00
commit d919c693dd
34 changed files with 624 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
dist/
repo/keys/
+63
View File
@@ -0,0 +1,63 @@
# os/ — the narOS build tree
This directory builds **narOS** (the Nucleic Agent Runtime OS) per
[docs/NAROS.md](../docs/NAROS.md): a Debian-trixie-derived rootfs assembled from scratch
with `mmdebstrap` against a pinned `snapshot.debian.org` date, plus a Nucleic apt package
layer (nash, naros-init, tier meta-packages, …). CI (`.github/workflows/naros.yml`)
publishes OCI images to GHCR (`naros-base`, later `naros-agent`/`naros-runner`) and the
signed apt repository to Cloudflare R2.
## Layout
```
VERSION # the narOS release version (26.MM[.p]) — single source of truth
SNAPSHOT # pinned snapshot.debian.org timestamp for this release
mkimage/
build-rootfs.sh # mmdebstrap driver: <tier> <arch> → rootfs tar (needs root/unshare)
hooks/00-identity.sh # writes /etc/os-release (ID=naros) + /etc/naros/{manifest.json,…}
profiles/<tier>.pkgs # Debian package list per tier
profiles/<tier>.naros-pkgs # Nucleic packages per tier (installed when a pool is supplied)
packages/
build-all.sh # builds every package below into dist/pool/ with dpkg-deb
<name>/control # control template (@VERSION@/@ARCH@ substituted)
<name>/files/ # static payload, copied verbatim
<name>/stage.sh # optional dynamic staging (e.g. install a prebuilt binary)
<name>/postinst,prerm # optional maintainer scripts
repo/
publish.sh # dist/pool → apt tree (dists/<channel>/…), signs when a key is present
r2-sync.sh # pushes the apt tree to Cloudflare R2 (CI; needs credentials)
dist/ # build output (gitignored): bin/, pool/, repo/, rootfs tars
```
## Building
Packages (any Debian-family host, no root needed):
```sh
os/packages/build-all.sh --arch arm64,amd64 # expects prebuilt nash/naros-init in
# os/dist/bin/<name>-<arch> (see below)
```
Prebuilt binaries: `nash` and `naros-init` are Rust (musl-static, built from `shell/`);
CI drops them at `os/dist/bin/nash-{arm64,amd64}` and `os/dist/bin/naros-init-{arm64,amd64}`.
Locally: `cargo build --release -p nash -p naros-init` (with the musl targets) and copy.
Packages whose binary is missing are skipped with a warning, so pure-metadata iteration
works without a Rust toolchain.
Rootfs (needs mmdebstrap; root or unshare-capable user — CI, or a root container):
```sh
os/mkimage/build-rootfs.sh base arm64 --pool os/dist/pool
docker import os/dist/naros-base-<ver>-arm64.tar naros-base:test
docker run --rm naros-base:test sh -c '. /etc/os-release && echo "$ID $VERSION_ID"'
```
Without `--pool`, the build produces a plain identity-only base (no Nucleic packages) —
useful for validating the mmdebstrap/snapshot/identity plumbing in isolation.
## Versioning
`VERSION` + `SNAPSHOT` define a release (NAROS.md §8). Channels: `edge` (weekly CI,
fresh snapshot) and `stable` (promoted deliberately; what `ProjectSandbox.defaultImage`
pins). Nucleic packages carry their own versions in `packages/<name>/VERSION` (falling
back to 0.1.0), suffixed with the channel.
+1
View File
@@ -0,0 +1 @@
20260701T000000Z
+1
View File
@@ -0,0 +1 @@
26.07
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env bash
# narOS rootfs builder (NAROS.md §2.2): mmdebstrap against the pinned Debian snapshot,
# plus (optionally) the local Nucleic package pool. Produces a rootfs tar that CI turns
# into an OCI image (naros-base/…) or the VM payload tarball.
#
# build-rootfs.sh <tier> <arch> [--pool DIR] [--out DIR] [--channel edge|stable]
#
# Needs mmdebstrap and either root or an unshare-capable user. The snapshot mirror serves
# stale Release files by design, hence Check-Valid-Until off (standard snapshot practice).
set -euo pipefail
OS_DIR="$(cd "$(dirname "$0")/.." && pwd)"
TIER="${1:?usage: build-rootfs.sh <tier> <arch> [--pool DIR] [--out DIR] [--channel C]}"
ARCH="${2:?missing arch (arm64|amd64)}"
shift 2
POOL="" OUT="$OS_DIR/dist" CHANNEL="${NAROS_CHANNEL:-edge}"
while [ $# -gt 0 ]; do
case "$1" in
--pool) POOL="$(cd "$2" && pwd)"; shift 2 ;;
--out) OUT="$2"; shift 2 ;;
--channel) CHANNEL="$2"; shift 2 ;;
*) echo "unknown arg: $1" >&2; exit 2 ;;
esac
done
VERSION="$(cat "$OS_DIR/VERSION")"
SNAPSHOT="$(cat "$OS_DIR/SNAPSHOT")"
SUITE="trixie"
MIRROR="https://snapshot.debian.org/archive/debian/${SNAPSHOT}/"
PROFILE="$OS_DIR/mkimage/profiles/$TIER.pkgs"
[ -f "$PROFILE" ] || { echo "no profile for tier '$TIER' ($PROFILE)" >&2; exit 2; }
pkg_list() { grep -vE '^\s*(#|$)' "$1" | tr '\n' ',' | sed 's/,$//'; }
INCLUDE="$(pkg_list "$PROFILE")"
SOURCES=("deb [check-valid-until=no] $MIRROR $SUITE main")
if [ -n "$POOL" ]; then
# Flat file:// repo over the pool; trusted because it is CI's own just-built artifact —
# end-user trust comes from the signed hosted repo (repo/publish.sh), not this path.
if [ ! -f "$POOL/Packages" ] || [ -n "$(find "$POOL" -name '*.deb' -newer "$POOL/Packages" 2>/dev/null)" ]; then
(cd "$POOL" && dpkg-scanpackages --multiversion . > Packages)
fi
# Late packages (profiles/<tier>.late-pkgs) install via a finish hook AFTER every
# Debian package is configured — mandatory for nash-default-shell: apt's configure
# order is not deterministic, so a mid-transaction divert would run the remaining
# Debian postinsts under nash (observed: ca-certificates' UTF-8 filenames mangled).
# The divert must be the image's final configure step.
LATE_DEBS=()
LPROFILE="$OS_DIR/mkimage/profiles/$TIER.late-pkgs"
if [ -f "$LPROFILE" ]; then
while IFS= read -r pkg; do
deb="$(ls "$POOL/${pkg}_"*.deb 2>/dev/null | head -1)"
if [ -n "$deb" ]; then
LATE_DEBS+=("$deb")
elif [ "${NAROS_STRICT:-0}" = "1" ]; then
echo "ERROR: late package $pkg absent from pool" >&2; exit 1
else
echo "WARNING: late package $pkg absent from pool — building WITHOUT it" >&2
fi
done < <(grep -vE '^\s*(#|$)' "$LPROFILE")
fi
NPROFILE="$OS_DIR/mkimage/profiles/$TIER.naros-pkgs"
if [ -f "$NPROFILE" ]; then
# Only request packages the pool actually carries: local builds legitimately lack
# some (e.g. naros-keyring without the signing key). Loud per-package warning;
# NAROS_STRICT=1 (CI) turns any gap into a hard failure.
while IFS= read -r pkg; do
if grep -qx "Package: $pkg" "$POOL/Packages"; then
INCLUDE="$INCLUDE,$pkg"
elif [ "${NAROS_STRICT:-0}" = "1" ]; then
echo "ERROR: $pkg requested by $TIER tier but absent from pool" >&2; exit 1
else
echo "WARNING: $pkg absent from pool — building WITHOUT it" >&2
fi
done < <(grep -vE '^\s*(#|$)' "$NPROFILE")
fi
SOURCES+=("deb [trusted=yes] copy://$POOL ./")
fi
mkdir -p "$OUT"
TAR="$OUT/naros-$TIER-$VERSION-$ARCH.tar"
export NAROS_TIER="$TIER" NAROS_VERSION="$VERSION" NAROS_ARCH="$ARCH" \
NAROS_CHANNEL="$CHANNEL" NAROS_SNAPSHOT="$SNAPSHOT" NAROS_SUITE="$SUITE"
HOOKS=()
if [ "${#LATE_DEBS[@]}" -gt 0 ]; then
LATE_ARGS=""
for deb in "${LATE_DEBS[@]}"; do
b="$(basename "$deb")"
HOOKS+=(--customize-hook="copy-in $deb /tmp")
LATE_ARGS="$LATE_ARGS /tmp/$b"
done
HOOKS+=(--customize-hook="chroot \"\$1\" dpkg -i$LATE_ARGS")
HOOKS+=(--customize-hook="rm -f$(printf ' "$1"%s' $LATE_ARGS)")
fi
HOOKS+=(--customize-hook="$OS_DIR/mkimage/hooks/00-identity.sh"' "$1"')
echo "narOS $VERSION/$CHANNEL: tier=$TIER arch=$ARCH snapshot=$SNAPSHOT pool=${POOL:-none}"
mmdebstrap \
--architectures="$ARCH" \
--variant=apt \
--include="$INCLUDE" \
--aptopt='Acquire::Check-Valid-Until "false"' \
"${HOOKS[@]}" \
"$SUITE" "$TAR" "${SOURCES[@]}"
echo "built $TAR"
+48
View File
@@ -0,0 +1,48 @@
#!/bin/sh
# mmdebstrap customize-hook: stamp narOS identity into the rootfs (NAROS.md §2.3).
# $1 = rootfs dir; NAROS_* env exported by build-rootfs.sh. Debian's own
# /usr/lib/os-release stays intact (ID_LIKE tooling keeps working); we replace only the
# /etc/os-release symlink with the narOS file.
set -eu
R="$1"
rm -f "$R/etc/os-release"
cat > "$R/etc/os-release" <<EOF
NAME="narOS"
PRETTY_NAME="narOS $NAROS_VERSION (Nucleic Agent Runtime OS)"
ID=naros
ID_LIKE=debian
VERSION_ID="$NAROS_VERSION"
VERSION="$NAROS_VERSION ($NAROS_CHANNEL)"
VERSION_CODENAME=$NAROS_SUITE
VARIANT="$NAROS_TIER"
VARIANT_ID=$NAROS_TIER
HOME_URL="https://github.com/abkslm/nucleic"
DOCUMENTATION_URL="https://github.com/abkslm/nucleic/blob/main/docs/NAROS.md"
EOF
mkdir -p "$R/etc/naros"
echo "$NAROS_CHANNEL" > "$R/etc/naros/channel"
echo "$NAROS_SNAPSHOT" > "$R/etc/naros/snapshot-date"
echo "container" > "$R/etc/naros/role"
# Capability manifest (NAROS.md §6.3). Base fields here; toolchain entries are appended
# by the tiers that install them (agent-tier hook, N2). Versions of Nucleic packages are
# queryable via dpkg, listed here for one-stop reads.
nash_ver="$(chroot "$R" dpkg-query -W -f '${Version}' nash 2>/dev/null || echo null)"
[ "$nash_ver" = null ] || nash_ver="\"$nash_ver\""
cat > "$R/etc/naros/manifest.json" <<EOF
{
"os": "naros",
"version": "$NAROS_VERSION",
"channel": "$NAROS_CHANNEL",
"tier": "$NAROS_TIER",
"arch": "$NAROS_ARCH",
"debian": { "suite": "$NAROS_SUITE", "snapshot": "$NAROS_SNAPSHOT" },
"nash": $nash_ver,
"toolchains": {},
"caches": {}
}
EOF
echo "narOS identity: $(. "$R/etc/os-release" && echo "$PRETTY_NAME [$VARIANT/$NAROS_ARCH]")"
+3
View File
@@ -0,0 +1,3 @@
# naros-agent tier (NAROS.md §4, milestone N2 — placeholder until the agent tier lands).
# Will carry: build-essential/pkg-config, python3 + pip/venv, the modern CLI kit, and the
# hooks that add Node (NodeSource), rustup, Go, mise, warm caches, agent CLIs, Playwright.
+5
View File
@@ -0,0 +1,5 @@
# Installed via dpkg -i in a finish hook, after every Debian package is configured.
# nash-default-shell MUST be last-stage: its divert flips /bin/sh to nash, and any
# Debian postinst that runs after the flip runs under nash (see build-rootfs.sh).
nash-default-shell
naros-tier-base
+7
View File
@@ -0,0 +1,7 @@
# Nucleic packages for naros-base, resolved from the local pool when --pool is given.
# The divert package + tier meta are in base.late-pkgs (must configure last); this list
# is what installs alongside the Debian set.
nash
naros-init
naros
naros-keyring
+7
View File
@@ -0,0 +1,7 @@
# naros-base Debian package list (NAROS.md §4). Kept deliberately small — the agent
# toolchain lives in the agent tier. apt itself comes from --variant=apt.
ca-certificates
curl
git
openssh-client
iproute2
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env bash
# Build every narOS package into dist/pool/ with plain dpkg-deb (NAROS.md §3.1).
#
# Deliberately not debhelper: these are binary payloads, scripts, and meta-packages —
# a uniform stage-and-pack loop keeps the whole layer readable and buildable anywhere
# dpkg-deb exists (this container, CI). Graduating a package to dpkg-buildpackage later
# is a per-package decision, not a build-system change.
#
# build-all.sh [--arch arm64,amd64] [--channel edge|stable] [--only pkg1,pkg2]
#
# Per package dir: control (template: @VERSION@ @ARCH@), optional files/ (copied
# verbatim), optional stage.sh (sourced; must define stage <destdir> <arch>), optional
# postinst/prerm/preinst/postrm. Arch-any packages build once per requested arch and
# typically stage a prebuilt binary from dist/bin/<name>-<arch>; missing binaries skip
# the package with a warning so metadata-only iteration needs no Rust toolchain.
set -euo pipefail
PKG_DIR="$(cd "$(dirname "$0")" && pwd)"
OS_DIR="$(cd "$PKG_DIR/.." && pwd)"
ARCHES="arm64,amd64" CHANNEL="${NAROS_CHANNEL:-edge}" ONLY=""
while [ $# -gt 0 ]; do
case "$1" in
--arch) ARCHES="$2"; shift 2 ;;
--channel) CHANNEL="$2"; shift 2 ;;
--only) ONLY="$2"; shift 2 ;;
*) echo "unknown arg: $1" >&2; exit 2 ;;
esac
done
OS_VERSION="$(cat "$OS_DIR/VERSION")"
POOL="$OS_DIR/dist/pool"
mkdir -p "$POOL"
pkg_version() { # package semver + channel tag, e.g. 0.1.0+edge26.07
local d="$1" base
base="$( [ -f "$d/VERSION" ] && cat "$d/VERSION" || echo 0.1.0 )"
echo "${base}+${CHANNEL}${OS_VERSION}"
}
build_one() { # <pkgdir> <arch>
local d="$1" arch="$2" name ver stage out
name="$(basename "$d")"
ver="$(pkg_version "$d")"
stage="$(mktemp -d)"
trap 'rm -rf "$stage"' RETURN
[ -d "$d/files" ] && cp -a "$d/files/." "$stage/"
if [ -f "$d/stage.sh" ]; then
# shellcheck source=/dev/null
( set -euo pipefail; OS_DIR="$OS_DIR" . "$d/stage.sh"; stage "$stage" "$arch" ) || {
echo "SKIP $name/$arch: $(cat "$stage/.skip-reason" 2>/dev/null || echo staging failed)" >&2
return 0
}
fi
mkdir -p "$stage/DEBIAN"
sed -e "s/@VERSION@/$ver/" -e "s/@ARCH@/$arch/" "$d/control" > "$stage/DEBIAN/control"
local s
for s in preinst postinst prerm postrm; do
[ -f "$d/$s" ] && install -m 0755 "$d/$s" "$stage/DEBIAN/$s"
done
out="$POOL/${name}_${ver}_$(grep -Po '^Architecture: \K.*' "$stage/DEBIAN/control").deb"
dpkg-deb --root-owner-group -Zxz --build "$stage" "$out" > /dev/null
echo "built ${out#"$OS_DIR/"}"
}
for d in "$PKG_DIR"/*/; do
name="$(basename "$d")"
[ -f "$d/control" ] || continue
if [ -n "$ONLY" ] && ! echo ",$ONLY," | grep -q ",$name,"; then continue; fi
if grep -q '^Architecture: @ARCH@' "$d/control"; then
IFS=, read -ra AA <<< "$ARCHES"
for a in "${AA[@]}"; do build_one "$d" "$a"; done
else
build_one "$d" all
fi
done
+12
View File
@@ -0,0 +1,12 @@
Package: naros-init
Version: @VERSION@
Architecture: @ARCH@
Maintainer: Nucleic <[email protected]>
Section: admin
Priority: optional
Description: narOS PID-1 supervisor for container surfaces (NAROS.md §5)
Small static init: reaps zombies, forwards signals, optionally supervises the
in-container control bridge (NAROS_BRIDGE=1) and/or a primary command
(everything after --), and otherwise acts as the keepalive that replaces
ContainerEngine's sleep loop. Role-driven via /etc/naros/role or NAROS_ROLE.
In the VM desktop flavor systemd stays PID 1 and naros-init runs as a unit.
+9
View File
@@ -0,0 +1,9 @@
# Stage the prebuilt static naros-init binary (built by CI from shell/naros-init).
stage() {
local dest="$1" arch="$2" bin="$OS_DIR/dist/bin/naros-init-$arch"
if [ ! -x "$bin" ]; then
echo "prebuilt binary missing: $bin" > "$dest/.skip-reason"
return 1
fi
install -D -m 0755 "$bin" "$dest/usr/sbin/naros-init"
}
+11
View File
@@ -0,0 +1,11 @@
Package: naros-keyring
Version: @VERSION@
Architecture: all
Maintainer: Nucleic <[email protected]>
Section: misc
Priority: optional
Description: narOS apt archive keyring and source entry (NAROS.md §3.2)
The narOS apt repository's signing public key
(/usr/share/keyrings/naros-archive-keyring.gpg) plus the deb822 source entry
for apt.naros.dev pinned to that key. Installing this on any Debian-family
system enables `apt install naros-tier-agent` conversion (NAROS.md §7.3).
@@ -0,0 +1,5 @@
Types: deb
URIs: https://apt.naros.dev
Suites: stable
Components: main
Signed-By: /usr/share/keyrings/naros-archive-keyring.gpg
+10
View File
@@ -0,0 +1,10 @@
# The public key is materialized by CI from the NAROS_APT_PUBLIC_KEY secret (or by an
# operator into os/repo/keys/). No key in the tree, no keyring package — skip cleanly.
stage() {
local dest="$1" key="$OS_DIR/repo/keys/naros-archive-keyring.gpg"
if [ ! -f "$key" ]; then
echo "public key missing: $key (CI materializes it from secrets)" > "$dest/.skip-reason"
return 1
fi
install -D -m 0644 "$key" "$dest/usr/share/keyrings/naros-archive-keyring.gpg"
}
+13
View File
@@ -0,0 +1,13 @@
Package: naros-tier-agent
Version: @VERSION@
Architecture: all
Maintainer: Nucleic <[email protected]>
Section: metapackages
Priority: optional
Depends: naros-tier-base, nucleic-bridge, build-essential, pkg-config, python3, python3-pip, python3-venv, ripgrep, fd-find, jq, sqlite3, htop, tree, zip, unzip, zstd, xz-utils, moreutils, rsync, less, procps, file, bsdextrautils
Description: narOS agent tier — apt-resolvable half (NAROS.md §4, §6)
The dev toolchain and modern CLI kit that come from Debian, plus the control
bridge. The non-apt half of the agent tier — Node (NodeSource), rustup, Go,
mise, warm caches, agent CLIs, Playwright — is layered by the naros-agent
image build (milestone N2); this meta is what `apt install` can deliver into
any Debian-family container (NAROS.md §7.3 conversion path).
+12
View File
@@ -0,0 +1,12 @@
Package: naros-tier-base
Version: @VERSION@
Architecture: all
Maintainer: Nucleic <[email protected]>
Section: metapackages
Priority: optional
Depends: nash, nash-default-shell, naros-init, naros, ca-certificates, curl, git, openssh-client, iproute2
Recommends: naros-keyring
Description: narOS base tier (NAROS.md §4)
The minimal narOS surface: nash forced as the default shell, naros-init, the
naros CLI, and the small always-wanted utility set. Installing this meta on a
stock Debian-family system converts it to a naros-base-equivalent environment.
+12
View File
@@ -0,0 +1,12 @@
Package: naros-tier-runner
Version: @VERSION@
Architecture: all
Maintainer: Nucleic <[email protected]>
Section: metapackages
Priority: optional
Depends: naros-tier-agent
Description: narOS runner tier (NAROS.md §4)
The Covalence runner surface: everything in the agent tier. nucleicd itself is
a direct image COPY (versioned with the app, not a deb — NAROS.md §3.1), so
this meta currently only anchors the tier for introspection and future
runner-only dependencies.
+12
View File
@@ -0,0 +1,12 @@
Package: naros-tier-vm
Version: @VERSION@
Architecture: all
Maintainer: Nucleic <[email protected]>
Section: metapackages
Priority: optional
Depends: naros-tier-base, systemd, dbus, sudo
Description: narOS VM guest tier — headless scope (NAROS.md §4, §7.4, milestone N4)
The bootable-guest surface. Headless scope for now: systemd (the VM flavor
keeps it as PID 1), dbus, sudo. nucleic-linux-agent packaging, the firstboot
provisioning glue, and the GNOME 50 desktop stack (naros-desktop, N5) land in
later milestones and will extend this meta.
+12
View File
@@ -0,0 +1,12 @@
Package: naros
Version: @VERSION@
Architecture: all
Maintainer: Nucleic <[email protected]>
Section: utils
Priority: optional
Description: narOS introspection CLI (NAROS.md §6.3)
`naros info [--json]` prints the capability manifest (/etc/naros/manifest.json:
tier, version, channel, snapshot, toolchains, caches); `naros version` prints
the release. Lets agents and the host ask "what can this box do" instead of
probing binary-by-binary. v1 is a POSIX sh script; a compiled multi-call
binary shared with naros-init can replace it without interface change.
+24
View File
@@ -0,0 +1,24 @@
#!/bin/sh
# narOS introspection CLI (NAROS.md §6.3). Reads /etc/naros + /etc/os-release only.
set -eu
MANIFEST=/etc/naros/manifest.json
case "${1:-info}" in
version)
. /etc/os-release
echo "narOS ${VERSION_ID:-unknown} (${VARIANT:-?}/$(cat /etc/naros/channel 2>/dev/null || echo '?'))"
;;
info)
if [ "${2:-}" = "--json" ]; then
cat "$MANIFEST"
elif command -v jq > /dev/null 2>&1; then
jq . "$MANIFEST"
else
cat "$MANIFEST"
fi
;;
*)
echo "usage: naros [info [--json] | version]" >&2
exit 2
;;
esac
+14
View File
@@ -0,0 +1,14 @@
Package: nash-default-shell
Version: @VERSION@
Architecture: all
Maintainer: Nucleic <[email protected]>
Section: shells
Priority: optional
Depends: nash
Description: force nash as the system default shell (NASH.md §7.1)
The maintainer-script form of the locked divert block: /bin/bash and /bin/dash
are dpkg-diverted to /usr/bin/{bash,dash}.real and /bin/{bash,dash,sh} point at
nash, so shebangs and tools that hardcode sh/bash land in nash. Real shells
stay reachable at the .real paths — nash's parse-failure fallback depends on
them. Removing this package cleanly restores stock shells. dpkg-divert keeps
apt upgrades of bash/dash from clobbering the links.
@@ -0,0 +1,4 @@
# narOS shell environment (nash-default-shell). NUCLEIC_REAL_BASH is nash's
# parse-failure fallback + NUCLEIC_NASH_DISABLE target (NASH.md §4.1).
export NUCLEIC_REAL_BASH=/usr/bin/bash.real
[ -n "${SHELL:-}" ] || export SHELL=/usr/local/bin/nash
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh
# NASH.md §7.1, verbatim semantics (merged-usr symlink handling verified in nash M0).
set -e
if [ "$1" = "configure" ]; then
dpkg-divert --package nash-default-shell --divert /usr/bin/bash.real --rename --add /bin/bash
dpkg-divert --package nash-default-shell --divert /usr/bin/dash.real --rename --add /bin/dash
ln -sf /usr/local/bin/nash /bin/bash
ln -sf /usr/local/bin/nash /bin/dash
ln -sf /usr/local/bin/nash /bin/sh
fi
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
# Clean revert: drop the nash links, un-divert the real shells, restore sh -> dash.
set -e
if [ "$1" = "remove" ]; then
rm -f /bin/bash /bin/dash
dpkg-divert --package nash-default-shell --rename --remove /bin/bash
dpkg-divert --package nash-default-shell --rename --remove /bin/dash
ln -sf dash /usr/bin/sh
fi
+12
View File
@@ -0,0 +1,12 @@
Package: nash
Version: @VERSION@
Architecture: @ARCH@
Maintainer: Nucleic <[email protected]>
Section: shells
Priority: optional
Description: Nucleic agent shell (brush fork)
Bourne/bash-compatible shell whose job is to make every shell action an agent
takes observable by construction (docs/NASH.md). Static musl binary; installs
as /usr/bin/nash with the locked /usr/local/bin/nash path provided as a
symlink. This package does NOT change the default shell — that is
nash-default-shell's job.
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
# The locked contract (NASH.md §2, §7) addresses nash at /usr/local/bin/nash on every
# surface (probe, exec argv, $SHELL). The real file lives at /usr/bin/nash per policy;
# this symlink satisfies the contract path.
set -e
if [ "$1" = "configure" ]; then
mkdir -p /usr/local/bin
ln -sf /usr/bin/nash /usr/local/bin/nash
fi
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
set -e
if [ "$1" = "remove" ]; then
[ -L /usr/local/bin/nash ] && rm -f /usr/local/bin/nash || true
fi
+9
View File
@@ -0,0 +1,9 @@
# Stage the prebuilt static nash binary (built by CI from shell/, target musl).
stage() {
local dest="$1" arch="$2" bin="$OS_DIR/dist/bin/nash-$arch"
if [ ! -x "$bin" ]; then
echo "prebuilt binary missing: $bin" > "$dest/.skip-reason"
return 1
fi
install -D -m 0755 "$bin" "$dest/usr/bin/nash"
}
+12
View File
@@ -0,0 +1,12 @@
Package: nucleic-bridge
Version: @VERSION@
Architecture: all
Maintainer: Nucleic <[email protected]>
Section: net
Priority: optional
Depends: nodejs
Description: in-container control bridge (docs/VSOCK_CONTROL_PLANE.md)
Loopback TCP to the vsock-relayed host control socket, so the agent and the
interceptor shims reach the host approval server with no IP listener.
Launched by naros-init (or the container's root init) only when Nucleic
relays a control socket in.
+6
View File
@@ -0,0 +1,6 @@
# The bridge source of truth stays beside the sandbox image context; the deb packages it.
stage() {
local dest="$1"
install -D -m 0644 "$OS_DIR/../containers/nucleic-sandbox/control-bridge.js" \
"$dest/opt/nucleic/control-bridge.js"
}
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# Publish dist/pool as a static apt tree (NAROS.md §3.2): dist/repo/dists/<channel>/main/
# with per-arch Packages(.xz) and a signed InRelease when a key is available.
#
# publish.sh [--channel edge|stable] [--sign KEYID]
#
# Unsigned mode is for local/dev use only (consume with [trusted=yes]); CI always signs
# (key from the NAROS_APT_SIGNING_KEY secret). Sync to R2 is r2-sync.sh's job.
set -euo pipefail
OS_DIR="$(cd "$(dirname "$0")/.." && pwd)"
CHANNEL="${NAROS_CHANNEL:-edge}" KEYID="${NAROS_APT_KEYID:-}"
while [ $# -gt 0 ]; do
case "$1" in
--channel) CHANNEL="$2"; shift 2 ;;
--sign) KEYID="$2"; shift 2 ;;
*) echo "unknown arg: $1" >&2; exit 2 ;;
esac
done
POOL="$OS_DIR/dist/pool"
REPO="$OS_DIR/dist/repo"
DISTS="$REPO/dists/$CHANNEL/main"
[ -d "$POOL" ] || { echo "no pool at $POOL — run packages/build-all.sh first" >&2; exit 2; }
rm -rf "$REPO/dists/$CHANNEL"
mkdir -p "$REPO/pool/main"
cp -a "$POOL/." "$REPO/pool/main/" 2>/dev/null || true
rm -f "$REPO/pool/main/Packages"
cd "$REPO"
for arch in arm64 amd64; do
bindir="dists/$CHANNEL/main/binary-$arch"
mkdir -p "$bindir"
# Arch-specific debs for this arch + arch:all debs, one Packages per binary-<arch>.
dpkg-scanpackages --multiversion --arch "$arch" pool > "$bindir/Packages"
xz -k -f "$bindir/Packages"
done
apt-ftparchive \
-o "APT::FTPArchive::Release::Origin=narOS" \
-o "APT::FTPArchive::Release::Label=narOS" \
-o "APT::FTPArchive::Release::Suite=$CHANNEL" \
-o "APT::FTPArchive::Release::Codename=$CHANNEL" \
-o "APT::FTPArchive::Release::Architectures=arm64 amd64" \
-o "APT::FTPArchive::Release::Components=main" \
release "dists/$CHANNEL" > "dists/$CHANNEL/Release"
if [ -n "$KEYID" ]; then
gpg --batch --yes -u "$KEYID" --clearsign -o "dists/$CHANNEL/InRelease" "dists/$CHANNEL/Release"
gpg --batch --yes -u "$KEYID" --detach-sign --armor -o "dists/$CHANNEL/Release.gpg" "dists/$CHANNEL/Release"
echo "published SIGNED repo: $REPO (channel $CHANNEL, key $KEYID)"
else
echo "published UNSIGNED repo: $REPO (channel $CHANNEL) — dev only, consume with [trusted=yes]"
fi
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Sync the published apt tree to Cloudflare R2 (served as apt.naros.dev; NAROS.md §3.2).
# Uses rclone's S3 backend with env-provided credentials (CI secrets):
# R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET (default naros-apt)
set -euo pipefail
OS_DIR="$(cd "$(dirname "$0")/.." && pwd)"
REPO="$OS_DIR/dist/repo"
: "${R2_ACCOUNT_ID:?}" "${R2_ACCESS_KEY_ID:?}" "${R2_SECRET_ACCESS_KEY:?}"
BUCKET="${R2_BUCKET:-naros-apt}"
[ -d "$REPO" ] || { echo "no repo at $REPO — run repo/publish.sh first" >&2; exit 2; }
export RCLONE_CONFIG_R2_TYPE=s3 \
RCLONE_CONFIG_R2_PROVIDER=Cloudflare \
RCLONE_CONFIG_R2_ACCESS_KEY_ID="$R2_ACCESS_KEY_ID" \
RCLONE_CONFIG_R2_SECRET_ACCESS_KEY="$R2_SECRET_ACCESS_KEY" \
RCLONE_CONFIG_R2_ENDPOINT="https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com"
# pool/ first, dists/ last: clients never see an index referencing a not-yet-uploaded deb.
rclone copy "$REPO/pool" "r2:$BUCKET/pool" --checksum
rclone sync "$REPO/dists" "r2:$BUCKET/dists" --checksum
echo "synced $REPO -> r2:$BUCKET"