Backup and restore FrogPilot

Added functionality to automatically and manually backup/restore FrogPilot.
This commit is contained in:
FrogAi
2024-03-10 20:01:57 -07:00
parent 12eb25c0fd
commit 0cd83a9021
2 changed files with 221 additions and 0 deletions

View File

@@ -4,7 +4,9 @@ import os
import signal import signal
import subprocess import subprocess
import sys import sys
import threading
import traceback import traceback
from pathlib import Path
from typing import List, Tuple, Union from typing import List, Tuple, Union
from cereal import log from cereal import log
@@ -404,6 +406,35 @@ def manager_thread() -> None:
if params_memory.get_bool("FrogPilotTogglesUpdated"): if params_memory.get_bool("FrogPilotTogglesUpdated"):
update_frogpilot_params(params, params_memory) update_frogpilot_params(params, params_memory)
def backup_openpilot():
# Configure the auto backup generator
backup_dir_path = '/data/backups'
Path(backup_dir_path).mkdir(parents=True, exist_ok=True)
# Sort backups by creation time and only keep the 5 newest auto generated ones
auto_backups = sorted([f for f in os.listdir(backup_dir_path) if f.endswith("_auto")],
key=lambda x: os.path.getmtime(os.path.join(backup_dir_path, x)))
for old_backup in auto_backups:
subprocess.run(['sudo', 'rm', '-rf', os.path.join(backup_dir_path, old_backup)], check=True)
print(f"Deleted oldest backup to maintain limit: {old_backup}")
# Generate the backup folder name from the current git commit and branch name
branch = get_short_branch()
commit = get_commit_date()[12:-16]
backup_folder_name = f"{branch}_{commit}_auto"
# Check if the backup folder already exists
backup_path = os.path.join(backup_dir_path, backup_folder_name)
if os.path.exists(backup_path):
print(f"Backup folder {backup_folder_name} already exists. Skipping backup.")
return
# Create the backup directory and copy openpilot to it
Path(backup_path).mkdir(parents=True, exist_ok=True)
subprocess.run(['sudo', 'cp', '-a', '/data/openpilot/.', backup_path + '/'], check=True)
print(f"Successfully backed up openpilot to {backup_folder_name}.")
def main() -> None: def main() -> None:
# Create the long term param storage folder # Create the long term param storage folder
try: try:
@@ -413,6 +444,13 @@ def main() -> None:
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f"Failed to remount /persist. Error: {e}") print(f"Failed to remount /persist. Error: {e}")
# Backup the current version of openpilot
try:
backup_thread = threading.Thread(target=backup_openpilot)
backup_thread.start()
except subprocess.CalledProcessError as e:
print(f"Failed to backup openpilot. Error: {e}")
manager_init() manager_init()
if os.getenv("PREPAREONLY") is not None: if os.getenv("PREPAREONLY") is not None:
return return

View File

@@ -2,6 +2,8 @@
#include <cassert> #include <cassert>
#include <cmath> #include <cmath>
#include <iomanip>
#include <iostream>
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <vector> #include <vector>
@@ -268,6 +270,187 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
}); });
addItem(deleteStorageParamsBtn); addItem(deleteStorageParamsBtn);
// Backup FrogPilot
std::vector<QString> frogpilotBackupOptions{tr("Backup"), tr("Delete"), tr("Restore")};
FrogPilotButtonsControl *frogpilotBackup = new FrogPilotButtonsControl("FrogPilot Backups", "Backup, delete, or restore your FrogPilot backups.", "", frogpilotBackupOptions);
connect(frogpilotBackup, &FrogPilotButtonsControl::buttonClicked, [=](int id) {
QDir backupDir("/data/backups");
if (id == 0) {
QString nameSelection = InputDialog::getText(tr("Name your backup"), this, "", false, 1);
if (!nameSelection.isEmpty()) {
std::thread([=]() {
frogpilotBackup->setValue("Backing up...");
std::string fullBackupPath = backupDir.absolutePath().toStdString() + "/" + nameSelection.toStdString();
std::ostringstream commandStream;
commandStream << "mkdir -p " << std::quoted(fullBackupPath)
<< " && rsync -av /data/openpilot/ " << std::quoted(fullBackupPath + "/");
std::string command = commandStream.str();
int result = std::system(command.c_str());
if (result == 0) {
std::cout << "Backup successful to " << fullBackupPath << std::endl;
frogpilotBackup->setValue("Success!");
std::this_thread::sleep_for(std::chrono::seconds(3));
frogpilotBackup->setValue("");
} else {
std::cerr << "Backup failed with error code: " << result << std::endl;
frogpilotBackup->setValue("Failed...");
std::this_thread::sleep_for(std::chrono::seconds(3));
frogpilotBackup->setValue("");
}
}).detach();
}
} else if (id == 1) {
QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
QString selection = MultiOptionDialog::getSelection(tr("Select a backup to delete"), backupNames, "", this);
if (!selection.isEmpty()) {
if (!ConfirmationDialog::confirm(tr("Are you sure you want to delete this backup?"), tr("Delete"), this)) return;
std::thread([=]() {
frogpilotBackup->setValue("Deleting...");
QDir dirToDelete(backupDir.absoluteFilePath(selection));
if (dirToDelete.removeRecursively()) {
frogpilotBackup->setValue("Deleted!");
} else {
frogpilotBackup->setValue("Failed...");
}
std::this_thread::sleep_for(std::chrono::seconds(3));
frogpilotBackup->setValue("");
}).detach();
}
} else {
QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
QString selection = MultiOptionDialog::getSelection(tr("Select a restore point"), backupNames, "", this);
if (!selection.isEmpty()) {
if (!ConfirmationDialog::confirm(tr("Are you sure you want to restore this version of FrogPilot?"), tr("Restore"), this)) return;
std::thread([=]() {
frogpilotBackup->setValue("Restoring...");
std::string sourcePath = backupDir.absolutePath().toStdString() + "/" + selection.toStdString();
std::string targetPath = "/data/safe_staging/finalized";
std::string consistentFilePath = targetPath + "/.overlay_consistent";
std::ostringstream commandStream;
commandStream << "rsync -av --delete --exclude='.overlay_consistent' " << std::quoted(sourcePath + "/") << " " << std::quoted(targetPath + "/");
std::string command = commandStream.str();
int result = std::system(command.c_str());
if (result == 0) {
std::cout << "Restore successful from " << sourcePath << " to " << targetPath << std::endl;
std::ofstream consistentFile(consistentFilePath);
if (consistentFile) {
consistentFile.close();
std::cout << ".overlay_consistent file created successfully." << std::endl;
} else {
std::cerr << "Failed to create .overlay_consistent file." << std::endl;
frogpilotBackup->setValue("Failed...");
std::this_thread::sleep_for(std::chrono::seconds(3));
frogpilotBackup->setValue("");
}
Hardware::reboot();
} else {
std::cerr << "Restore failed with error code: " << result << std::endl;
}
}).detach();
}
}
});
addItem(frogpilotBackup);
// Backup toggles
std::vector<QString> toggleBackupOptions{tr("Backup"), tr("Delete"), tr("Restore")};
FrogPilotButtonsControl *toggleBackup = new FrogPilotButtonsControl("Toggle Backups", "Backup, delete, or restore your toggle backups.", "", toggleBackupOptions);
connect(toggleBackup, &FrogPilotButtonsControl::buttonClicked, [=](int id) {
QDir backupDir("/data/toggle_backups");
if (id == 0) {
QString nameSelection = InputDialog::getText(tr("Name your backup"), this, "", false, 1);
if (!nameSelection.isEmpty()) {
std::thread([=]() {
toggleBackup->setValue("Backing up...");
std::string fullBackupPath = backupDir.absolutePath().toStdString() + "/" + nameSelection.toStdString() + "/";
std::ostringstream commandStream;
commandStream << "mkdir -p " << std::quoted(fullBackupPath)
<< " && rsync -av /data/params/d/ " << std::quoted(fullBackupPath);
std::string command = commandStream.str();
int result = std::system(command.c_str());
if (result == 0) {
std::cout << "Backup successful to " << fullBackupPath << std::endl;
toggleBackup->setValue("Success!");
std::this_thread::sleep_for(std::chrono::seconds(3));
toggleBackup->setValue("");
} else {
std::cerr << "Backup failed with error code: " << result << std::endl;
toggleBackup->setValue("Failed...");
std::this_thread::sleep_for(std::chrono::seconds(3));
toggleBackup->setValue("");
}
}).detach();
}
} else if (id == 1) {
QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
QString selection = MultiOptionDialog::getSelection(tr("Select a backup to delete"), backupNames, "", this);
if (!selection.isEmpty()) {
if (!ConfirmationDialog::confirm(tr("Are you sure you want to delete this backup?"), tr("Delete"), this)) return;
std::thread([=]() {
toggleBackup->setValue("Deleting...");
QDir dirToDelete(backupDir.absoluteFilePath(selection));
if (dirToDelete.removeRecursively()) {
toggleBackup->setValue("Deleted!");
} else {
toggleBackup->setValue("Failed...");
}
std::this_thread::sleep_for(std::chrono::seconds(3));
toggleBackup->setValue("");
}).detach();
}
} else {
QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
QString selection = MultiOptionDialog::getSelection(tr("Select a restore point"), backupNames, "", this);
if (!selection.isEmpty()) {
if (!ConfirmationDialog::confirm(tr("Are you sure you want to restore this toggle backup?"), tr("Restore"), this)) return;
std::thread([=]() {
toggleBackup->setValue("Restoring...");
std::string sourcePath = backupDir.absolutePath().toStdString() + "/" + selection.toStdString() + "/";
std::string targetPath = "/data/params/d/";
std::ostringstream commandStream;
commandStream << "rsync -av --delete " << std::quoted(sourcePath) << " " << std::quoted(targetPath);
std::string command = commandStream.str();
int result = std::system(command.c_str());
if (result == 0) {
std::cout << "Restore successful from " << sourcePath << " to " << targetPath << std::endl;
toggleBackup->setValue("Success!");
paramsMemory.putBool("FrogPilotTogglesUpdated", true);
std::this_thread::sleep_for(std::chrono::seconds(3));
toggleBackup->setValue("");
paramsMemory.putBool("FrogPilotTogglesUpdated", false);
} else {
std::cerr << "Restore failed with error code: " << result << std::endl;
toggleBackup->setValue("Failed...");
std::this_thread::sleep_for(std::chrono::seconds(3));
toggleBackup->setValue("");
}
}).detach();
}
}
});
addItem(toggleBackup);
QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) {
for (auto btn : findChildren<ButtonControl *>()) { for (auto btn : findChildren<ButtonControl *>()) {
btn->setEnabled(offroad); btn->setEnabled(offroad);