This commit is contained in:
firestar5683
2026-04-06 02:33:47 -05:00
parent aa427a6ee1
commit cc653f3bad
3 changed files with 189 additions and 3 deletions
@@ -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;
@@ -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`
<div class="toggle-control-wrapper">
<section class="toggle-control-widget">
@@ -98,6 +124,18 @@ export function ToggleControl() {
<button class="toggle-control-button" @click="${confirmResetStock}">
Reset Toggles to Stock
</button>
<div class="toggle-control-danger-zone">
<div class="toggle-control-danger-title">WARNING: Factory Reset</div>
<p class="toggle-control-danger-text">
Last resort only. This wipes params, backups, themes, models, maps, and route data, then reboots the device.
</p>
<button
class="toggle-control-button toggle-control-button-danger toggle-control-button-save-me"
@click="${confirmSaveMe}"
disabled="${() => state.factoryResetBusy}">
${() => state.factoryResetBusy ? "Starting..." : "SAVE ME"}
</button>
</div>
</section>
${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"
}) : ""}
`
}
+99 -3
View File
@@ -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"