Backup and restore FrogPilot
Added functionality to automatically and manually backup/restore FrogPilot.
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user