feat: factory reset, backup restore, auto-identity creation

- system.factory-reset RPC: wipes user data, preserves images/node_key
- Factory Reset button in Settings with confirmation modal
- backup.restore-identity RPC: decrypts and restores DID key
- Restore from Backup panel in OnboardingIntro first screen
- Auto-create default identity with Nostr key on boot if none exist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-15 05:18:12 +00:00
co-authored by Claude Opus 4.6
parent de8dcee155
commit d1e14c4269
9 changed files with 346 additions and 13 deletions
+75
View File
@@ -590,3 +590,78 @@ async fn read_temperatures() -> Result<Vec<serde_json::Value>> {
Ok(temps)
}
impl RpcHandler {
/// system.factory-reset — Wipe all user data and restart.
/// Preserves container images and node_key (hardware identity).
pub(super) async fn handle_system_factory_reset(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
// Safety check: require { confirm: true }
let confirmed = params
.as_ref()
.and_then(|p| p.get("confirm"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
if !confirmed {
anyhow::bail!("Factory reset requires {{ \"confirm\": true }}");
}
tracing::warn!("Factory reset initiated — wiping user data");
let data_dir = &self.config.data_dir;
// Stop all running containers
if let Ok(client) = archipelago_container::PodmanClient::detect().await {
if let Ok(containers) = client.list_containers().await {
for c in &containers {
let _ = client.stop_container(&c.names).await;
}
}
}
// Delete user data (preserving node_key and container images)
let files_to_remove = [
"user.json",
"onboarding.json",
"peers.json",
"server-name",
];
for f in &files_to_remove {
let path = data_dir.join(f);
if path.exists() {
let _ = tokio::fs::remove_file(&path).await;
}
}
let dirs_to_remove = [
"identities",
"credentials",
"did-cache",
"dwn",
];
for d in &dirs_to_remove {
let path = data_dir.join(d);
if path.exists() {
let _ = tokio::fs::remove_dir_all(&path).await;
}
}
// Clear all sessions
self.session_store.invalidate_all_except("").await;
tracing::warn!("Factory reset complete — restarting service");
// Restart the service via systemd
tokio::spawn(async {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
let _ = std::process::Command::new("sudo")
.args(["systemctl", "restart", "archipelago"])
.spawn();
});
Ok(serde_json::json!({ "status": "resetting" }))
}
}