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:
2026-06-25 23:01:52 -04:00
parent 44d12eeb9e
commit 038d59f972
17 changed files with 1295 additions and 0 deletions
+133
View File
@@ -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"
+78
View File
@@ -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
View File
@@ -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"
+12
View File
@@ -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"
+68
View File
@@ -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."
+105
View File
@@ -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
View File
@@ -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
View File
@@ -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})"
+74
View File
@@ -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[@]}"