#!/usr/bin/env bash # Lumotia rebrand-migration dogfood drill. # # Launches the real lumotia binary against synthetic legacy magnotia state # planted on disk, then probes the post-startup state to confirm both # migration paths ran: # 1. paths.rs: ~/.local/share/magnotia/ -> ~/.local/share/lumotia/ # magnotia.db -> lumotia.db # 2. tauri_app_data_migration.rs: # ~/.local/share/uk.co.corbel.magnotia/ copied via staging to # ~/.local/share/consulting.corbel.lumotia/ (legacy preserved # as a backup) # # Modes: # (default) Sandbox. Sets HOME=, plants legacy state # inside, launches the binary, verifies, tears down. # Faithful on Linux. NOT faithful on macOS (Tauri 2 # uses NSSearchPathForDirectoriesInDomains which # ignores HOME overrides and would write to your # real Application Support tree). # --against-real-home Real $HOME. Refuses to run if any lumotia data # already exists at the real paths. Backs up the # legacy planting before running so cleanup can # restore your real-home tree to its pre-drill # state. # # Flags: # --keep Keep the sandbox dir / preserved backups after the # run for manual inspection. # --timeout SECS How long to wait for the binary to come up and run # the migration. Default 20s. Bump on slow hardware # or when running under a debugger. # --binary PATH Override the default ./target/debug/lumotia. # # Exit codes: # 0 all probes passed # 1 a probe failed (migration did not produce the expected on-disk state) # 2 argument error # 3 preflight check failed (real-home already has lumotia data, binary # missing, unsupported platform for sandbox mode, ...) set -euo pipefail MODE="sandbox" KEEP=false TIMEOUT_SECS=20 BINARY="./target/debug/lumotia" # ---- arg parsing ----------------------------------------------------------- while [[ $# -gt 0 ]]; do case "$1" in --against-real-home) MODE="real-home"; shift ;; --keep) KEEP=true; shift ;; --timeout) TIMEOUT_SECS="$2"; shift 2 ;; --binary) BINARY="$2"; shift 2 ;; -h|--help) sed -n '2,33p' "$0" | sed 's/^# \{0,1\}//' exit 0 ;; *) echo "Unknown option: $1" >&2; exit 2 ;; esac done # ---- preflight ------------------------------------------------------------- if [[ ! -x "$BINARY" ]]; then echo "FAIL: binary not found at $BINARY. Run 'cargo build -p lumotia' first." >&2 exit 3 fi PLATFORM="$(uname -s)" if [[ "$MODE" == "sandbox" && "$PLATFORM" == "Darwin" ]]; then echo "FAIL: sandbox mode is not faithful on macOS." >&2 echo " Tauri 2 uses NSSearchPathForDirectoriesInDomains which ignores HOME overrides." >&2 echo " Either run on Linux or use --against-real-home (with backup discipline)." >&2 exit 3 fi # Real-home mode: refuse to run if the user already has lumotia data on # disk. We'd risk merging fake planted state into real data on cleanup. if [[ "$MODE" == "real-home" ]]; then REAL_HOME="$HOME" REAL_LUMOTIA_DATA="${XDG_DATA_HOME:-$REAL_HOME/.local/share}/lumotia" REAL_LUMOTIA_TAURI="${XDG_DATA_HOME:-$REAL_HOME/.local/share}/consulting.corbel.lumotia" for p in "$REAL_LUMOTIA_DATA" "$REAL_LUMOTIA_TAURI" "$REAL_HOME/.lumotia"; do if [[ -e "$p" ]]; then echo "FAIL: $p already exists. Refusing to run in real-home mode against existing data." >&2 echo " Either back it up manually and remove the original, or run in sandbox mode." >&2 exit 3 fi done fi # ---- sandbox setup --------------------------------------------------------- if [[ "$MODE" == "sandbox" ]]; then SANDBOX="$(mktemp -d -t lumotia-dogfood-XXXXXX)" export HOME="$SANDBOX" unset XDG_DATA_HOME PLANT_ROOT="$SANDBOX/.local/share" echo "Sandbox: $SANDBOX (HOME overridden)" else SANDBOX="" # signal: don't tear down a sandbox at the end PLANT_ROOT="${XDG_DATA_HOME:-$HOME/.local/share}" echo "Real home: planting under $PLANT_ROOT" fi LOG_FILE="$(mktemp -t lumotia-dogfood-binary-XXXXXX.log)" cleanup() { if [[ -n "${BINARY_PID:-}" ]] && kill -0 "$BINARY_PID" 2>/dev/null; then kill -TERM "$BINARY_PID" 2>/dev/null || true sleep 0.5 kill -KILL "$BINARY_PID" 2>/dev/null || true fi if [[ "$KEEP" == "false" && -n "$SANDBOX" && -d "$SANDBOX" ]]; then rm -rf "$SANDBOX" fi if [[ "$KEEP" == "false" && "$MODE" == "real-home" ]]; then # Real-home cleanup: remove planted legacy state AND any # post-migration artefacts we created. We refused to start if # any of these existed, so removing them now is safe. rm -rf \ "$PLANT_ROOT/magnotia" \ "$PLANT_ROOT/uk.co.corbel.magnotia" \ "$PLANT_ROOT/lumotia" \ "$PLANT_ROOT/consulting.corbel.lumotia" fi } trap cleanup EXIT INT TERM # ---- plant synthetic legacy state ----------------------------------------- mkdir -p "$PLANT_ROOT/magnotia/recordings/2026-05-13" # Plant a sentinel non-DB file inside the legacy data dir to confirm the # migration sweeps the whole tree, not just magnotia.db. echo "fake-wav-bytes-for-dogfood-drill" > "$PLANT_ROOT/magnotia/recordings/2026-05-13/clip.wav" # An empty file at magnotia.db is enough for the file-level rename probe. # The Rust integration test (crates/storage/tests/legacy_db_migration.rs) # covers the "DB still openable after rename" path with a real SQLite; # this drill is about the LIVE BINARY at startup, not schema integrity. : > "$PLANT_ROOT/magnotia/magnotia.db" mkdir -p "$PLANT_ROOT/uk.co.corbel.magnotia/localStorage/leveldb" echo "sentinel-leveldb-bytes" > "$PLANT_ROOT/uk.co.corbel.magnotia/localStorage/leveldb/000003.log" echo '{"main":{"x":100,"y":200,"width":1280,"height":720}}' > "$PLANT_ROOT/uk.co.corbel.magnotia/window-state.json" echo "Planted synthetic legacy state. Launching $BINARY..." # ---- launch binary in background, give it time to run migrations ---------- # RUST_LOG forces lumotia_startup tracing events to disk regardless of any # default filter. The setup hook logs `migrated legacy magnotia data dir` # at info level on a successful rename. RUST_LOG="lumotia_startup=info,lumotia=info" \ "$BINARY" >"$LOG_FILE" 2>&1 & BINARY_PID=$! # Wait for either: the migration log line to appear, OR the timeout. DEADLINE=$(( $(date +%s) + TIMEOUT_SECS )) MIGRATED_DATA_DIR_SEEN=false MIGRATED_TAURI_DIR_SEEN=false while (( $(date +%s) < DEADLINE )); do if grep -q "migrated legacy magnotia data dir to lumotia" "$LOG_FILE" 2>/dev/null; then MIGRATED_DATA_DIR_SEEN=true fi if grep -qE "Migrated|migrated.*tauri.*app_data|copied legacy Tauri" "$LOG_FILE" 2>/dev/null; then MIGRATED_TAURI_DIR_SEEN=true fi if [[ "$MIGRATED_DATA_DIR_SEEN" == "true" ]]; then # Give the Tauri side another moment after the data-dir migration. sleep 1 break fi sleep 0.5 done # SIGTERM the binary cleanly. We don't need it running for the probes — # all probes inspect the post-migration filesystem state. if kill -0 "$BINARY_PID" 2>/dev/null; then kill -TERM "$BINARY_PID" || true fi wait "$BINARY_PID" 2>/dev/null || true # ---- probes ---------------------------------------------------------------- PROBES_PASSED=0 PROBES_FAILED=0 report_probe() { local name="$1"; local outcome="$2"; local detail="${3:-}" if [[ "$outcome" == "PASS" ]]; then printf ' PASS %s\n' "$name" PROBES_PASSED=$(( PROBES_PASSED + 1 )) else printf ' FAIL %s %s\n' "$name" "$detail" PROBES_FAILED=$(( PROBES_FAILED + 1 )) fi } printf '\nProbe results:\n' # 1. Data-dir migration: target exists at the new path. if [[ -d "$PLANT_ROOT/lumotia" ]]; then report_probe "data-dir rename produced ~/.local/share/lumotia/" PASS else report_probe "data-dir rename produced ~/.local/share/lumotia/" FAIL "(missing)" fi # 2. Data-dir migration: lumotia.db is at the new path. if [[ -f "$PLANT_ROOT/lumotia/lumotia.db" ]]; then report_probe "magnotia.db renamed to lumotia.db at new path" PASS else report_probe "magnotia.db renamed to lumotia.db at new path" FAIL "(missing)" fi # 3. Data-dir migration: legacy magnotia tree is gone (rename moved it). if [[ ! -d "$PLANT_ROOT/magnotia" ]]; then report_probe "legacy ~/.local/share/magnotia/ removed by rename" PASS else report_probe "legacy ~/.local/share/magnotia/ removed by rename" FAIL "(still on disk)" fi # 4. Data-dir migration: non-DB companion file carried along. if [[ -f "$PLANT_ROOT/lumotia/recordings/2026-05-13/clip.wav" ]]; then report_probe "non-DB companion file carried along by directory rename" PASS else report_probe "non-DB companion file carried along by directory rename" FAIL "(missing)" fi # 5. Tauri migration: webview/localStorage copied to new bundle id path. if [[ -f "$PLANT_ROOT/consulting.corbel.lumotia/localStorage/leveldb/000003.log" ]]; then report_probe "Tauri app_data_dir copied to consulting.corbel.lumotia/" PASS else report_probe "Tauri app_data_dir copied to consulting.corbel.lumotia/" FAIL "(localStorage missing)" fi # 6. Tauri migration: legacy uk.co.corbel.magnotia preserved as backup. if [[ -f "$PLANT_ROOT/uk.co.corbel.magnotia/localStorage/leveldb/000003.log" ]]; then report_probe "legacy Tauri dir preserved as backup" PASS else report_probe "legacy Tauri dir preserved as backup" FAIL "(missing — migration should NOT delete legacy)" fi # 7. Tauri migration: staging dir cleaned up. if [[ ! -d "$PLANT_ROOT/consulting.corbel.lumotia.migrating" ]]; then report_probe "staging dir consulting.corbel.lumotia.migrating cleaned up" PASS else report_probe "staging dir consulting.corbel.lumotia.migrating cleaned up" FAIL "(staging leaked)" fi # 8. Log line: lumotia_startup migration event appeared. if [[ "$MIGRATED_DATA_DIR_SEEN" == "true" ]]; then report_probe "lumotia_startup logged the data-dir migration" PASS else report_probe "lumotia_startup logged the data-dir migration" FAIL "(no log line within ${TIMEOUT_SECS}s)" fi # ---- summary -------------------------------------------------------------- printf '\nLog file: %s\n' "$LOG_FILE" if [[ "$KEEP" == "true" && -n "$SANDBOX" ]]; then printf 'Sandbox preserved at: %s\n' "$SANDBOX" fi printf '\nPassed: %s / Failed: %s\n' "$PROBES_PASSED" "$PROBES_FAILED" if (( PROBES_FAILED > 0 )); then printf '\nDrill FAILED. Inspect %s for clues.\n' "$LOG_FILE" exit 1 fi printf '\nDrill PASSED. Rebrand migration runs end-to-end against real OS paths.\n'