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`
${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"