171 lines
7.4 KiB
Bash
171 lines
7.4 KiB
Bash
#!/bin/bash
|
|
set -Eeuo pipefail
|
|
|
|
STATUS=/boot/MSCSTAT.TXT
|
|
PACKAGE_LOG=/boot/MSCPKG.TXT
|
|
PACKAGE_INVENTORY=/boot/MSCPKGS.TSV
|
|
DEPLOY_LOG=/boot/MSCDEPLOY.TXT
|
|
CONFIG=/boot/MSCCFG.BIN
|
|
BUNDLE=/opt/matrix-image-bootstrap/app/MSCBOOT.TGZ
|
|
META=/boot/MSCMETA.JSN
|
|
WORK=/run/matrix-image-provision
|
|
KERNEL_ROOT=/opt/matrix-image-bootstrap/axp313a
|
|
KERNEL_ARCHIVE=$KERNEL_ROOT/axp313a-kernel-6.1.31-matrix-axp313a1.tar.gz
|
|
KERNEL_METADATA=$KERNEL_ROOT/METADATA.json
|
|
KERNEL_WORK=/var/tmp/matrix-axp313a-image-install
|
|
|
|
status() {
|
|
printf '%s\n' "$1" > "$STATUS"
|
|
sync "$STATUS" || true
|
|
}
|
|
|
|
failed() {
|
|
code=$?
|
|
trap - ERR
|
|
rm -rf -- /var/tmp/matrix-axp313a-image-install
|
|
status "FAILED: stage=${STAGE:-starting}; code=$code; line=${BASH_LINENO[0]:-unknown}. Power off, remove the TF card, and inspect this file on Windows."
|
|
exit "$code"
|
|
}
|
|
trap failed ERR
|
|
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
status "FAILED: the one-time provisioner did not run as root."
|
|
exit 1
|
|
fi
|
|
|
|
STAGE=validate
|
|
status "RUNNING: validating the offline image payload."
|
|
rm -rf -- "$WORK"
|
|
install -d -m 0700 "$WORK"
|
|
python3 - "$META" "$BUNDLE" "$KERNEL_ARCHIVE" "$KERNEL_METADATA" <<'PY'
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
meta = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
bundle = Path(sys.argv[2])
|
|
kernel_artifact = Path(sys.argv[3])
|
|
kernel = json.loads(Path(sys.argv[4]).read_text(encoding="utf-8"))
|
|
assert meta == {
|
|
"format_version": 3,
|
|
"product": "matrix-screen-controller-walnutpi",
|
|
"software_version": meta["software_version"],
|
|
"bundle_path": "/opt/matrix-image-bootstrap/app/MSCBOOT.TGZ",
|
|
"bundle_bytes": meta["bundle_bytes"],
|
|
"bundle_sha256": meta["bundle_sha256"],
|
|
"kernel_release": "6.1.31-matrix-axp313a1",
|
|
"kernel_artifact": "axp313a-kernel-6.1.31-matrix-axp313a1.tar.gz",
|
|
"kernel_artifact_bytes": meta["kernel_artifact_bytes"],
|
|
"kernel_artifact_sha256": meta["kernel_artifact_sha256"],
|
|
"kernel_unpacked_file_bytes": meta["kernel_unpacked_file_bytes"],
|
|
"boot_required_bytes": meta["boot_required_bytes"],
|
|
"boot_safety_bytes": 32 * 1024 * 1024,
|
|
}
|
|
assert isinstance(meta["boot_required_bytes"], int) and meta["boot_required_bytes"] > 0
|
|
assert bundle.stat().st_size == meta["bundle_bytes"]
|
|
digest = hashlib.sha256()
|
|
with bundle.open("rb") as handle:
|
|
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
digest.update(chunk)
|
|
assert digest.hexdigest() == meta["bundle_sha256"]
|
|
assert kernel["schema_version"] == 1
|
|
assert kernel["architecture"] == "aarch64"
|
|
assert kernel["kernel_release"] == meta["kernel_release"]
|
|
assert kernel["artifact"] == meta["kernel_artifact"]
|
|
assert kernel["artifact_bytes"] == meta["kernel_artifact_bytes"] == kernel_artifact.stat().st_size
|
|
assert kernel["artifact_sha256"] == meta["kernel_artifact_sha256"]
|
|
assert kernel["unpacked_file_bytes"] == meta["kernel_unpacked_file_bytes"]
|
|
digest = hashlib.sha256()
|
|
with kernel_artifact.open("rb") as handle:
|
|
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
digest.update(chunk)
|
|
assert digest.hexdigest() == meta["kernel_artifact_sha256"]
|
|
PY
|
|
tar -xzf "$BUNDLE" -C "$WORK"
|
|
cd "$WORK/project/核桃派软件源代码"
|
|
PYTHONPATH=. python3 scripts/kernel_artifact.py --kernel "$KERNEL_ROOT" >/dev/null
|
|
cd "$WORK/project/离线依赖/其他依赖/aarch64-py311"
|
|
sha256sum -c SHA256SUMS
|
|
cd "$WORK/project/离线依赖/其他依赖/debian12-aarch64"
|
|
sha256sum -c SHA256SUMS
|
|
|
|
STAGE=packages
|
|
status "RUNNING: installing offline Debian packages."
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
dpkg-query -W -f='${db:Status-Abbrev}\t${binary:Package}\t${Version}\n' >"$PACKAGE_INVENTORY"
|
|
if ! dpkg -i "$WORK"/project/离线依赖/其他依赖/debian12-aarch64/*.deb >"$PACKAGE_LOG" 2>&1; then
|
|
status "FAILED: stage=packages; offline Debian package installation failed. Inspect MSCPKG.TXT on Windows; it contains package diagnostics but no configured credentials."
|
|
exit 1
|
|
fi
|
|
rm -f -- "$PACKAGE_LOG" "$PACKAGE_INVENTORY"
|
|
|
|
STAGE=network
|
|
status "RUNNING: applying the requested network configuration."
|
|
cd "$WORK/project/核桃派软件源代码"
|
|
PYTHONPATH=. python3 -m scripts.provision_device network --config "$CONFIG"
|
|
systemctl restart NetworkManager.service
|
|
|
|
STAGE=deploy
|
|
status "RUNNING: installing and testing matrix-screen-controller."
|
|
{
|
|
if [ -e /opt/matrix-screen-controller ]; then
|
|
printf '%s\n' 'A previous deployment attempt left /opt/matrix-screen-controller in place.'
|
|
systemctl --no-pager --full status matrix-screen-controller.service || true
|
|
journalctl --no-pager -u matrix-screen-controller.service -n 120 || true
|
|
fi
|
|
} >"$DEPLOY_LOG" 2>&1
|
|
FRPC_RUN_USER=$(PYTHONPATH=. python3 -c 'from pathlib import Path; from scripts.image_config import read_config_path; import sys; print(read_config_path(Path(sys.argv[1]))["account"]["username"])' "$CONFIG")
|
|
if ! FRPC_RUN_USER="$FRPC_RUN_USER" DEFER_SERVICE_START=1 WHEELHOUSE="$WORK/project/离线依赖/其他依赖/aarch64-py311" FRPC_BUNDLE="$WORK/project/离线依赖/其他依赖/frp/0.71.0/linux-arm64" ./scripts/deploy_walnutpi.sh >>"$DEPLOY_LOG" 2>&1; then
|
|
status "FAILED: stage=deploy; controller deployment failed. Inspect MSCDEPLOY.TXT on Windows; it contains deployment diagnostics but no configured credentials."
|
|
exit 1
|
|
fi
|
|
rm -f -- "$DEPLOY_LOG"
|
|
|
|
STAGE=account
|
|
status "RUNNING: creating the configured account and unique device identity."
|
|
PYTHONPATH=. python3 -m scripts.provision_device account --config "$CONFIG"
|
|
FRPC_RUN_USER="$FRPC_RUN_USER" DEFER_SERVICE_START=1 FRPC_BUNDLE="$WORK/project/离线依赖/其他依赖/frp/0.71.0/linux-arm64" /bin/sh ./scripts/install_frpc_system.sh
|
|
systemctl disable boot_script.service >/dev/null 2>&1 || true
|
|
PYTHONPATH=. python3 -m scripts.provision_device identity --config "$CONFIG"
|
|
|
|
STAGE=ssh
|
|
status "RUNNING: enabling password-authenticated SSH for the configured administrator."
|
|
PYTHONPATH=. python3 -m scripts.provision_device ssh --config "$CONFIG"
|
|
|
|
STAGE=kernel
|
|
status "RUNNING: installing the verified offline AXP313A kernel."
|
|
kernel_unpacked_bytes=$(python3 -c 'import json; print(json.load(open("/opt/matrix-image-bootstrap/axp313a/METADATA.json", encoding="utf-8"))["unpacked_file_bytes"])')
|
|
available_bytes=$(df -Pk /var/tmp | awk 'NR == 2 { printf "%.0f\n", $4 * 1024 }')
|
|
required_bytes=$((kernel_unpacked_bytes * 2 + 268435456))
|
|
if [ "$available_bytes" -lt "$required_bytes" ]; then
|
|
printf '%s\n' 'insufficient root filesystem space to install the offline kernel safely' >&2
|
|
exit 1
|
|
fi
|
|
rm -rf -- "$KERNEL_WORK"
|
|
install -d -m 0700 "$KERNEL_WORK"
|
|
tar -xzf "$KERNEL_ARCHIVE" -C "$KERNEL_WORK"
|
|
cd "$WORK/project/核桃派软件源代码"
|
|
./scripts/install_axp313a_kernel.sh --artifact "$KERNEL_WORK"
|
|
rm -rf -- "$KERNEL_WORK"
|
|
rm -rf -- /opt/matrix-image-bootstrap
|
|
|
|
STAGE=finish
|
|
install -d -m 0711 /var/lib/matrix-screen-controller
|
|
printf '%s\n' "$(cat VERSION)" > /var/lib/matrix-screen-controller/.factory-image-version
|
|
if command -v shred >/dev/null 2>&1; then
|
|
shred -n 1 -z "$CONFIG" || true
|
|
fi
|
|
rm -f -- "$CONFIG" "$META" /boot/MSCINIT
|
|
systemctl disable matrix-image-firstboot.service >/dev/null 2>&1 || true
|
|
rm -f -- /etc/systemd/system/matrix-image-firstboot.service
|
|
rm -rf -- "$WORK"
|
|
sync
|
|
if nmcli -t -f NAME connection show --active 2>/dev/null | grep -Fxq 'matrix-screen' \
|
|
&& ip route show default | grep -q .; then
|
|
status "SUCCESS: provisioning completed; the device is rebooting into the installed controller."
|
|
else
|
|
status "SUCCESS: provisioning completed while Wi-Fi is not connected; the installed controller will keep trying after reboot."
|
|
fi
|
|
systemctl reboot
|