Cutover script: dry run (snapshot, checksums, restore rehearsal, computation) and execution only with Paul's go
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+85
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
# The one-time coordinated cutover (docs/cutover-proposal-final.md). Run only on Paul's explicit go, with the TV idle and
|
||||
# wireless debugging on.
|
||||
# tools/cutover.sh dry-run snapshot, checksums, restore rehearsal and the full computation; writes nothing new
|
||||
# tools/cutover.sh execute I-HAVE-PAULS-GO the same, then writes the new schedules, checks them and starts Orbit
|
||||
# tools/cutover.sh restore <snapshot-dir> immediate rollback: the snapshot back over the live schedules, checked
|
||||
set -euo pipefail
|
||||
MODE=${1:?dry-run, execute or restore}
|
||||
HOST=${ORBIT_BOX:-192.168.0.252:5555}
|
||||
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
||||
ADB="timeout 120 /opt/android-sdk/platform-tools/adb -s $HOST"
|
||||
APP=tv.orbit.classic
|
||||
STAMP=$(date -u +%Y%m%d-%H%M%S)
|
||||
WORK=${ORBIT_CUTOVER_WORK:-$ROOT/../cutover-$STAMP}
|
||||
say() { echo "[$(date +%H:%M:%S)] $*"; }
|
||||
box() { $ADB shell run-as $APP "$@"; }
|
||||
|
||||
timeout 15 /opt/android-sdk/platform-tools/adb connect "$HOST" >/dev/null 2>&1 || true
|
||||
$ADB get-state >/dev/null || { say "box not reachable: switch wireless debugging on"; exit 1; }
|
||||
|
||||
if [ "$MODE" = restore ]; then
|
||||
SNAP=${2:?snapshot dir}; NAME=$(cat "$SNAP/device-copy.txt")
|
||||
$ADB shell am force-stop $APP
|
||||
box sh -c "cp -a no_backup/$NAME/. no_backup/schedule/"
|
||||
box sh -c "cd no_backup/schedule && sha256sum $(cd "$SNAP/files" && ls | tr '\n' ' ')" | sort -k2 > "$SNAP/after-restore.sha256"
|
||||
diff <(sort -k2 "$SNAP/manifest.sha256") "$SNAP/after-restore.sha256" && say "restored: every file matches the snapshot"
|
||||
$ADB shell am start -n $APP/.MainActivity >/dev/null; exit 0
|
||||
fi
|
||||
[ "$MODE" = dry-run ] || [ "${2:-}" = I-HAVE-PAULS-GO ] || { say "execute needs Paul's explicit go"; exit 1; }
|
||||
|
||||
say "1. checking the box"
|
||||
$ADB shell dumpsys package $APP | grep -m1 versionName
|
||||
TOP=$($ADB shell dumpsys activity activities | grep -m1 topResumedActivity)
|
||||
case "$TOP" in *orbit*|*youtube*|*netflix*) say "the TV is in use ($TOP): stopping"; exit 1;; esac
|
||||
|
||||
say "2. stopping Orbit so nothing tops up during the change"
|
||||
$ADB shell am force-stop $APP
|
||||
|
||||
say "3. snapshot: this machine, the box, and a manifest"
|
||||
mkdir -p "$WORK/snapshot/files"
|
||||
FILES=$(box ls no_backup/schedule)
|
||||
for f in $FILES; do $ADB exec-out run-as $APP cat "no_backup/schedule/$f" > "$WORK/snapshot/files/$f"; done
|
||||
(cd "$WORK/snapshot/files" && sha256sum * | sort -k2) > "$WORK/snapshot/manifest.sha256"
|
||||
box sh -c "cd no_backup/schedule && sha256sum *" | sort -k2 > "$WORK/snapshot/device.sha256"
|
||||
diff "$WORK/snapshot/manifest.sha256" "$WORK/snapshot/device.sha256" >/dev/null || { say "copy on this machine differs from the box: stopping"; exit 1; }
|
||||
NAME=schedule-rollback-$STAMP
|
||||
box cp -a no_backup/schedule "no_backup/$NAME"
|
||||
echo "$NAME" > "$WORK/snapshot/device-copy.txt"
|
||||
box sh -c "cd no_backup/$NAME && sha256sum *" | sort -k2 > "$WORK/snapshot/device-copy.sha256"
|
||||
diff "$WORK/snapshot/manifest.sha256" "$WORK/snapshot/device-copy.sha256" >/dev/null || { say "box copy differs: stopping"; exit 1; }
|
||||
say " $(wc -l < "$WORK/snapshot/manifest.sha256") files, checksums match on both copies; box copy kept as no_backup/$NAME"
|
||||
|
||||
say "4. rehearsing the restore on identical bytes"
|
||||
box sh -c "cp -a no_backup/$NAME/. no_backup/schedule/"
|
||||
box sh -c "cd no_backup/schedule && sha256sum *" | sort -k2 > "$WORK/snapshot/rehearsal.sha256"
|
||||
diff "$WORK/snapshot/manifest.sha256" "$WORK/snapshot/rehearsal.sha256" >/dev/null || { say "rehearsal changed something: stopping"; exit 1; }
|
||||
say " restore rehearsed: every file matches"
|
||||
|
||||
say "5. computing the new schedules from this snapshot (every check must pass)"
|
||||
T=$(( $(date +%s) * 1000 ))
|
||||
mkdir -p "$WORK/new"
|
||||
(cd "$ROOT/android" && GRADLE_USER_HOME="$ROOT/.tooling/gradle-cache" ANDROID_USER_HOME="$ROOT/.tooling/android" ANDROID_HOME=/opt/android-sdk \
|
||||
ORBIT_REPLAY_DIR="$WORK/snapshot/files" ORBIT_CUTOVER_OUT="$WORK/new" ORBIT_CUTOVER_AT=$T \
|
||||
./gradlew --no-daemon --offline -q :app:testDebugUnitTest --rerun --tests '*CutoverSimulationTest*') || { say "a check failed: nothing written"; exit 1; }
|
||||
mv "$WORK/snapshot/files/cutover-simulation.md" "$WORK/cutover-report.md"
|
||||
(cd "$WORK/new" && sha256sum * | sort -k2) > "$WORK/new.sha256"
|
||||
say " $(ls "$WORK/new" | wc -l) files computed; report: $WORK/cutover-report.md"
|
||||
|
||||
if [ "$MODE" = dry-run ]; then
|
||||
$ADB shell am start -a android.intent.action.MAIN -c android.intent.category.HOME >/dev/null 2>&1
|
||||
say "dry run complete: nothing new written. Orbit stays stopped until it's next opened."; exit 0
|
||||
fi
|
||||
|
||||
say "6. writing the new schedules and checking them on the box"
|
||||
$ADB shell mkdir -p /data/local/tmp/orbit-cutover
|
||||
for f in "$WORK"/new/*; do $ADB push "$f" /data/local/tmp/orbit-cutover/ >/dev/null; done
|
||||
box sh -c "cp /data/local/tmp/orbit-cutover/* no_backup/schedule/"
|
||||
$ADB shell rm -rf /data/local/tmp/orbit-cutover
|
||||
box sh -c "cd no_backup/schedule && sha256sum $(cd "$WORK/new" && ls | tr '\n' ' ')" | sort -k2 > "$WORK/written.sha256"
|
||||
diff "$WORK/new.sha256" "$WORK/written.sha256" >/dev/null || { say "written files differ: restoring the snapshot"; "$0" restore "$WORK/snapshot"; exit 1; }
|
||||
say " every written file matches"
|
||||
|
||||
say "7. starting Orbit"
|
||||
$ADB logcat -c; $ADB shell am start -n $APP/.MainActivity >/dev/null
|
||||
say "done. Verify the log (added=0, clashes=0), playback and idents; the snapshot is in $WORK/snapshot and no_backup/$NAME"
|
||||
Reference in New Issue
Block a user