263 lines
10 KiB
Bash
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"
|