From cc653f3bad430c5bb64e7f0e8dfa6d042fb0508e Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Mon, 6 Apr 2026 02:33:47 -0500 Subject: [PATCH] saveme --- .../assets/components/tools/toggles.css | 45 ++++++++ .../assets/components/tools/toggles.js | 45 ++++++++ starpilot/system/the_pond/the_pond.py | 102 +++++++++++++++++- 3 files changed, 189 insertions(+), 3 deletions(-) diff --git a/starpilot/system/the_pond/assets/components/tools/toggles.css b/starpilot/system/the_pond/assets/components/tools/toggles.css index 1b65b9cd..8218cb49 100644 --- a/starpilot/system/the_pond/assets/components/tools/toggles.css +++ b/starpilot/system/the_pond/assets/components/tools/toggles.css @@ -22,11 +22,47 @@ margin-top: var(--padding-sm); } +.toggle-control-button-save-me { + font-size: calc(var(--font-size-base) * 1.35); + letter-spacing: 0.08em; + margin-top: var(--padding-base); + min-height: 5.5rem; +} + +.toggle-control-button-danger { + background-color: rgba(224, 85, 119, 0.2); + color: var(--danger-fg); +} + +.toggle-control-button-danger:hover { + background-color: rgba(224, 85, 119, 0.35); + color: var(--danger-fg); +} + +.toggle-control-button:disabled { + cursor: not-allowed; + opacity: var(--disabled-opacity); + transform: none; +} + .toggle-control-text { color: var(--text-color); text-align: center; } +.toggle-control-danger-text { + color: var(--text-color); + margin: var(--padding-sm) 0 0; + text-align: center; +} + +.toggle-control-danger-title { + color: var(--danger-fg); + font-size: var(--font-size-lg); + font-weight: var(--font-weight-bold); + text-align: center; +} + .toggle-control-title { background-color: var(--input-bg); border-radius: var(--border-radius-lg); @@ -64,6 +100,15 @@ transform: var(--hover-scale-sm); } +.toggle-control-danger-zone { + background-color: rgba(224, 85, 119, 0.12); + border: 1px solid rgba(224, 85, 119, 0.35); + border-radius: var(--border-radius-lg); + margin-top: var(--padding-sm); + padding: var(--padding-base); + width: 100%; +} + @media only screen and (max-width: 768px) and (orientation: portrait) { .toggle-control-wrapper { flex-direction: column; diff --git a/starpilot/system/the_pond/assets/components/tools/toggles.js b/starpilot/system/the_pond/assets/components/tools/toggles.js index 351652ab..4f2097a5 100644 --- a/starpilot/system/the_pond/assets/components/tools/toggles.js +++ b/starpilot/system/the_pond/assets/components/tools/toggles.js @@ -6,6 +6,8 @@ export function ToggleControl() { const state = reactive({ showResetDefaultModal: false, showResetStockModal: false, + showSaveMeModal: false, + factoryResetBusy: false, }); const fileInput = document.createElement("input") @@ -76,6 +78,30 @@ export function ToggleControl() { fileInput.click() } + function confirmSaveMe() { + state.showSaveMeModal = true; + } + + async function runFactoryReset() { + if (state.factoryResetBusy) return + + state.showSaveMeModal = false + state.factoryResetBusy = true + try { + const response = await fetch("/api/update/factory_reset", { method: "POST" }) + const payload = await response.json() + if (!response.ok) { + throw new Error(payload.error || response.statusText || "Failed to start factory reset") + } + + showSnackbar(payload.message || "Factory reset started.") + } catch (error) { + showSnackbar(error?.message || "Failed to start factory reset", "error") + } finally { + state.factoryResetBusy = false + } + } + return html`
@@ -98,6 +124,18 @@ export function ToggleControl() { +
+
WARNING: Factory Reset
+

+ Last resort only. This wipes params, backups, themes, models, maps, and route data, then reboots the device. +

+ +
${TailscaleControl()} @@ -115,6 +153,13 @@ export function ToggleControl() { onConfirm: resetTogglesToStock, onCancel: () => { state.showResetStockModal = false; }, confirmText: "Reset to Stock" + }) : ""} + ${() => state.showSaveMeModal ? Modal({ + title: "SAVE ME", + message: "This will factory reset the device by wiping params, backups, themes, models, maps, and route data. The device will reboot when the wipe is complete. This cannot be undone.", + onConfirm: runFactoryReset, + onCancel: () => { state.showSaveMeModal = false; }, + confirmText: "Factory Reset" }) : ""} ` } diff --git a/starpilot/system/the_pond/the_pond.py b/starpilot/system/the_pond/the_pond.py index a946029c..740fa9fe 100644 --- a/starpilot/system/the_pond/the_pond.py +++ b/starpilot/system/the_pond/the_pond.py @@ -535,6 +535,7 @@ _FAST_UPDATE_PROGRESS_UPDATE_INTERVAL_S = 5.0 _FAST_UPDATE_REBOOT_NOTICE_SECONDS = 6.0 _FAST_UPDATE_FETCH_TIMEOUT_S = 60 _FAST_BRANCH_SWITCH_FETCH_TIMEOUT_S = 60 +_FACTORY_RESET_DELETE_TIMEOUT_S = 1800 _GIT_PROGRESS_PERCENT_RE = re.compile(r'([A-Za-z][A-Za-z /_-]+):\s*([0-9]{1,3})%') _GIT_SUBMODULE_SECTION_RE = re.compile(r'^\s*\[submodule\s+"[^"]+"\]\s*$', re.MULTILINE) _TESTING_GROUNDS_SCHEMA_VERSION = SHARED_TESTING_GROUNDS_SCHEMA_VERSION @@ -566,6 +567,22 @@ _fast_update_state = { "progressDetail": "", } +_FACTORY_RESET_WIPE_PATHS = [ + "/data/params", + "/persist/params", + "/cache/params", + "/data/media/0/realdata", + "/data/media/0/realdata_HD", + "/data/media/0/realdata_konik", + "/data/models", + "/data/toggle_backups", + "/data/backups", + "/data/themes", + "/data/media/0/osm/offline", + "/cache/use_HD", + "/cache/use_konik", +] + _PLOTS_POLL_INTERVAL_S = 0.75 _PLOTS_BOOT_STABILIZATION_WINDOW_S = 45.0 _PLOTS_BOOT_POLL_INTERVAL_S = 1.0 @@ -1377,6 +1394,53 @@ def _set_fast_update_error_state(message, exception): progressDetail="Update failed. See Last Error below.", ) +def _run_factory_reset_delete(path): + result = subprocess.run( + ["sudo", "rm", "-rf", path], + capture_output=True, + text=True, + timeout=_FACTORY_RESET_DELETE_TIMEOUT_S, + check=False, + ) + if result.returncode != 0: + error_text = (result.stderr or result.stdout or "sudo rm -rf failed").strip() + raise RuntimeError(f"Failed to remove {path}: {error_text}") + +def _factory_reset_worker(): + started_at = time.time() + + try: + _set_fast_update_progress(1, "Preparing factory reset", 10.0, "Cleaning up legacy device state...") + _set_fast_update_state( + running=True, + stage="factory-resetting", + message="Factory reset started. Wiping device state...", + lastError="", + lastMode="factory-reset", + startedAt=started_at, + finishedAt=0.0, + ) + _set_fast_update_progress(1, "Preparing factory reset", 100.0, "Factory reset initialized.") + + total_paths = max(1, len(_FACTORY_RESET_WIPE_PATHS)) + for index, path in enumerate(_FACTORY_RESET_WIPE_PATHS, start=1): + step_percent = ((index - 1) / total_paths) * 100.0 + _set_fast_update_progress(2, "Wiping device data", step_percent, f"Removing {path}...") + _run_factory_reset_delete(path) + _set_fast_update_progress(2, "Wiping device data", (index / total_paths) * 100.0, f"Removed {path}.") + + _set_fast_update_progress(3, "Resetting factory state", 100.0, "Legacy device state removed.") + + _set_fast_update_progress(4, "Finalizing reset", 50.0, "Syncing filesystem before reboot...") + subprocess.run(["sync"], capture_output=True, text=True, timeout=60, check=False) + _set_fast_update_progress(4, "Finalizing reset", 100.0, "Filesystem sync complete.") + + _finish_update_and_reboot( + "Factory reset complete. Device is rebooting now. Please wait for reconnection." + ) + except Exception as exception: + _set_fast_update_error_state("Factory reset failed.", exception) + def _collect_fast_update_info(include_remote=True): repo_path = str(_get_openpilot_root()) @@ -4365,8 +4429,8 @@ def setup(app): return jsonify({ **state_data, **git_data, - "isOnroad": params.get_bool("IsOnroad"), - "automaticUpdates": params.get_bool("AutomaticUpdates"), + "isOnroad": _safe_params_get_bool("IsOnroad"), + "automaticUpdates": _safe_params_get_bool("AutomaticUpdates"), "warning": "Fast update skips backup creation and finalization safeguards.", }), 200 @@ -4388,7 +4452,7 @@ def setup(app): "currentBranch": current_branch, "branches": branches, "remoteError": remote_error, - "isOnroad": params.get_bool("IsOnroad"), + "isOnroad": _safe_params_get_bool("IsOnroad"), "running": state_data.get("running", False), }), 200 @@ -4463,6 +4527,38 @@ def setup(app): "warning": "Fast update skips backup creation and finalization safeguards.", }), 202 + @app.route("/api/update/factory_reset", methods=["POST"]) + def run_factory_reset(): + if _safe_params_get_bool("IsOnroad"): + return jsonify({"error": "Cannot run a factory reset while driving."}), 409 + + with _fast_update_lock: + if _fast_update_state.get("running"): + return jsonify({"error": "Another update action is already in progress."}), 409 + _fast_update_state.update({ + "running": True, + "stage": "starting", + "message": "Starting factory reset...", + "lastError": "", + "lastBranch": "", + "lastMode": "factory-reset", + "startedAt": time.time(), + "finishedAt": 0.0, + "progressStep": 1, + "progressTotalSteps": _FAST_UPDATE_TOTAL_STEPS, + "progressStepPercent": 0.0, + "progressPercent": 0.0, + "progressLabel": "Preparing factory reset", + "progressDetail": "Initializing factory reset...", + }) + + threading.Thread(target=_factory_reset_worker, daemon=True).start() + + return jsonify({ + "message": "Factory reset started. Device will reboot when complete.", + "warning": "This wipes local params, backups, themes, models, maps, and route data.", + }), 202 + # ── Galaxy pairing (mirrors settings.cc L262-282) ────────────────── GALAXY_DIR = Path("/data/galaxy") GALAXY_AUTH_FILE = GALAXY_DIR / "glxyauth"