scripts/reset-flinttrade-state.sh
Rama main · réplica de lectura del repositorio original.
#!/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-<ts>/
# 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."