#!/usr/bin/env bash # Reset FlintTrade user state for a fresh-install test. # # Wipes the # per-OS workspace directory (auth.db, credentials.db, activity, security, # traffic/error logs, jwt_secret, master_password, workspace.json, # contracts cache, rag store): # Linux ~/.flinttrade # macOS ~/Library/Application Support/flinttrade # Windows %APPDATA%\flinttrade # Overrides, in precedence order: FLINTTRADE_WORKSPACE_DIR > FLINTTRADE_HOME. # Either may be written with a leading '~' or relative; both are expanded and # made absolute exactly as flinttrade_core.workspace resolves them. # # Archives everything first to .local/archive/flinttrade-state-/ # per the project's never-delete rule. Nothing is lost, just moved. # # Usage: # scripts/reset-flinttrade-state.sh # archive + wipe + (user restarts backend) # scripts/reset-flinttrade-state.sh --restart # also kill + restart backend set -euo pipefail # Whole-authority copy/reset awaits the coordinated restore transaction. case "${1:-}" in --help|-h) printf '%s\n' 'Workspace reset is temporarily unavailable: coordinated_restore_unavailable' exit 0 ;; esac printf '%s\n' 'coordinated_restore_unavailable' >&2 exit 1 REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$REPO_ROOT" # Resolve the REAL per-OS workspace, honouring both env overrides in precedence # order: FLINTTRADE_WORKSPACE_DIR > FLINTTRADE_HOME > platform default. # Hardcoding ${HOME}/.flinttrade wiped the wrong directory on macOS and Windows # (where the workspace lives under Application Support / %APPDATA%), so the reset # silently did nothing while reporting success. default_workspace() { case "$(uname -s)" in Darwin) printf '%s' "$HOME/Library/Application Support/flinttrade" ;; MINGW*|MSYS*|CYGWIN*) printf '%s' "${APPDATA:-$HOME/AppData/Roaming}/flinttrade" ;; *) printf '%s' "$HOME/.flinttrade" ;; esac } # Canonicalise the resolved workspace the way flinttrade_core.workspace does # (Path(value).expanduser().resolve()) BEFORE anything destructive touches it. # An override arriving literally as '~/custom-workspace' — which is how env # files and service units routinely spell it, since neither expands '~' — was # taken as a directory named '~' below the repository root, so the reset wiped # and re-created that while the real workspace survived untouched and the run # still reported success. canonical_workspace_path() { local value="$1" resolved case "$value" in "~") value="$HOME" ;; "~/"*) value="$HOME/${value#\~/}" ;; # This runs in a command substitution, so the exit ends the subshell and # leaves STATE_DIR empty; the refusal guard below then stops the run. "~"*) echo "! Named-user home overrides are not supported: $value" >&2; exit 1 ;; esac case "$value" in # POSIX absolute, or an absolute Windows path under MSYS/Git Bash. /*|[A-Za-z]:[/\\]*) ;; *) value="$(pwd -P)/$value" ;; esac if [ -d "$value" ]; then resolved="$(cd -P "$value" 2>/dev/null && pwd -P)" || resolved="" if [ -n "$resolved" ]; then value="$resolved"; fi fi printf '%s' "$value" } STATE_DIR="$(canonical_workspace_path "${FLINTTRADE_WORKSPACE_DIR:-${FLINTTRADE_HOME:-$(default_workspace)}}")" # The archive-then-wipe below is 'rm -rf "$STATE_DIR"', so refuse the paths that # would take the whole home (or the filesystem root) with them. HOME_CANONICAL="$(cd -P "$HOME" 2>/dev/null && pwd -P)" || HOME_CANONICAL="$HOME" case "$STATE_DIR" in ""|"/"|"$HOME"|"${HOME%/}"|"$HOME_CANONICAL"|"${HOME_CANONICAL%/}") echo "! Refusing to reset '$STATE_DIR' — that is not a FlintTrade workspace directory." >&2 exit 1 ;; esac PYTHON_BIN="${PYTHON:-python}" FLINTTRADE_PYTHONPATH="$REPO_ROOT/packages/core/core/src" RESTART=false for arg in "$@"; do case "$arg" in --restart) RESTART=true ;; esac done # --------------------------------------------------------------------------- # 1. Kill FlintTrade backend (port 5100) if running. # --------------------------------------------------------------------------- BACKEND_PID="" # 'set -euo pipefail' is in force: lsof exits 1 when nothing is listening, which # under pipefail failed the assignment and aborted the whole reset silently on # every machine whose backend was already stopped. Finding no backend is the # normal case, not an error. if command -v lsof >/dev/null 2>&1; then BACKEND_PID="$(lsof -tiTCP:5100 -sTCP:LISTEN 2>/dev/null | head -1 || true)" elif netstat -ano 2>/dev/null | grep -qE "127\.0\.0\.1:5100\s+.*LISTENING"; then BACKEND_PID="$(netstat -ano | grep LISTENING | grep -E '127\.0\.0\.1:5100\s' | awk '{print $NF}' | head -1 || true)" fi if [ -n "${BACKEND_PID:-}" ]; then echo "→ Killing FlintTrade backend (PID $BACKEND_PID)" taskkill //F //PID "$BACKEND_PID" >/dev/null 2>&1 || kill "$BACKEND_PID" 2>/dev/null || kill -9 "$BACKEND_PID" 2>/dev/null || true sleep 2 fi # --------------------------------------------------------------------------- # 2. Archive current state. # --------------------------------------------------------------------------- TS="$(date +%Y%m%d-%H%M%S)" ARCHIVE_DIR=".local/archive/flinttrade-state-${TS}" mkdir -p "$ARCHIVE_DIR" if [ -d "$STATE_DIR" ] && [ "$(ls -A "$STATE_DIR" 2>/dev/null)" ]; then cp -r "$STATE_DIR"/. "$ARCHIVE_DIR/" 2>/dev/null || true SIZE="$(du -sh "$ARCHIVE_DIR" 2>/dev/null | cut -f1)" echo "→ Archived FlintTrade state to $ARCHIVE_DIR (${SIZE})" else echo "→ No existing state to archive" fi # --------------------------------------------------------------------------- # 3. Wipe the workspace but keep the empty dir so the backend writes cleanly. # --------------------------------------------------------------------------- rm -rf "$STATE_DIR" mkdir -p "$STATE_DIR" echo "→ Wiped $STATE_DIR" # --------------------------------------------------------------------------- # 5. Optionally restart the backend. # --------------------------------------------------------------------------- if [ "$RESTART" = true ]; then mkdir -p .local/dev-logs echo "→ Restarting FlintTrade backend in background…" # ';' under a Windows Python (this script already assumes MSYS: taskkill, # netstat -ano), ':' everywhere else. A ':' join splits on the drive letter. case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) PATH_SEP=';' ;; *) PATH_SEP=':' ;; esac PYTHONIOENCODING=utf-8 PYTHONUTF8=1 \ PYTHONPATH="${FLINTTRADE_PYTHONPATH}${PATH_SEP}${PYTHONPATH:-}" \ nohup "$PYTHON_BIN" -m flinttrade_core.app > .local/dev-logs/backend.log 2>&1 & disown # Wait up to 20 s for port 5100. for _ in $(seq 1 20); do if netstat -ano 2>/dev/null | grep -qE "127\.0\.0\.1:5100\s+.*LISTENING"; then echo "→ Backend up on http://127.0.0.1:5100" exit 0 fi sleep 1 done echo "! Backend did not bind 5100 in 20 s — check .local/dev-logs/backend.log" >&2 exit 1 fi echo "" echo "Done. Next: start the backend manually, or re-run with --restart."