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:
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[@]}"
|
||||
Reference in New Issue
Block a user