Add distro/ubuntuCore for UC26 snap and image builds.
Centralize salmanoff snapcraft, dangerous-model image scripts, and QEMU workflow so UC26 can be reproduced from the SMO repo without ubuntu-core-practice. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# Image build outputs (large)
|
||||
artifacts/images/
|
||||
artifacts/images-dev/
|
||||
artifacts/logs/
|
||||
|
||||
# ubuntu-image scratch
|
||||
work/ubuntu-image-snap/
|
||||
work/ubuntu-image-dev/
|
||||
|
||||
# Dev signing outputs (account-specific)
|
||||
models/salmanoff-dev-amd64.model
|
||||
models/ubuntu-core-26-amd64.model
|
||||
assertions/
|
||||
config/dev-image.env
|
||||
config/ssh/smo-dev
|
||||
config/ssh/smo-dev.pub
|
||||
|
||||
# QEMU writable UEFI vars
|
||||
artifacts/firmware/OVMF_VARS_4M.ms.fd
|
||||
|
||||
# snapcraft working tree
|
||||
snaps/salmanoff/parts/
|
||||
snaps/salmanoff/stage/
|
||||
snaps/salmanoff/prime/
|
||||
snaps/salmanoff/.snapcraft/
|
||||
snaps/salmanoff/*.snap
|
||||
|
||||
# Editor / OS noise
|
||||
*~
|
||||
.DS_Store
|
||||
@@ -0,0 +1,16 @@
|
||||
# Copy to config/dev-image.env and adjust after setup-dev-signing.sh.
|
||||
#
|
||||
# ACCOUNT_ID comes from: snapcraft whoami (or snap whoami when logged in)
|
||||
# SIGN_KEY_NAME comes from: snap keys (after snapcraft create-key + register-key)
|
||||
|
||||
ACCOUNT_ID=
|
||||
SIGN_KEY_NAME=salmanoff-dev
|
||||
|
||||
# System user created on first boot (no Ubuntu One / console-conf SSO).
|
||||
SYSTEM_USER_NAME=smo
|
||||
SYSTEM_USER_EMAIL=smo-dev@salmanoff
|
||||
# Public key used in the system-user assertion (private key stays local).
|
||||
SSH_PUBKEY_FILE=config/ssh/smo-dev.pub
|
||||
|
||||
# Dangerous-grade dev model (UC26 amd64). salmanoff is optional until --snap is passed.
|
||||
MODEL_NAME=salmanoff-dev-amd64
|
||||
@@ -0,0 +1,6 @@
|
||||
# Defaults for scripts/run-qemu.sh (source manually if you want overrides)
|
||||
RAM_MB=2048
|
||||
SMP=2
|
||||
SSH_PORT=8022
|
||||
OVMF_CODE=/usr/share/OVMF/OVMF_CODE_4M.secboot.fd
|
||||
OVMF_VARS_TEMPLATE=/usr/share/OVMF/OVMF_VARS_4M.ms.fd
|
||||
@@ -0,0 +1,3 @@
|
||||
# Optional: remote git source if snapcraft.yaml is switched from local tree to git.
|
||||
SMO_GIT_URL=git@zbz-gitea-as-hayodea:hayodea/salmanoff.git
|
||||
SMO_GIT_BRANCH=clast
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"type": "model",
|
||||
"authority-id": "@ACCOUNT_ID@",
|
||||
"brand-id": "@ACCOUNT_ID@",
|
||||
"series": "16",
|
||||
"model": "salmanoff-dev-amd64",
|
||||
"architecture": "amd64",
|
||||
"base": "core26",
|
||||
"grade": "dangerous",
|
||||
"system-user-authority": "*",
|
||||
"timestamp": "@TIMESTAMP@",
|
||||
"snaps": [
|
||||
{
|
||||
"name": "pc",
|
||||
"type": "gadget",
|
||||
"default-channel": "26/stable",
|
||||
"id": "UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH"
|
||||
},
|
||||
{
|
||||
"name": "pc-kernel",
|
||||
"type": "kernel",
|
||||
"default-channel": "26/stable",
|
||||
"id": "pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza"
|
||||
},
|
||||
{
|
||||
"name": "core26",
|
||||
"type": "base",
|
||||
"default-channel": "cloud-init/stable",
|
||||
"id": "cUqM61hRuZAJYmIS898Ux66VY61gBbZf"
|
||||
},
|
||||
{
|
||||
"name": "snapd",
|
||||
"type": "snapd",
|
||||
"default-channel": "latest/stable",
|
||||
"id": "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4"
|
||||
},
|
||||
{
|
||||
"name": "salmanoff",
|
||||
"type": "app",
|
||||
"presence": "optional"
|
||||
}
|
||||
]
|
||||
}
|
||||
Executable
+133
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build dangerous-grade UC26 image with seeded system-user (no Ubuntu One at first boot).
|
||||
set -euo pipefail
|
||||
|
||||
UC_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ENV_FILE="${UC_ROOT}/config/dev-image.env"
|
||||
|
||||
MODEL="${UC_ROOT}/models/salmanoff-dev-amd64.model"
|
||||
SYSTEM_USER_ASSERT="${UC_ROOT}/assertions/smo-system-user.assert"
|
||||
WORKDIR="${UC_ROOT}/work/ubuntu-image-dev"
|
||||
OUTPUT_DIR="${UC_ROOT}/artifacts/images-dev"
|
||||
LOG_DIR="${UC_ROOT}/artifacts/logs"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: build-dev-image.sh [OPTIONS]
|
||||
|
||||
Build a dangerous-grade ubuntu-core dev image with:
|
||||
- custom model (salmanoff-dev-amd64)
|
||||
- system-user assertion seeded (SSH login as smo, no Ubuntu One)
|
||||
|
||||
Prerequisites:
|
||||
scripts/setup-dev-signing.sh (once: Snap Store login + signing key)
|
||||
scripts/sign-dev-assertions.sh (sign model + system-user)
|
||||
|
||||
Options:
|
||||
--snap PATH Extra snap to preinstall (repeatable). Requires grade dangerous.
|
||||
--fresh-workdir Remove work/ubuntu-image-dev before building
|
||||
--resume Pass --resume to ubuntu-image
|
||||
--no-sign Skip sign-dev-assertions.sh (use existing assertions)
|
||||
-h, --help Show this help
|
||||
|
||||
Outputs:
|
||||
artifacts/images-dev/pc.img
|
||||
artifacts/logs/build-dev-<timestamp>.log
|
||||
|
||||
After first boot in QEMU:
|
||||
ssh -i config/ssh/smo-dev smo@localhost -p 8022
|
||||
EOF
|
||||
}
|
||||
|
||||
fresh_workdir=false
|
||||
resume=false
|
||||
no_sign=false
|
||||
extra_snaps=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--snap) extra_snaps+=("$2"); shift 2 ;;
|
||||
--fresh-workdir) fresh_workdir=true; shift ;;
|
||||
--resume) resume=true; shift ;;
|
||||
--no-sign) no_sign=true; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$no_sign" == false ]]; then
|
||||
"${UC_ROOT}/scripts/sign-dev-assertions.sh"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$MODEL" ]]; then
|
||||
echo "Missing signed model: $MODEL" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$SYSTEM_USER_ASSERT" ]]; then
|
||||
echo "Missing system-user assertion: $SYSTEM_USER_ASSERT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v ubuntu-image >/dev/null 2>&1; then
|
||||
echo "ubuntu-image not found. Install with: sudo snap install ubuntu-image --classic" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$OUTPUT_DIR" "$LOG_DIR"
|
||||
|
||||
if [[ "$fresh_workdir" == true ]] || [[ "$resume" == false ]]; then
|
||||
if [[ -d "$WORKDIR" ]]; then
|
||||
echo "Removing workdir: $WORKDIR"
|
||||
rm -rf "$WORKDIR"
|
||||
fi
|
||||
fi
|
||||
mkdir -p "$WORKDIR"
|
||||
|
||||
timestamp="$(date +%Y%m%d-%H%M%S)"
|
||||
log_file="${LOG_DIR}/build-dev-${timestamp}.log"
|
||||
|
||||
cmd=(ubuntu-image snap
|
||||
--workdir "$WORKDIR"
|
||||
--output-dir "$OUTPUT_DIR"
|
||||
--image-size 4G
|
||||
--assertion "$SYSTEM_USER_ASSERT"
|
||||
"$MODEL")
|
||||
|
||||
for snap_path in "${extra_snaps[@]}"; do
|
||||
if [[ ! -f "$snap_path" ]]; then
|
||||
echo "Snap not found: $snap_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
cmd+=(--snap "$snap_path")
|
||||
done
|
||||
|
||||
if [[ "$resume" == true ]]; then
|
||||
cmd+=(--resume)
|
||||
fi
|
||||
|
||||
echo "Model: $MODEL"
|
||||
echo "System user: $SYSTEM_USER_ASSERT"
|
||||
echo "Workdir: $WORKDIR"
|
||||
echo "Output dir: $OUTPUT_DIR"
|
||||
echo "Log: $log_file"
|
||||
if [[ ${#extra_snaps[@]} -gt 0 ]]; then
|
||||
echo "Extra snaps: ${extra_snaps[*]}"
|
||||
fi
|
||||
echo "Running: ${cmd[*]}"
|
||||
|
||||
"${cmd[@]}" 2>&1 | tee "$log_file"
|
||||
|
||||
img="${OUTPUT_DIR}/pc.img"
|
||||
if [[ ! -f "$img" ]]; then
|
||||
echo "Build finished but $img not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Done. Image: $img"
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$ENV_FILE"
|
||||
echo "SSH: ssh -i ${UC_ROOT}/config/ssh/smo-dev ${SYSTEM_USER_NAME:-smo}@localhost -p 8022"
|
||||
fi
|
||||
ls -lh "$img" "${img}.seed.manifest" 2>/dev/null || ls -lh "$img"
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the salmanoff snap from the enclosing SMO repo tree.
|
||||
set -euo pipefail
|
||||
|
||||
UC_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SNAP_DIR="${UC_ROOT}/snaps/salmanoff"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: build-snap.sh [OPTIONS]
|
||||
|
||||
Build salmanoff snap with snapcraft (source: SMO repo root via local snapcraft.yaml).
|
||||
|
||||
Options:
|
||||
--refetch-source Clean salmanoff part before pack (re-copy tree + submodules)
|
||||
--clean Remove all snapcraft parts/prime/stage before building
|
||||
--lxd Use LXD cleanroom instead of --destructive-mode
|
||||
-h, --help Show this help
|
||||
|
||||
Prerequisites:
|
||||
snap install snapcraft --classic
|
||||
For --lxd on core26: LXD outbound internet (run: sudo scripts/fix-lxd-network.sh)
|
||||
For --destructive-mode: run on Ubuntu 26.04 or use --lxd
|
||||
EOF
|
||||
}
|
||||
|
||||
clean=false
|
||||
refetch_source=false
|
||||
use_lxd=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--clean) clean=true; shift ;;
|
||||
--refetch-source) refetch_source=true; shift ;;
|
||||
--lxd) use_lxd=true; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ! command -v snapcraft >/dev/null 2>&1; then
|
||||
echo "snapcraft not found. Install with: sudo snap install snapcraft --classic" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$clean" == true ]]; then
|
||||
rm -rf "${SNAP_DIR}/parts" "${SNAP_DIR}/stage" "${SNAP_DIR}/prime" \
|
||||
"${SNAP_DIR}/.snapcraft" "${SNAP_DIR}"/*.snap 2>/dev/null || true
|
||||
fi
|
||||
|
||||
cd "$SNAP_DIR"
|
||||
|
||||
pack_flags=()
|
||||
if [[ "$use_lxd" != true ]]; then
|
||||
pack_flags+=(--destructive-mode)
|
||||
else
|
||||
pack_flags+=(--use-lxd)
|
||||
fi
|
||||
|
||||
if [[ "$refetch_source" == true && "$clean" != true ]]; then
|
||||
echo "Refetching salmanoff source (clean part + pull)..."
|
||||
snapcraft clean salmanoff "${pack_flags[@]}"
|
||||
fi
|
||||
|
||||
cmd=(snapcraft pack "${pack_flags[@]}")
|
||||
|
||||
echo "UC root: $UC_ROOT"
|
||||
echo "Snap dir: $SNAP_DIR"
|
||||
echo "Running: ${cmd[*]}"
|
||||
|
||||
"${cmd[@]}"
|
||||
|
||||
snap_file="$(ls -1t "${SNAP_DIR}"/*.snap 2>/dev/null | head -1)"
|
||||
if [[ -n "$snap_file" ]]; then
|
||||
echo ""
|
||||
echo "Built: $snap_file"
|
||||
ls -lh "$snap_file"
|
||||
fi
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build stock Ubuntu Core 26 amd64 image from Canonical's signed model assertion.
|
||||
set -euo pipefail
|
||||
|
||||
UC_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
MODEL="${UC_ROOT}/models/ubuntu-core-26-amd64.model"
|
||||
WORKDIR="${UC_ROOT}/work/ubuntu-image-snap"
|
||||
OUTPUT_DIR="${UC_ROOT}/artifacts/images"
|
||||
LOG_DIR="${UC_ROOT}/artifacts/logs"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: build-stock-image.sh [OPTIONS]
|
||||
|
||||
Build a stock ubuntu-core-26-amd64 disk image with ubuntu-image snap.
|
||||
|
||||
Options:
|
||||
--fresh-workdir Remove work/ubuntu-image-snap before building (full re-download)
|
||||
--resume Pass --resume to ubuntu-image (continue partial build)
|
||||
-h, --help Show this help
|
||||
|
||||
Outputs:
|
||||
artifacts/images/pc.img
|
||||
artifacts/images/pc.img.seed.manifest
|
||||
artifacts/logs/build-<timestamp>.log
|
||||
|
||||
Host prerequisites:
|
||||
snap install ubuntu-image --classic
|
||||
scripts/fetch-model.sh (download signed model if missing)
|
||||
EOF
|
||||
}
|
||||
|
||||
fresh_workdir=false
|
||||
resume=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--fresh-workdir) fresh_workdir=true; shift ;;
|
||||
--resume) resume=true; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ! -f "$MODEL" ]]; then
|
||||
echo "Missing model assertion: $MODEL" >&2
|
||||
echo "Run: scripts/fetch-model.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v ubuntu-image >/dev/null 2>&1; then
|
||||
echo "ubuntu-image not found. Install with: sudo snap install ubuntu-image --classic" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$OUTPUT_DIR" "$LOG_DIR"
|
||||
|
||||
if [[ "$fresh_workdir" == true ]] || [[ "$resume" == false ]]; then
|
||||
if [[ -d "$WORKDIR" ]]; then
|
||||
echo "Removing workdir: $WORKDIR"
|
||||
rm -rf "$WORKDIR"
|
||||
fi
|
||||
fi
|
||||
mkdir -p "$WORKDIR"
|
||||
|
||||
timestamp="$(date +%Y%m%d-%H%M%S)"
|
||||
log_file="${LOG_DIR}/build-${timestamp}.log"
|
||||
|
||||
cmd=(ubuntu-image snap
|
||||
--workdir "$WORKDIR"
|
||||
--output-dir "$OUTPUT_DIR"
|
||||
--image-size 4G
|
||||
"$MODEL")
|
||||
|
||||
if [[ "$resume" == true ]]; then
|
||||
cmd+=(--resume)
|
||||
fi
|
||||
|
||||
echo "Model: $MODEL"
|
||||
echo "Workdir: $WORKDIR"
|
||||
echo "Output dir: $OUTPUT_DIR"
|
||||
echo "Log: $log_file"
|
||||
echo "Running: ${cmd[*]}"
|
||||
|
||||
"${cmd[@]}" 2>&1 | tee "$log_file"
|
||||
|
||||
img="${OUTPUT_DIR}/pc.img"
|
||||
if [[ ! -f "$img" ]]; then
|
||||
echo "Build finished but $img not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Done. Image: $img"
|
||||
ls -lh "$img" "${img}.seed.manifest" 2>/dev/null || ls -lh "$img"
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# Refresh the signed reference model from snapcore/models on GitHub.
|
||||
set -euo pipefail
|
||||
|
||||
UC_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
MODEL="${UC_ROOT}/models/ubuntu-core-26-amd64.model"
|
||||
URL="https://raw.githubusercontent.com/snapcore/models/master/ubuntu-core-26-amd64.model"
|
||||
|
||||
mkdir -p "$(dirname "$MODEL")"
|
||||
curl -fsSL -o "$MODEL" "$URL"
|
||||
echo "Updated: $MODEL"
|
||||
head -15 "$MODEL"
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# Restore outbound internet for LXD containers (common Docker + LXD conflict on Ubuntu).
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "Run with sudo: sudo $0" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LXD_BRIDGE="${LXD_BRIDGE:-lxdbr0}"
|
||||
LXD_SUBNET="$(lxc network get "${LXD_BRIDGE}" ipv4.address 2>/dev/null | cut -d/ -f1 | awk -F. '{print $1"."$2"."$3".0/24"}')"
|
||||
if [[ -z "${LXD_SUBNET}" || "${LXD_SUBNET}" == ".0/24" ]]; then
|
||||
LXD_SUBNET="10.239.141.0/24"
|
||||
fi
|
||||
|
||||
echo "LXD bridge: ${LXD_BRIDGE}"
|
||||
echo "LXD subnet: ${LXD_SUBNET}"
|
||||
|
||||
echo "==> Allow LXD traffic through Docker's DOCKER-USER chain (if present)"
|
||||
if iptables -L DOCKER-USER -n &>/dev/null; then
|
||||
iptables -C DOCKER-USER -i "${LXD_BRIDGE}" -j ACCEPT 2>/dev/null \
|
||||
|| iptables -I DOCKER-USER 1 -i "${LXD_BRIDGE}" -j ACCEPT
|
||||
iptables -C DOCKER-USER -o "${LXD_BRIDGE}" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null \
|
||||
|| iptables -I DOCKER-USER 2 -o "${LXD_BRIDGE}" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||
echo " DOCKER-USER rules added"
|
||||
else
|
||||
echo " No DOCKER-USER chain (Docker may not be managing iptables)"
|
||||
fi
|
||||
|
||||
echo "==> Ensure FORWARD accepts ${LXD_BRIDGE}"
|
||||
iptables -C FORWARD -i "${LXD_BRIDGE}" -j ACCEPT 2>/dev/null \
|
||||
|| iptables -I FORWARD 1 -i "${LXD_BRIDGE}" -j ACCEPT
|
||||
iptables -C FORWARD -o "${LXD_BRIDGE}" -j ACCEPT 2>/dev/null \
|
||||
|| iptables -I FORWARD 1 -o "${LXD_BRIDGE}" -j ACCEPT
|
||||
|
||||
echo "==> Ensure MASQUERADE for ${LXD_SUBNET}"
|
||||
if ! iptables -t nat -C POSTROUTING -s "${LXD_SUBNET}" ! -d "${LXD_SUBNET}" -j MASQUERADE 2>/dev/null; then
|
||||
iptables -t nat -A POSTROUTING -s "${LXD_SUBNET}" ! -d "${LXD_SUBNET}" -j MASQUERADE
|
||||
fi
|
||||
|
||||
echo "==> LXD network: disable per-network firewall, refresh NAT"
|
||||
lxc network set "${LXD_BRIDGE}" ipv4.firewall false
|
||||
lxc network set "${LXD_BRIDGE}" ipv6.firewall false
|
||||
lxc network set "${LXD_BRIDGE}" ipv4.nat false
|
||||
lxc network set "${LXD_BRIDGE}" ipv4.nat true
|
||||
|
||||
echo "==> Restart LXD daemon"
|
||||
if command -v snap >/dev/null 2>&1 && snap list lxd &>/dev/null; then
|
||||
snap restart lxd
|
||||
else
|
||||
systemctl restart lxd || systemctl restart snap.lxd.daemon
|
||||
fi
|
||||
sleep 2
|
||||
|
||||
echo "==> Smoke test (ephemeral container in project snapcraft)"
|
||||
TEST_NAME="lxd-net-test-$$"
|
||||
lxc launch ubuntu:26.04 "${TEST_NAME}" --project snapcraft
|
||||
trap 'lxc delete -f --project snapcraft "${TEST_NAME}" 2>/dev/null || true' EXIT
|
||||
if lxc exec --project snapcraft "${TEST_NAME}" -- curl -fsSI --max-time 15 https://github.com | head -1; then
|
||||
echo "OK: container outbound HTTPS works"
|
||||
else
|
||||
echo "FAIL: container still cannot reach github.com" >&2
|
||||
echo "Consider permanent Docker fix: add \"iptables\": false to /etc/docker/daemon.json and restart docker" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Done. LXD containers should have outbound internet now."
|
||||
Executable
+105
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
# Boot Ubuntu Core 26 amd64 image in QEMU (UEFI, user networking, SSH on localhost:8022).
|
||||
set -euo pipefail
|
||||
|
||||
UC_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
IMG="${UC_ROOT}/artifacts/images-dev/pc.img"
|
||||
FIRMWARE_DIR="${UC_ROOT}/artifacts/firmware"
|
||||
|
||||
OVMF_CODE="${OVMF_CODE:-/usr/share/OVMF/OVMF_CODE_4M.secboot.fd}"
|
||||
OVMF_VARS_TEMPLATE="${OVMF_VARS_TEMPLATE:-/usr/share/OVMF/OVMF_VARS_4M.ms.fd}"
|
||||
OVMF_VARS="${FIRMWARE_DIR}/OVMF_VARS_4M.ms.fd"
|
||||
|
||||
RAM_MB="${RAM_MB:-2048}"
|
||||
SMP="${SMP:-2}"
|
||||
SSH_PORT="${SSH_PORT:-8022}"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: run-qemu.sh [OPTIONS]
|
||||
|
||||
Boot a UC26 pc.img in QEMU.
|
||||
|
||||
Options:
|
||||
--img PATH Disk image (default: artifacts/images-dev/pc.img)
|
||||
--stock Use stock image artifacts/images/pc.img (console-conf / Ubuntu SSO)
|
||||
--ram MB RAM in MiB (default: 2048)
|
||||
--smp N vCPU count (default: 2)
|
||||
--ssh-port PORT Host port forwarded to guest :22 (default: 8022)
|
||||
--reset-uefi-vars Recopy writable OVMF vars from host template
|
||||
-h, --help Show this help
|
||||
|
||||
Dev image (default): SSH after first boot without Ubuntu One:
|
||||
ssh -i config/ssh/smo-dev smo@localhost -p 8022
|
||||
|
||||
Stock image (--stock): complete console-conf in the serial console first.
|
||||
EOF
|
||||
}
|
||||
|
||||
reset_vars=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--stock) IMG="${UC_ROOT}/artifacts/images/pc.img"; shift ;;
|
||||
--img) IMG="$2"; shift 2 ;;
|
||||
--ram) RAM_MB="$2"; shift 2 ;;
|
||||
--smp) SMP="$2"; shift 2 ;;
|
||||
--ssh-port) SSH_PORT="$2"; shift 2 ;;
|
||||
--reset-uefi-vars) reset_vars=true; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ! -f "$IMG" ]]; then
|
||||
echo "Image not found: $IMG" >&2
|
||||
echo "Build dev image: scripts/build-dev-image.sh" >&2
|
||||
echo "Or stock image: scripts/build-stock-image.sh (then run-qemu.sh --stock)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for f in "$OVMF_CODE" "$OVMF_VARS_TEMPLATE"; do
|
||||
if [[ ! -f "$f" ]]; then
|
||||
echo "Missing firmware file: $f" >&2
|
||||
echo "Install with: sudo apt install ovmf qemu-kvm" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if ! command -v qemu-system-x86_64 >/dev/null 2>&1; then
|
||||
echo "qemu-system-x86_64 not found. Install with: sudo apt install qemu-kvm" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$FIRMWARE_DIR"
|
||||
|
||||
if [[ "$reset_vars" == true || ! -f "$OVMF_VARS" ]]; then
|
||||
cp "$OVMF_VARS_TEMPLATE" "$OVMF_VARS"
|
||||
echo "UEFI vars: $OVMF_VARS (fresh copy from template)"
|
||||
fi
|
||||
|
||||
kvm_args=()
|
||||
if [[ -r /dev/kvm ]]; then
|
||||
kvm_args=(-enable-kvm -cpu host)
|
||||
else
|
||||
echo "Warning: /dev/kvm not available; running without KVM" >&2
|
||||
kvm_args=(-cpu max)
|
||||
fi
|
||||
|
||||
echo "Image: $IMG"
|
||||
echo "RAM: ${RAM_MB}M SMP: $SMP SSH: localhost:${SSH_PORT}"
|
||||
|
||||
exec qemu-system-x86_64 \
|
||||
"${kvm_args[@]}" \
|
||||
-smp "$SMP" \
|
||||
-m "$RAM_MB" \
|
||||
-machine q35 \
|
||||
-global ICH9-LPC.disable_s3=1 \
|
||||
-netdev "user,id=net0,hostfwd=tcp::${SSH_PORT}-:22" \
|
||||
-device virtio-net-pci,netdev=net0 \
|
||||
-drive "file=${OVMF_CODE},if=pflash,format=raw,unit=0,readonly=on" \
|
||||
-drive "file=${OVMF_VARS},if=pflash,format=raw,unit=1" \
|
||||
-drive "file=${IMG},if=none,format=raw,id=disk1" \
|
||||
-device virtio-blk-pci,drive=disk1,bootindex=1 \
|
||||
-serial mon:stdio
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env bash
|
||||
# One-time setup: Ubuntu One login + GPG signing key for custom UC26 dev models.
|
||||
set -euo pipefail
|
||||
|
||||
UC_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ENV_FILE="${UC_ROOT}/config/dev-image.env"
|
||||
EXAMPLE="${UC_ROOT}/config/dev-image.env.example"
|
||||
KEY_NAME="${SIGN_KEY_NAME:-salmanoff-dev}"
|
||||
SSH_DIR="${UC_ROOT}/config/ssh"
|
||||
SSH_PRIV="${SSH_DIR}/smo-dev"
|
||||
SSH_PUB="${SSH_DIR}/smo-dev.pub"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: setup-dev-signing.sh [OPTIONS]
|
||||
|
||||
Prepare signing credentials for dangerous-grade salmanoff-dev-amd64 images.
|
||||
|
||||
This script:
|
||||
1. Ensures an SSH keypair exists for the seeded system user (smo).
|
||||
2. Guides snapcraft login + create-key + register-key (interactive).
|
||||
3. Writes config/dev-image.env with your Snap Store account id.
|
||||
|
||||
Options:
|
||||
--key-name NAME Signing key name (default: salmanoff-dev)
|
||||
-h, --help Show this help
|
||||
|
||||
After setup, run:
|
||||
scripts/sign-dev-assertions.sh
|
||||
scripts/build-dev-image.sh
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--key-name) KEY_NAME="$2"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
mkdir -p "$SSH_DIR"
|
||||
|
||||
if [[ ! -f "$SSH_PUB" ]]; then
|
||||
echo "Generating SSH keypair for system user: $SSH_PRIV"
|
||||
ssh-keygen -t ed25519 -N "" -f "$SSH_PRIV" -C "smo-dev@salmanoff"
|
||||
fi
|
||||
|
||||
if ! command -v snapcraft >/dev/null 2>&1; then
|
||||
echo "snapcraft not found. Install with: sudo snap install snapcraft --classic" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Step 1: log in to the Snap Store (Ubuntu One) ==="
|
||||
echo "Run: snapcraft login"
|
||||
echo ""
|
||||
if ! snapcraft whoami >/dev/null 2>&1; then
|
||||
echo "Not logged in yet. Complete 'snapcraft login' in this terminal, then re-run this script." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ACCOUNT_ID="$(snapcraft whoami 2>/dev/null | awk '/^id:/ {print $2}')"
|
||||
if [[ -z "$ACCOUNT_ID" ]]; then
|
||||
echo "Could not read account id from 'snapcraft whoami'" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Account id: $ACCOUNT_ID"
|
||||
|
||||
echo ""
|
||||
echo "=== Step 2: create and register a signing key ==="
|
||||
if ! snap keys 2>/dev/null | awk 'NR>1 {print $1}' | grep -qx "$KEY_NAME"; then
|
||||
echo "No local key named '$KEY_NAME'."
|
||||
echo "Run interactively (you will choose a passphrase):"
|
||||
echo " snapcraft create-key $KEY_NAME"
|
||||
echo " snapcraft register-key $KEY_NAME"
|
||||
echo ""
|
||||
echo "Re-run this script after both commands succeed." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
KEY_FP="$(snap keys 2>/dev/null | awk -v k="$KEY_NAME" '$1 == k {print $2}')"
|
||||
if [[ -z "$KEY_FP" ]]; then
|
||||
echo "Could not read SHA3-384 fingerprint for key '$KEY_NAME'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! snap known --remote account-key "public-key-sha3-384=${KEY_FP}" >/dev/null 2>&1; then
|
||||
echo "Key '$KEY_NAME' exists locally but is not registered in the store."
|
||||
echo "Run: snapcraft register-key $KEY_NAME"
|
||||
echo "Then re-run this script." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Signing key: $KEY_NAME ($KEY_FP)"
|
||||
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
cp "$EXAMPLE" "$ENV_FILE"
|
||||
fi
|
||||
|
||||
tmp="$(mktemp)"
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
case "$line" in
|
||||
ACCOUNT_ID=*) echo "ACCOUNT_ID=${ACCOUNT_ID}" ;;
|
||||
SIGN_KEY_NAME=*) echo "SIGN_KEY_NAME=${KEY_NAME}" ;;
|
||||
SSH_PUBKEY_FILE=*) echo "SSH_PUBKEY_FILE=config/ssh/smo-dev.pub" ;;
|
||||
*) echo "$line" ;;
|
||||
esac
|
||||
done < "$ENV_FILE" > "$tmp"
|
||||
mv "$tmp" "$ENV_FILE"
|
||||
|
||||
echo ""
|
||||
echo "Wrote $ENV_FILE"
|
||||
echo ""
|
||||
echo "Next:"
|
||||
echo " scripts/sign-dev-assertions.sh"
|
||||
echo " scripts/build-dev-image.sh"
|
||||
echo ""
|
||||
echo "SSH to the VM after first boot:"
|
||||
echo " ssh -i ${SSH_PRIV} smo@localhost -p 8022"
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env bash
|
||||
# Sign dangerous-grade model + system-user assertions for salmanoff-dev-amd64.
|
||||
set -euo pipefail
|
||||
|
||||
UC_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ENV_FILE="${UC_ROOT}/config/dev-image.env"
|
||||
MODEL_TEMPLATE="${UC_ROOT}/models/salmanoff-dev-amd64.model.json"
|
||||
ASSERT_DIR="${UC_ROOT}/assertions"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: sign-dev-assertions.sh [OPTIONS]
|
||||
|
||||
Sign the dev model assertion and a system-user assertion (SSH key, no Ubuntu One).
|
||||
|
||||
Requires config/dev-image.env (see scripts/setup-dev-signing.sh).
|
||||
|
||||
Outputs:
|
||||
models/salmanoff-dev-amd64.model
|
||||
assertions/smo-system-user.assert (account + account-key + system-user chain)
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
echo "Missing $ENV_FILE — run scripts/setup-dev-signing.sh first" >&2
|
||||
exit 1
|
||||
fi
|
||||
# shellcheck source=/dev/null
|
||||
source "$ENV_FILE"
|
||||
|
||||
: "${ACCOUNT_ID:?ACCOUNT_ID not set in $ENV_FILE}"
|
||||
: "${SIGN_KEY_NAME:?SIGN_KEY_NAME not set in $ENV_FILE}"
|
||||
: "${SYSTEM_USER_NAME:=smo}"
|
||||
: "${SYSTEM_USER_EMAIL:=smo-dev@salmanoff}"
|
||||
: "${SSH_PUBKEY_FILE:=config/ssh/smo-dev.pub}"
|
||||
: "${MODEL_NAME:=salmanoff-dev-amd64}"
|
||||
|
||||
SSH_PUBKEY_PATH="${UC_ROOT}/${SSH_PUBKEY_FILE}"
|
||||
if [[ ! -f "$SSH_PUBKEY_PATH" ]]; then
|
||||
echo "SSH public key not found: $SSH_PUBKEY_PATH" >&2
|
||||
echo "Run scripts/setup-dev-signing.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
KEY_FP="$(snap keys 2>/dev/null | awk -v k="$SIGN_KEY_NAME" '$1 == k {print $2}')"
|
||||
if [[ -z "$KEY_FP" ]]; then
|
||||
echo "Signing key '$SIGN_KEY_NAME' not found. Run scripts/setup-dev-signing.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! snap known --remote account-key "public-key-sha3-384=${KEY_FP}" >/dev/null 2>&1; then
|
||||
echo "Key '$SIGN_KEY_NAME' is not registered in the Snap Store." >&2
|
||||
echo "Run: snapcraft register-key $SIGN_KEY_NAME" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export GPG_TTY="${GPG_TTY:-$(tty)}"
|
||||
|
||||
mkdir -p "$ASSERT_DIR" "${UC_ROOT}/models"
|
||||
|
||||
TIMESTAMP="$(date -Iseconds --utc)"
|
||||
MODEL_JSON="$(mktemp)"
|
||||
MODEL_OUT="${UC_ROOT}/models/${MODEL_NAME}.model"
|
||||
SYSTEM_USER_JSON="$(mktemp)"
|
||||
SYSTEM_USER_OUT="${ASSERT_DIR}/smo-system-user.assert"
|
||||
|
||||
sed -e "s/@ACCOUNT_ID@/${ACCOUNT_ID}/g" \
|
||||
-e "s/@TIMESTAMP@/${TIMESTAMP}/g" \
|
||||
"$MODEL_TEMPLATE" > "$MODEL_JSON"
|
||||
|
||||
echo "Signing model → $MODEL_OUT"
|
||||
snap sign -k "$SIGN_KEY_NAME" "$MODEL_JSON" > "$MODEL_OUT"
|
||||
|
||||
SSH_PUB="$(tr -d '\n' < "$SSH_PUBKEY_PATH")"
|
||||
cat > "$SYSTEM_USER_JSON" <<EOF
|
||||
{
|
||||
"type": "system-user",
|
||||
"authority-id": "${ACCOUNT_ID}",
|
||||
"brand-id": "${ACCOUNT_ID}",
|
||||
"series": ["16"],
|
||||
"models": ["${MODEL_NAME}"],
|
||||
"name": "Salmanoff Dev",
|
||||
"username": "${SYSTEM_USER_NAME}",
|
||||
"email": "${SYSTEM_USER_EMAIL}",
|
||||
"ssh-keys": ["${SSH_PUB}"],
|
||||
"since": "2026-06-21T00:00:00+00:00",
|
||||
"until": "2064-06-21T00:00:00+00:00"
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Signing system-user chain → $SYSTEM_USER_OUT"
|
||||
snap sign -k "$SIGN_KEY_NAME" "$SYSTEM_USER_JSON" --chain > "$SYSTEM_USER_OUT"
|
||||
|
||||
rm -f "$MODEL_JSON" "$SYSTEM_USER_JSON"
|
||||
|
||||
echo ""
|
||||
echo "Model authority/brand: $ACCOUNT_ID"
|
||||
echo "System user: ${SYSTEM_USER_NAME} (SSH pubkey from ${SSH_PUBKEY_FILE})"
|
||||
echo "Signing key: ${SIGN_KEY_NAME} (${KEY_FP})"
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install and smoke-test the salmanoff snap (strict by default).
|
||||
set -euo pipefail
|
||||
|
||||
UC_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SNAP_DIR="${UC_ROOT}/snaps/salmanoff"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: test-snap.sh [OPTIONS] [-- ARGS_FOR_SALMANOFF...]
|
||||
|
||||
Install the newest salmanoff_*.snap and run a smoke test.
|
||||
|
||||
Options:
|
||||
--snap PATH Specific .snap file (default: newest in snaps/salmanoff/)
|
||||
--no-install Skip install; only run snap run (snap already installed)
|
||||
--devmode Install with --devmode (overrides strict snap metadata)
|
||||
-- ARGS Passed to salmanoff (default: --help)
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
}
|
||||
|
||||
snap_file=""
|
||||
do_install=true
|
||||
use_devmode=false
|
||||
extra_args=(--help)
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--snap) snap_file="$2"; shift 2 ;;
|
||||
--no-install) do_install=false; shift ;;
|
||||
--devmode) use_devmode=true; shift ;;
|
||||
--) shift; extra_args=("$@"); break ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) extra_args=("$@"); break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$snap_file" ]]; then
|
||||
snap_file="$(ls -1t "${SNAP_DIR}"/*.snap 2>/dev/null | head -1 || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "$snap_file" || ! -f "$snap_file" ]]; then
|
||||
if [[ "$do_install" == true ]]; then
|
||||
echo "No .snap found. Build first: ./scripts/build-snap.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Newest .snap: $snap_file"
|
||||
fi
|
||||
|
||||
connect_plugs() {
|
||||
local plug
|
||||
for plug in network network-bind hardware-observe camera media-control; do
|
||||
if snap interfaces 2>/dev/null | grep -q "salmanoff:${plug}"; then
|
||||
if ! snap interfaces 2>/dev/null | grep -q "salmanoff:${plug}.*-"; then
|
||||
echo "Connecting plug: ${plug}"
|
||||
sudo snap connect "salmanoff:${plug}" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
if [[ "$do_install" == true ]]; then
|
||||
install_flags=(--dangerous)
|
||||
if [[ "$use_devmode" == true ]]; then
|
||||
install_flags+=(--devmode)
|
||||
fi
|
||||
sudo snap install "${install_flags[@]}" "$snap_file"
|
||||
connect_plugs
|
||||
fi
|
||||
|
||||
echo "Running: snap run salmanoff ${extra_args[*]}"
|
||||
snap run salmanoff "${extra_args[@]}"
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
# Wrapper: plugin search path, OpenCL ICD, Rusticl llvmpipe, Gallium DRI, libcamera IPA.
|
||||
set -euo pipefail
|
||||
|
||||
shopt -s nullglob
|
||||
lib_dirs=("$SNAP/usr/lib/"*-linux-gnu)
|
||||
if ((${#lib_dirs[@]} > 0)); then
|
||||
lib_dir="${lib_dirs[0]}"
|
||||
else
|
||||
lib_dir="$SNAP/usr/lib"
|
||||
fi
|
||||
|
||||
export RUSTICL_ENABLE="${RUSTICL_ENABLE:-llvmpipe}"
|
||||
export OCL_ICD_VENDORS="${OCL_ICD_VENDORS:-$SNAP/etc/OpenCL/vendors}"
|
||||
export LIBGL_DRIVERS_PATH="${LIBGL_DRIVERS_PATH:-${lib_dir}/dri}"
|
||||
export LIBCAMERA_IPA_MODULE_PATH="${LIBCAMERA_IPA_MODULE_PATH:-${lib_dir}/libcamera}"
|
||||
export LIBCAMERA_IPA_CONFIG_PATH="${LIBCAMERA_IPA_CONFIG_PATH:-$SNAP/usr/share/libcamera/ipa}"
|
||||
export LD_LIBRARY_PATH="${lib_dir}:${SNAP}/usr/lib:${LD_LIBRARY_PATH:-}"
|
||||
|
||||
exec "${SNAP}/usr/bin/salmanoff" -p "${lib_dir}" "$@"
|
||||
@@ -0,0 +1,109 @@
|
||||
# Salmanoff snap for Ubuntu Core 26 (salmanoff-dev-amd64 dangerous model).
|
||||
# Build from distro/ubuntuCore: ../../scripts/build-snap.sh
|
||||
name: salmanoff
|
||||
version: "0.01.001"
|
||||
summary: Salmanoff cognitive robotics runtime
|
||||
description: |
|
||||
Sensor management runtime (SMO) with Livox LiDAR, libcamera, and OpenCL
|
||||
stimulus paths. Packaged for iterative snap-first development before seeding
|
||||
into an Ubuntu Core image.
|
||||
|
||||
grade: devel
|
||||
confinement: strict
|
||||
base: core26
|
||||
|
||||
lint:
|
||||
ignore:
|
||||
- unused-library:
|
||||
- usr/lib/*/libcoreComp.so*
|
||||
- usr/lib/*/liblcameraBuff.so*
|
||||
- usr/lib/*/liblcameraDev.so*
|
||||
- usr/lib/*/liblivoxGen1.so*
|
||||
- usr/lib/*/liblivoxProto1.so*
|
||||
- usr/lib/*/libattachmentSupport.so*
|
||||
- usr/lib/*/libspinscale.so*
|
||||
- usr/lib/*/libRusticlOpenCL.so*
|
||||
- usr/lib/*/libboost_log_setup.so*
|
||||
- usr/lib/*/libunwind*.so*
|
||||
- usr/lib/*/liburing-ffi.so*
|
||||
- usr/lib/*/libicu*.so*
|
||||
- usr/lib/*/liblttng*.so*
|
||||
|
||||
apps:
|
||||
salmanoff:
|
||||
command: bin/salmanoff-launch
|
||||
plugs:
|
||||
- network
|
||||
- network-bind
|
||||
- hardware-observe
|
||||
- camera
|
||||
- media-control
|
||||
environment:
|
||||
RUSTICL_ENABLE: llvmpipe
|
||||
OCL_ICD_VENDORS: $SNAP/etc/OpenCL/vendors
|
||||
|
||||
layout:
|
||||
/etc/OpenCL:
|
||||
bind: $SNAP/etc/OpenCL
|
||||
/usr/lib/clc:
|
||||
bind: $SNAP/usr/lib/clc
|
||||
/usr/lib/x86_64-linux-gnu/libcamera:
|
||||
bind: $SNAP/usr/lib/x86_64-linux-gnu/libcamera
|
||||
/usr/share/libcamera:
|
||||
bind: $SNAP/usr/share/libcamera
|
||||
|
||||
parts:
|
||||
launch:
|
||||
plugin: nil
|
||||
override-build: |
|
||||
install -Dm755 "$CRAFT_PROJECT_DIR/bin/salmanoff-launch" \
|
||||
"$CRAFT_PART_INSTALL/bin/salmanoff-launch"
|
||||
|
||||
salmanoff:
|
||||
after: [launch]
|
||||
plugin: cmake
|
||||
# SMO repo root (distro/ubuntuCore/snaps/salmanoff -> ../../../..)
|
||||
source: ../../..
|
||||
source-type: local
|
||||
source-submodules:
|
||||
- libspinscale
|
||||
build-packages:
|
||||
- build-essential
|
||||
- cmake
|
||||
- ninja-build
|
||||
- pkg-config
|
||||
- flex
|
||||
- bison
|
||||
- git
|
||||
- libboost1.88-dev
|
||||
- libboost-system1.88-dev
|
||||
- libboost-log1.88-dev
|
||||
- liburing-dev
|
||||
- libcamera-dev
|
||||
- ocl-icd-opencl-dev
|
||||
stage-packages:
|
||||
- libboost-system1.88.0
|
||||
- libboost-log1.88.0
|
||||
- liburing2
|
||||
- libcamera0.7
|
||||
- libcamera-ipa
|
||||
- ocl-icd-libopencl1
|
||||
- mesa-opencl-icd
|
||||
- libclc-20
|
||||
- mesa-libgallium
|
||||
- libgl1-mesa-dri
|
||||
cmake-parameters:
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
- -DBoost_DIR=/usr/lib/x86_64-linux-gnu/cmake/Boost-1.88.0
|
||||
- -DENABLE_TESTS=OFF
|
||||
- -DENABLE_LIB_xcbXorg=OFF
|
||||
- -DENABLE_LIB_lcameraDev=ON
|
||||
- -DENABLE_STIMBUFFAPI_lcameraBuff=ON
|
||||
- -DENABLE_LCAMERADEV_TOOLS=OFF
|
||||
- -DCOMPILE_PCL_TOOLS=OFF
|
||||
override-pull: |
|
||||
craftctl default
|
||||
git -C "$CRAFT_PART_SRC" submodule update --init libspinscale
|
||||
prime:
|
||||
- -usr/lib/x86_64-linux-gnu/liburing-ffi.so*
|
||||
@@ -0,0 +1,276 @@
|
||||
# Salmanoff on Ubuntu Core — Plan (derived from Yocto work)
|
||||
|
||||
## Purpose of this document
|
||||
|
||||
This plan is **not** a translation of an original Yocto design doc. It reflects **what we actually built and validated** in the Yocto path, reframed as goals for Ubuntu Core. Use it as primary context when scaffolding snaps, gadget/model, and dev VM workflow.
|
||||
|
||||
When done, snap definitions and support files land in the SMO repo under:
|
||||
|
||||
```
|
||||
smo/distro/ubuntuCore/
|
||||
```
|
||||
|
||||
(parallel to the existing `smo/distro/yocto/meta-salmanoff/`).
|
||||
|
||||
---
|
||||
|
||||
## What we proved in Yocto (facts, not aspirations)
|
||||
|
||||
We shipped a **minimal headless image** that runs the Salmanoff (SMO) cognitive robotics runtime on **QEMU x86-64**, bridged onto a lab LAN, talking to a **Livox LiDAR** on a fixed IP.
|
||||
|
||||
### Lab network (canonical — carry forward unchanged)
|
||||
|
||||
| Host | IP | Role |
|
||||
|---|---|---|
|
||||
| Laptop / gateway | `10.42.0.1` | Gateway + DNS |
|
||||
| RPi5 (production target) | `10.42.0.2` | Future production node |
|
||||
| **Dev guest (QEMU / UC VM)** | **`10.42.0.16`** | SMO dev runtime |
|
||||
| Livox Avia | `10.42.0.139` | LiDAR sensor |
|
||||
|
||||
### Runtime configuration we validated
|
||||
|
||||
- **Body file**: `yocto-qemu-x86-headless.dapss` — Livox-only, headless (no win0/camera), `smo-ip=10.42.0.16`, includes `avia0.dapss`.
|
||||
- **SMO build profile** (CMake):
|
||||
- `RelWithDebInfo`
|
||||
- `ENABLE_LIB_lcameraDev=ON`, `ENABLE_STIMBUFFAPI_lcameraBuff=ON` (libcamera + OpenCL YUV path compiled in; no camera passthrough needed in QEMU)
|
||||
- `ENABLE_LIB_xcbXorg=OFF`, `COMPILE_PCL_TOOLS=OFF`, `ENABLE_TESTS=OFF`, `ENABLE_LCAMERADEV_TOOLS=OFF`
|
||||
- Git submodules required (`libspinscale`, etc.)
|
||||
- **Runtime command shape** (approximate):
|
||||
```bash
|
||||
salmanoff -d /usr/share/salmanoff/devices/bodies/body_yocto_qemu_x86_headless.daps -p /usr/lib -v
|
||||
```
|
||||
(exact installed `.daps` filename follows DAPSS build output naming)
|
||||
|
||||
### Platform services / packages we pulled into the image
|
||||
|
||||
| Need | What we used in Yocto |
|
||||
|---|---|
|
||||
| SMO runtime + libs | `salmanoff` recipe (`libspinscale`, `liblcameraDev`, `liblcameraBuff`, `liblivoxGen1`, …) |
|
||||
| Boost | Pinned **1.86** (shared `boost-system`; must stay **< 1.89**) |
|
||||
| liburing | Runtime dep |
|
||||
| libcamera + v4l-utils | Present on image; camera optional at runtime |
|
||||
| OpenCL | Mesa Rusticl via `opencl` distro feature; **needs `RUSTICL_ENABLE=llvmpipe`** or platform shows 0 devices |
|
||||
| OpenCL verify | `clinfo` (also discovered **256M RAM OOMs** OpenCL probe — use **≥ 2G** for dev VM) |
|
||||
| Network | Static `eth0`: `10.42.0.16/24`, gw `10.42.0.1` |
|
||||
| Dev access | SSH (openssh on Yocto image) |
|
||||
| Kernel probe | `rseqsliceprobe` + kernel config append for rseq slice extension |
|
||||
| QEMU dev | Host bridge `br-smo`, `runqemu` with `bridge=br-smo`, `nographic`, `-m 2048` |
|
||||
|
||||
### Repo / packaging decisions already made in SMO
|
||||
|
||||
- Yocto layer vendored at `smo/distro/yocto/meta-salmanoff/` (committed on `clast`).
|
||||
- SMO source changes on `clast`: headless body, flex/bison repo-relative `#line` paths, etc.
|
||||
- Fetch uses private Gitea (`hayodea/salmanoff`, branch `clast`, submodules via `libspinscale` on separate SSH host alias).
|
||||
|
||||
---
|
||||
|
||||
## Conceptual goals (portable to Ubuntu Core)
|
||||
|
||||
These are the **intent** items — independent of BitBake:
|
||||
|
||||
1. **Repeatable dev environment** — headless guest on `10.42.0.16`, reachable from laptop, Livox at `10.42.0.139`.
|
||||
2. **SMO as first-class payload** — not “install deps manually”; runtime + `.daps` bodies + plugin `.so`s packaged and startable.
|
||||
3. **Same CMake feature profile** as Yocto (lcamera + OpenCL enabled; xcb/PCL tools off).
|
||||
4. **OpenCL that actually works headless** — Rusticl/llvmpipe with env var set before SMO starts.
|
||||
5. **libcamera stack present** — libraries available even when no camera device is passed through (QEMU/UC dev).
|
||||
6. **Boost version constraint honored** — do not silently pick Boost ≥ 1.89.
|
||||
7. **Production path to RPi5** — same snap(s), different body/IP (`10.42.0.2`), gadget/kernel for Pi hardware.
|
||||
8. **Distro metadata lives in SMO** — `distro/ubuntuCore/` committed back into SMO when stable (mirrors Yocto layout).
|
||||
|
||||
---
|
||||
|
||||
## Yocto → Ubuntu Core mapping
|
||||
|
||||
| Yocto (what we did) | Ubuntu Core (target) |
|
||||
|---|---|
|
||||
| `meta-salmanoff/` layer | `distro/ubuntuCore/` — snaps, gadget, model, helper scripts |
|
||||
| `salmanoff-image.bb` | **Model assertion** + `core` + gadget snap + app snaps on seeded image |
|
||||
| `salmanoff.bb` recipe | **`salmanoff` snap** (`snapcraft.yaml`: cmake build, submodules, organize libs + daps) |
|
||||
| `IMAGE_INSTALL` | Snap `stage-packages` / parts / content snaps (`mesa`, libcamera deps) |
|
||||
| `salmanoff-rusticl-env` recipe | Snap `environment:` block or wrapper script: `RUSTICL_ENABLE=llvmpipe` |
|
||||
| `init-ifupdown` static IP | **Netplan** via gadget default config, `network-setup` snap, or `system-connections` on UC |
|
||||
| `runqemu-salmanoff-bridge` | **Dev VM script**: UC image in QEMU/LXD/multipass on `br-smo`, static `10.42.0.16`, 2G RAM |
|
||||
| `DISTRO_FEATURES += opencl` | Ensure Mesa/OpenCL ICD available to snap (layout + plugs or bundled libs) |
|
||||
| `DEPENDS` / `RDEPENDS` | Snapcraft `build-packages`, `stage-packages`, `slots`/`plugs`, `layout:` for `/usr/lib` ICD paths |
|
||||
| `gitsm://` + `SRCREV` | Snapcraft `source` + `source-submodules` or `override-pull` git submodule update |
|
||||
| `yocto-qemu-x86-headless.dapss` | Installed by salmanoff snap; UC-specific body can be `ubuntu-core-x86-headless.dapss` later |
|
||||
| `rseqsliceprobe` | Validate UC kernel/base; custom kernel snap only if probe fails on stock UC kernel |
|
||||
| SSH on image | UC: serial console, `ssh-keys` in model, or dedicated snap — **don’t assume openssh like Yocto** |
|
||||
| Boost 1.86 pin | Pin in snapcraft (`stage-packages` version) or build Boost part from source |
|
||||
| `RelWithDebInfo` + debug maps | Snapcraft `override-build` cmake flags; consider `debug` snap or stripped separate artifact |
|
||||
|
||||
---
|
||||
|
||||
## Proposed Ubuntu Core architecture
|
||||
|
||||
```
|
||||
Host (10.42.0.1)
|
||||
└── bridge br-smo (10.42.0.0/24)
|
||||
└── UC dev VM / RPi5 (10.42.0.16 or .2)
|
||||
├── snap: core (or core24)
|
||||
├── snap: gadget-<platform> (boot, partitions, default netplan)
|
||||
├── snap: salmanoff (daemon — main payload)
|
||||
└── optional: mesa-support / libcamera-support content snaps if not bundled
|
||||
```
|
||||
|
||||
### `salmanoff` snap (primary deliverable)
|
||||
|
||||
**Type**: likely `daemon` (simple) or `daemon` (notify) once startup semantics are known.
|
||||
|
||||
**Build** (mirror Yocto recipe intent):
|
||||
|
||||
- Source: SMO repo `clast` (submodules!)
|
||||
- CMake options: same as Yocto `EXTRA_OECMAKE` block
|
||||
- Install: `salmanoff` binary, `lib*.so*`, `/usr/share/salmanoff/devices/**`
|
||||
- Apps: main daemon + optional `salmanoff.probe` for debugging
|
||||
|
||||
**Runtime environment**:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
RUSTICL_ENABLE: llvmpipe
|
||||
```
|
||||
|
||||
**Interfaces to evaluate early** (exact set TBD by confinement testing):
|
||||
|
||||
- `network` / `network-bind` — Livox UDP/TCP to `10.42.0.139`
|
||||
- `hardware-observe` — may be needed for camera discovery on Pi
|
||||
- `camera` — for libcamera on production Pi
|
||||
- `opengl` — if Mesa GL stack needed beyond OpenCL ICD
|
||||
- `raw-usb` / `serial-port` — only if Livox path requires it on some platforms
|
||||
- `home` — usually **avoid**; use `$SNAP_DATA` for state
|
||||
|
||||
**Layouts** (likely needed):
|
||||
|
||||
- OpenCL ICD loader expects `/etc/OpenCL/vendors` or known `LD_LIBRARY_PATH`
|
||||
- May need `layout:` bind mounts for Mesa `libRusticlOpenCL.so` / gallium stack
|
||||
|
||||
### Platform snaps
|
||||
|
||||
| Platform | Gadget | Notes |
|
||||
|---|---|---|
|
||||
| **QEMU x86 dev** | `pc` or custom gadget | Static IP `10.42.0.16`, 2G RAM, bridged NIC |
|
||||
| **RPi5 production** | `pi` gadget (22+ arm64) | Static IP `10.42.0.2`, libcamera, Livox |
|
||||
|
||||
### Bodies / config
|
||||
|
||||
- **Dev (UC VM)**: start from Yocto body or add `ubuntu-core-x86-headless.dapss` with same Livox-only intent and `smo-ip=10.42.0.16`.
|
||||
- **Production (RPi5)**: reuse/adapt existing `rpi5-persys-headless.dapss` pattern with `smo-ip=10.42.0.2`.
|
||||
|
||||
Body files stay in SMO `devices/bodies/`; snap selects default via config or snap config hook.
|
||||
|
||||
---
|
||||
|
||||
## Phased plan (recommended order)
|
||||
|
||||
### Phase 0 — Repo scaffold
|
||||
|
||||
- [ ] New repo (or `distro/ubuntuCore/` in SMO) with `snapcraft.yaml` skeleton
|
||||
- [ ] Document lab IP table and bridge prerequisites (port `runqemu-salmanoff-bridge` concepts)
|
||||
- [ ] Link to SMO `clast` branch + submodule SSH host requirements
|
||||
|
||||
### Phase 1 — Build snap on host (no hardware)
|
||||
|
||||
- [ ] `snapcraft` / `craft` builds SMO with same CMake profile as Yocto
|
||||
- [ ] Confirm submodule pull (`libspinscale`) in clean CI/local environment
|
||||
- [ ] Resolve Boost < 1.89 (stage-package pin or source part)
|
||||
- [ ] Package `.daps` + shared libs; verify `salmanoff -v` / dry-run in snap run environment
|
||||
|
||||
### Phase 2 — OpenCL + libcamera in confinement
|
||||
|
||||
- [ ] Replicate Yocto OpenCL stack inside snap (bundled vs system Mesa)
|
||||
- [ ] Confirm `RUSTICL_ENABLE=llvmpipe` → `clinfo` shows ≥1 device **inside snap**
|
||||
- [ ] Confirm `liblcameraDev` / `liblcameraBuff` load; no crash without camera device
|
||||
- [ ] Document RAM requirement (≥ 2G for dev VM)
|
||||
|
||||
### Phase 3 — UC dev VM on lab LAN
|
||||
|
||||
- [ ] Boot Ubuntu Core x86 VM bridged to `br-smo`
|
||||
- [ ] Static IP **10.42.0.16** (netplan/gadget)
|
||||
- [ ] Install seeded `salmanoff` snap (devmode first, then strict)
|
||||
- [ ] Ping gateway; reach Livox at `10.42.0.139`
|
||||
- [ ] Run headless body; validate Livox traffic / SMO logs
|
||||
|
||||
### Phase 4 — Production path (RPi5)
|
||||
|
||||
- [ ] Model assertion for Pi5 + arm64 core
|
||||
- [ ] Gadget with `10.42.0.2` netplan
|
||||
- [ ] libcamera plug + Pi body file
|
||||
- [ ] OTA refresh via snap channels
|
||||
|
||||
### Phase 5 — Commit back to SMO
|
||||
|
||||
- [ ] Move stable `distro/ubuntuCore/` into SMO repo
|
||||
- [ ] Commit on `clast` (or dedicated branch); do **not** block on unfinished WIP snaps
|
||||
|
||||
---
|
||||
|
||||
## Known constraints & pitfalls (from Yocto — expect repeats)
|
||||
|
||||
1. **OpenCL**: platform visible but **0 devices** until `RUSTICL_ENABLE=llvmpipe`.
|
||||
2. **Memory**: 256M insufficient for Rusticl/`clinfo`; use **2G** for dev VM.
|
||||
3. **Boost**: must stay **< 1.89** (shared `boost-system` linkage).
|
||||
4. **Submodules**: `libspinscale` is mandatory; fetch must be recursive; separate SSH host alias on Gitea.
|
||||
5. **lcamera without hardware**: compile and ship libs; no QEMU camera passthrough required for basic bring-up.
|
||||
6. **PCL / xcb**: keep off unless explicitly needed (MPI/cmake pain in Yocto).
|
||||
7. **Private git**: snapcraft build needs SSH keys / credentials for Gitea fetch (or vendored source tarball part).
|
||||
8. **Strict confinement**: biggest unknown vs Yocto — plan devmode → strict iteration; interfaces will take multiple passes.
|
||||
9. **rseq kernel feature**: validate on UC base; custom kernel snap is expensive — only if probe fails.
|
||||
|
||||
---
|
||||
|
||||
## Success criteria (match Yocto milestone)
|
||||
|
||||
Minimum “we’re at parity” for UC dev:
|
||||
|
||||
- [ ] UC VM at `10.42.0.16` on `10.42.0.0/24` via host bridge
|
||||
- [ ] `salmanoff` snap installed and daemon running (or manual `snap run` equivalent)
|
||||
- [ ] Headless Livox body loaded; SMO stable with Livox on `10.42.0.139`
|
||||
- [ ] OpenCL initialized (llvmpipe); lcamera libs present
|
||||
- [ ] Snap definitions committed under `smo/distro/ubuntuCore/`
|
||||
|
||||
Stretch (production):
|
||||
|
||||
- [ ] Same snap on RPi5 at `10.42.0.2` with Pi-appropriate body
|
||||
|
||||
---
|
||||
|
||||
## Explicit non-goals (for now)
|
||||
|
||||
- Re-implement Yocto layer / BitBake in UC repo
|
||||
- Camera passthrough in QEMU/UC x86 dev
|
||||
- PCL tools / xcb window stack
|
||||
- lcameraDev probe tools (need test support libs — off in Yocto too)
|
||||
- Full OTA/signing production pipeline (Phase 4+)
|
||||
|
||||
---
|
||||
|
||||
## Reference: Yocto artifacts in SMO repo
|
||||
|
||||
When unsure, read the working Yocto implementation:
|
||||
|
||||
```
|
||||
smo/distro/yocto/meta-salmanoff/
|
||||
conf/layer.conf
|
||||
conf/salmanoff-local.inc # opencl distro feature
|
||||
recipes-salmanoff/salmanoff/salmanoff.bb
|
||||
recipes-core/images/salmanoff-image.bb
|
||||
recipes-core/init-ifupdown/... # 10.42.0.16 static IP
|
||||
recipes-support/salmanoff-rusticl-env/
|
||||
scripts/runqemu-salmanoff-bridge
|
||||
|
||||
smo/devices/bodies/yocto-qemu-x86-headless.dapss
|
||||
```
|
||||
|
||||
SMO branch: **`clast`** on `hayodea/salmanoff` (Gitea).
|
||||
|
||||
---
|
||||
|
||||
## Instructions for the LLM in the Ubuntu Core repo
|
||||
|
||||
1. **Treat Yocto work as validated reference**, not something to re-derive from scratch.
|
||||
2. **Preserve lab IP plan and CMake profile** unless user says otherwise.
|
||||
3. **Start with snapcraft build on host**, then UC VM, then strict confinement — same order we used (build → package QA → runtime).
|
||||
4. **Plan for `distro/ubuntuCore/` in SMO** as the final home of snaps/gadget/model/scripts.
|
||||
5. **Ask before** committing snaps into SMO; user will commit when done.
|
||||
6. **Do not** assume UC has openssh or classic Ubuntu package management — everything is snaps + interfaces.
|
||||
Reference in New Issue
Block a user