saveme
This commit is contained in:
@@ -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"
|
||||
}) : ""}
|
||||
`
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user