Files
matrix-screen-controller/核桃派软件源代码/scripts/update_walnutpi.sh
T

263 lines
10 KiB
Bash

#!/bin/sh
set -eu
if [ "$(id -u)" -ne 0 ]; then
echo "update_walnutpi.sh must run as root" >&2
exit 1
fi
if [ "$(uname -m)" != "aarch64" ]; then
echo "this update is only for the WalnutPi AArch64 host" >&2
exit 1
fi
SOURCE_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
PROJECT_ROOT=$(CDPATH= cd -- "$SOURCE_ROOT/.." && pwd)
WHEELHOUSE=${WHEELHOUSE:-$PROJECT_ROOT/发布更新相关/其他依赖/aarch64-py311}
FRPC_BUNDLE=${FRPC_BUNDLE:-$PROJECT_ROOT/发布更新相关/其他依赖/frp/0.71.0/linux-arm64}
MANIFEST=$WHEELHOUSE/SHA256SUMS
TARGET=/opt/matrix-screen-controller
DATA_ROOT=/var/lib/matrix-screen-controller
UNIT=/etc/systemd/system/matrix-screen-controller.service
OTA_UNIT=/etc/systemd/system/matrix-screen-controller-ota.service
SERVICE=matrix-screen-controller.service
SOFTWARE_VERSION=$(tr -d '\r\n' < "$SOURCE_ROOT/VERSION")
RELEASE_ID=${RELEASE_ID:-$SOFTWARE_VERSION-$(date -u +%Y%m%dT%H%M%SZ)}
RELEASES=/opt/matrix-screen-controller.releases
case "$RELEASE_ID" in
*[!A-Za-z0-9._-]*|'')
echo "RELEASE_ID may contain only letters, digits, dot, underscore, and dash" >&2
exit 1
;;
esac
STAGING=/opt/matrix-screen-controller.stage.$RELEASE_ID
BACKUP=/opt/matrix-screen-controller.rollback.$RELEASE_ID
NEW_RELEASE=$RELEASES/$RELEASE_ID
DATA_CANDIDATE=/var/lib/matrix-screen-controller.update.$RELEASE_ID
DATA_BACKUP=/var/lib/matrix-screen-controller.rollback.$RELEASE_ID
MIGRATION_RUNTIME=/run/matrix-screen-controller-update.$RELEASE_ID.migration
STATUS_FILE=/run/matrix-screen-controller-update.$RELEASE_ID.status
TEST_DATA=/run/matrix-screen-controller-update.$RELEASE_ID.test-data
TEST_RUNTIME=/run/matrix-screen-controller-update.$RELEASE_ID.test-runtime
FRPC_BACKUP=/run/matrix-screen-controller-update.$RELEASE_ID.frpc-backup
SWAPPED=0
DATA_BACKED_UP=0
SUCCESS=0
SERVICE_STOPPED=0
FRPC_CHANGED=0
FRPC_WAS_ACTIVE=0
FRPC_WAS_ENABLED=0
PREVIOUS_LINK=
cleanup_paths() {
for path in "$STAGING" "$DATA_CANDIDATE" "$MIGRATION_RUNTIME" "$STATUS_FILE" "$TEST_DATA" "$TEST_RUNTIME" "$FRPC_BACKUP"; do
case "$path" in
/opt/matrix-screen-controller.stage.$RELEASE_ID|/var/lib/matrix-screen-controller.update.$RELEASE_ID|/run/matrix-screen-controller-update.$RELEASE_ID.migration|/run/matrix-screen-controller-update.$RELEASE_ID.status|/run/matrix-screen-controller-update.$RELEASE_ID.test-data|/run/matrix-screen-controller-update.$RELEASE_ID.test-runtime|/run/matrix-screen-controller-update.$RELEASE_ID.frpc-backup)
if [ -d "$path" ]; then rm -rf -- "$path"; else rm -f -- "$path"; fi
;;
esac
done
}
rollback() {
if [ "$SUCCESS" -eq 1 ]; then
return
fi
if [ "$SWAPPED" -eq 1 ] || [ "$DATA_BACKED_UP" -eq 1 ]; then
echo "new release failed; restoring previous program and persistent data" >&2
fi
if [ "$SERVICE_STOPPED" -eq 1 ] || [ "$SWAPPED" -eq 1 ] || [ "$DATA_BACKED_UP" -eq 1 ]; then
systemctl stop "$SERVICE" || true
fi
if [ "$DATA_BACKED_UP" -eq 1 ]; then
FAILED_DATA=/var/lib/matrix-screen-controller.failed.$RELEASE_ID
if [ -e "$DATA_ROOT" ] && [ ! -e "$FAILED_DATA" ]; then mv -- "$DATA_ROOT" "$FAILED_DATA"; fi
if [ -d "$DATA_BACKUP" ]; then mv -- "$DATA_BACKUP" "$DATA_ROOT"; fi
case "$FAILED_DATA" in
/var/lib/matrix-screen-controller.failed.$RELEASE_ID)
if [ -d "$FAILED_DATA" ]; then rm -rf -- "$FAILED_DATA"; fi
;;
esac
fi
if [ "$FRPC_CHANGED" -eq 1 ]; then
systemctl stop matrix-screen-frpc.service 2>/dev/null || true
if [ -f "$FRPC_BACKUP/frpc" ]; then install -m 0755 "$FRPC_BACKUP/frpc" /usr/local/bin/frpc; else rm -f -- /usr/local/bin/frpc; fi
if [ -f "$FRPC_BACKUP/unit" ]; then install -m 0644 "$FRPC_BACKUP/unit" /etc/systemd/system/matrix-screen-frpc.service; else rm -f -- /etc/systemd/system/matrix-screen-frpc.service; fi
if [ -f "$FRPC_BACKUP/dropin" ]; then
install -d -m 0755 /etc/systemd/system/matrix-screen-frpc.service.d
install -m 0644 "$FRPC_BACKUP/dropin" /etc/systemd/system/matrix-screen-frpc.service.d/user.conf
else
rm -f -- /etc/systemd/system/matrix-screen-frpc.service.d/user.conf
fi
systemctl daemon-reload
if [ "$FRPC_WAS_ENABLED" -eq 1 ]; then systemctl enable matrix-screen-frpc.service >/dev/null; else systemctl disable matrix-screen-frpc.service >/dev/null 2>&1 || true; fi
if [ "$FRPC_WAS_ACTIVE" -eq 1 ]; then systemctl start matrix-screen-frpc.service || true; fi
fi
if [ "$SWAPPED" -eq 1 ]; then
if [ -L "$TARGET" ]; then
rm -f -- "$TARGET"
elif [ -d "$TARGET" ]; then
FAILED=/opt/matrix-screen-controller.failed.$RELEASE_ID
if [ ! -e "$FAILED" ]; then mv -- "$TARGET" "$FAILED"; fi
fi
if [ -d "$BACKUP" ]; then
mv -- "$BACKUP" "$TARGET"
elif [ -n "$PREVIOUS_LINK" ]; then
ln -s -- "$PREVIOUS_LINK" "$TARGET"
fi
rm -rf -- "$NEW_RELEASE"
install -m 0644 "$TARGET/systemd/matrix-screen-controller.service" "$UNIT"
install -m 0644 "$TARGET/systemd/matrix-screen-controller-ota.service" "$OTA_UNIT"
systemctl daemon-reload
fi
if [ "$SERVICE_STOPPED" -eq 1 ] || [ "$SWAPPED" -eq 1 ] || [ "$DATA_BACKED_UP" -eq 1 ]; then
systemctl start "$SERVICE" || true
fi
}
finish() {
code=$?
trap - EXIT HUP INT TERM
rollback
cleanup_paths
exit "$code"
}
trap finish EXIT HUP INT TERM
if [ ! -f "$MANIFEST" ]; then
echo "offline wheel manifest missing: $MANIFEST" >&2
exit 1
fi
if [ ! -d "$TARGET" ]; then
echo "existing production target missing: $TARGET" >&2
exit 1
fi
if [ ! -d "$DATA_ROOT" ]; then
echo "existing persistent data root missing: $DATA_ROOT" >&2
exit 1
fi
if [ -e "$STAGING" ] || [ -e "$BACKUP" ] || [ -e "$NEW_RELEASE" ] || [ -e "$DATA_CANDIDATE" ] || [ -e "$DATA_BACKUP" ]; then
echo "release staging or rollback path already exists for $RELEASE_ID" >&2
exit 1
fi
(cd "$WHEELHOUSE" && sha256sum -c SHA256SUMS)
mkdir -m 0750 -- "$STAGING"
cp -a "$SOURCE_ROOT/." "$STAGING/"
python3 -m venv "$STAGING/.venv"
"$STAGING/.venv/bin/pip" install \
--no-index \
--find-links "$WHEELHOUSE" \
-r "$STAGING/requirements-dev.txt"
make -C "$STAGING/app/display/native" clean all test
"$STAGING/.venv/bin/python" -m compileall -q "$STAGING/app"
mkdir -p -- "$TEST_DATA" "$TEST_RUNTIME/tmp"
(cd "$STAGING" && \
MATRIX_DRIVER=mock \
MATRIX_TEST_ROOT="$TEST_DATA" \
MATRIX_DATA_DIR="$TEST_DATA" \
MATRIX_RUNTIME_DIR="$TEST_RUNTIME" \
MATRIX_SOURCE_ONLY_UPDATE_TESTS=1 \
TMPDIR="$TEST_RUNTIME/tmp" \
TEMP="$TEST_RUNTIME/tmp" \
TMP="$TEST_RUNTIME/tmp" \
"$STAGING/.venv/bin/python" -m pytest -p no:cacheprovider -q \
--basetemp="$TEST_RUNTIME/pytest" --tb=short --disable-warnings)
"$STAGING/.venv/bin/python" "$STAGING/scripts/dedicated_host.py" check
systemctl stop "$SERVICE"
SERVICE_STOPPED=1
cp -a -- "$DATA_ROOT" "$DATA_CANDIDATE"
PYTHONPATH="$STAGING" "$STAGING/.venv/bin/python" -m scripts.prepare_data_root \
--data-root "$DATA_CANDIDATE" \
--runtime-root "$MIGRATION_RUNTIME"
mkdir -p -- "$RELEASES"
if [ -L "$TARGET" ]; then
PREVIOUS_LINK=$(readlink "$TARGET")
rm -f -- "$TARGET"
SWAPPED=1
else
mv -- "$TARGET" "$BACKUP"
SWAPPED=1
fi
mv -- "$STAGING" "$NEW_RELEASE"
ln -s -- "$NEW_RELEASE" "$TARGET"
mv -- "$DATA_ROOT" "$DATA_BACKUP"
DATA_BACKED_UP=1
mv -- "$DATA_CANDIDATE" "$DATA_ROOT"
install -m 0644 "$TARGET/systemd/matrix-screen-controller.service" "$UNIT"
install -m 0644 "$TARGET/systemd/matrix-screen-controller-ota.service" "$OTA_UNIT"
install -d -m 0700 "$FRPC_BACKUP"
systemctl is-active --quiet matrix-screen-frpc.service 2>/dev/null && FRPC_WAS_ACTIVE=1 || true
systemctl is-enabled --quiet matrix-screen-frpc.service 2>/dev/null && FRPC_WAS_ENABLED=1 || true
if [ -f /usr/local/bin/frpc ]; then cp -p -- /usr/local/bin/frpc "$FRPC_BACKUP/frpc"; fi
if [ -f /etc/systemd/system/matrix-screen-frpc.service ]; then cp -p -- /etc/systemd/system/matrix-screen-frpc.service "$FRPC_BACKUP/unit"; fi
if [ -f /etc/systemd/system/matrix-screen-frpc.service.d/user.conf ]; then cp -p -- /etc/systemd/system/matrix-screen-frpc.service.d/user.conf "$FRPC_BACKUP/dropin"; fi
FRPC_CHANGED=1
FRPC_BUNDLE="$FRPC_BUNDLE" /bin/sh "$TARGET/scripts/install_frpc_system.sh"
systemctl daemon-reload
systemctl start "$SERVICE"
check_health() {
attempt=0
while [ "$attempt" -lt 30 ]; do
if curl --fail --silent http://127.0.0.1:8080/api/status >"$STATUS_FILE"; then
if "$TARGET/.venv/bin/python" - "$STATUS_FILE" <<'PY'
import json
from pathlib import Path
import sys
status = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
screen = status.get("screen", {})
driver = screen.get("driver_status") or {}
expected_version = Path("/opt/matrix-screen-controller/VERSION").read_text(encoding="utf-8").strip()
assert status.get("service", {}).get("software_version") == expected_version
assert screen.get("driver") == "walnutpi-h618-hub75"
assert screen.get("hardware_mapping") == "walnutpi-pi-bank-pwm-oe-v2"
assert driver.get("oe_timing_backend") == "h618-pwm4"
assert driver.get("oe_pulse_faults") == 0
PY
then
return 0
fi
fi
attempt=$((attempt + 1))
sleep 1
done
return 1
}
if ! check_health; then
echo "new release did not pass the production health check" >&2
exit 1
fi
systemctl restart "$SERVICE"
if ! check_health; then
echo "new release did not pass the restart health check" >&2
exit 1
fi
SUCCESS=1
SWAPPED=0
DATA_BACKED_UP=0
SERVICE_STOPPED=0
FRPC_CHANGED=0
case "$BACKUP" in
/opt/matrix-screen-controller.rollback.$RELEASE_ID)
rm -rf -- "$BACKUP"
;;
esac
case "$DATA_BACKUP" in
/var/lib/matrix-screen-controller.rollback.$RELEASE_ID)
rm -rf -- "$DATA_BACKUP"
;;
esac
case "$PREVIOUS_LINK" in
/opt/matrix-screen-controller.releases/*)
if [ "$PREVIOUS_LINK" != "$NEW_RELEASE" ]; then rm -rf -- "$PREVIOUS_LINK"; fi
;;
esac
systemctl --no-pager --full status "$SERVICE"
echo "WalnutPi Matrix Screen Controller updated successfully: $RELEASE_ID"