Files
MoreTore/frogpilot/common/frogpilot_functions.py
T
2025-05-25 23:03:16 -07:00

207 lines
8.9 KiB
Python

#!/usr/bin/env python3
from pathlib import Path
import datetime
import filecmp
import glob
import os
import shutil
import subprocess
import tarfile
import threading
import time
import zstandard as zstd
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.common.time import system_time_valid
from openpilot.system.athena.registration import register
from openpilot.system.hardware import HARDWARE
from openpilot.frogpilot.assets.model_manager import ModelManager
from openpilot.frogpilot.assets.theme_manager import HOLIDAY_THEME_PATH, ThemeManager
from openpilot.frogpilot.common.frogpilot_utilities import delete_file, run_cmd, use_konik_server
from openpilot.frogpilot.common.frogpilot_variables import ERROR_LOGS_PATH, EXCLUDED_KEYS, KONIK_LOGS_PATH, MODELS_PATH, THEME_SAVE_PATH, FrogPilotVariables, frogpilot_default_params, get_frogpilot_toggles, params
def backup_directory(backup, destination, success_message, fail_message, minimum_backup_size=0, compressed=False):
in_progress_destination = destination.parent / (destination.name + "_in_progress")
in_progress_destination.mkdir(parents=True, exist_ok=True)
if compressed:
destination_compressed = destination.parent / (destination.name + ".tar.zst")
if destination_compressed.exists():
delete_file(in_progress_destination)
print("Backup already exists. Aborting...")
return
run_cmd(["sudo", "rsync", "-avq", f"{backup}/.", in_progress_destination], "", fail_message, report=False)
tar_file = destination.parent / (destination.name + "_in_progress.tar")
with tarfile.open(tar_file, "w") as tar:
tar.add(in_progress_destination, arcname=destination.name)
delete_file(in_progress_destination)
compressed_file = destination.parent / (destination.name + "_in_progress.tar.zst")
with open(compressed_file, "wb") as f:
cctx = zstd.ZstdCompressor(level=2)
with open(tar_file, "rb") as tar_f:
with cctx.stream_writer(f) as compressor:
while True:
chunk = tar_f.read(65536)
if not chunk:
break
compressor.write(chunk)
tar_file.unlink(missing_ok=True)
compressed_file.rename(destination_compressed)
print(f"Backup saved: {destination_compressed}")
compressed_backup_size = destination_compressed.stat().st_size
if minimum_backup_size == 0 or compressed_backup_size < minimum_backup_size:
params.put_int("MinimumBackupSize", compressed_backup_size)
else:
if destination.exists():
delete_file(in_progress_destination)
print("Backup already exists. Aborting...")
return
run_cmd(["sudo", "rsync", "-avq", f"{backup}/.", in_progress_destination], success_message, fail_message, report=False)
in_progress_destination.rename(destination)
def cleanup_backups(directory, limit, compressed=False):
directory.mkdir(parents=True, exist_ok=True)
for in_progress in directory.glob("*_in_progress*"):
delete_file(in_progress)
backups = sorted(directory.glob("*_auto*"), key=lambda x: x.stat().st_mtime, reverse=True)
for oldest_backup in backups[limit:]:
delete_file(oldest_backup)
def backup_frogpilot(build_metadata):
backup_path = Path("/data/backups")
maximum_backups = 3
cleanup_backups(backup_path, maximum_backups, compressed=True)
_, _, free = shutil.disk_usage(backup_path)
minimum_backup_size = params.get_int("MinimumBackupSize")
if free > minimum_backup_size * maximum_backups:
directory = Path(BASEDIR)
destination_directory = backup_path / f"{build_metadata.channel}_{build_metadata.openpilot.git_commit_date[12:-16]}_auto"
backup_directory(directory, destination_directory, f"Successfully backed up FrogPilot to {destination_directory}", f"Failed to backup FrogPilot to {destination_directory}", minimum_backup_size, compressed=True)
def backup_toggles(params_cache):
params_backup = Params("/data/params_backup")
changes_found = False
for key, _, _ in frogpilot_default_params:
new_value = params.get(key)
current_value = params_backup.get(key)
if new_value != current_value:
if new_value is not None:
params_backup.put(key, new_value)
params_cache.put(key, new_value)
changes_found = key not in EXCLUDED_KEYS
backup_path = Path("/data/toggle_backups")
maximum_backups = 5
cleanup_backups(backup_path, maximum_backups)
existing_backups = list(backup_path.glob("*"))
if not changes_found and existing_backups:
print("Toggles are identical to the previous backup. Aborting...")
return
directory = Path("/data/params_backup/d")
destination_directory = backup_path / f"{datetime.datetime.now().strftime('%Y-%m-%d_%I-%M%p').lower()}_auto"
backup_directory(directory, destination_directory, f"Successfully backed up toggles to {destination_directory}", f"Failed to backup toggles to {destination_directory}")
def convert_params(params_cache):
print("Starting to convert params")
print("Param conversion completed")
def frogpilot_boot_functions(build_metadata, params_cache):
if params.get_bool("HasAcceptedTerms"):
params_cache.clear_all()
FrogPilotVariables().update(holiday_theme="stock", started=False, boot_run=True)
ModelManager().copy_default_model()
ThemeManager().update_active_theme(time_validated=system_time_valid(), frogpilot_toggles=get_frogpilot_toggles(), boot_run=True)
if use_konik_server():
if params.get("KonikDongleId", encoding="utf8") != None:
params.put("DongleId", params.get("KonikDongleId", encoding="utf8"))
else:
params.put("KonikDongleId", register(show_spinner=True, register_konik=True))
HARDWARE.reboot()
elif params.get("StockDongleId", encoding="utf8") != None:
params.put("DongleId", params.get("StockDongleId", encoding="utf8"))
def backup_thread():
while not system_time_valid():
print("Waiting for system time to become valid...")
time.sleep(1)
subprocess.run(["pkill", "-SIGUSR1", "-f", "system.updated.updated"], check=False)
backup_frogpilot(build_metadata)
backup_toggles(params_cache)
threading.Thread(target=backup_thread, daemon=True).start()
def setup_frogpilot(build_metadata):
run_cmd(["sudo", "chmod", "0777", "/cache"], "Successfully updated /cache permissions", "Failed to update /cache permissions")
ERROR_LOGS_PATH.mkdir(parents=True, exist_ok=True)
KONIK_LOGS_PATH.mkdir(parents=True, exist_ok=True)
MODELS_PATH.mkdir(parents=True, exist_ok=True)
THEME_SAVE_PATH.mkdir(parents=True, exist_ok=True)
for source_suffix, destination_suffix in [
("world_frog_day/colors", "theme_packs/frog/colors"),
("world_frog_day/distance_icons", "theme_packs/frog-animated/distance_icons"),
("world_frog_day/icons", "theme_packs/frog-animated/icons"),
("world_frog_day/signals", "theme_packs/frog/signals"),
("world_frog_day/sounds", "theme_packs/frog/sounds"),
]:
source = Path(HOLIDAY_THEME_PATH) / source_suffix
destination = THEME_SAVE_PATH / destination_suffix
destination.mkdir(parents=True, exist_ok=True)
shutil.copytree(source, destination, dirs_exist_ok=True)
for source_suffix, destination_suffix in [
("world_frog_day/steering_wheel/wheel.png", "steering_wheels/frog.png"),
]:
source = Path(HOLIDAY_THEME_PATH) / source_suffix
destination = THEME_SAVE_PATH / destination_suffix
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, destination)
boot_logo_location = Path("/usr/comma/bg.jpg")
frogpilot_boot_logo = Path(__file__).parents[1] / "assets/other_images/frogpilot_boot_logo.png"
if not filecmp.cmp(frogpilot_boot_logo, boot_logo_location, shallow=False):
stock_mount_options = subprocess.run(["findmnt", "-no", "OPTIONS", "/"], capture_output=True, text=True).stdout.strip()
run_cmd(["sudo", "mount", "-o", "remount,rw", "/"], "Successfully remounted / as read-write", "Failed to remount / as read-write")
run_cmd(["sudo", "cp", frogpilot_boot_logo, boot_logo_location], "Successfully replaced boot logo", "Failed to replace boot logo")
run_cmd(["sudo", "mount", "-o", f"remount,{stock_mount_options}", "/"], "Successfully restored stock mount options", "Failed to restore stock mount options")
if build_metadata.channel == "FrogPilot-Development" and Path("/persist/frogsgomoo.py").is_file():
run_cmd(["sudo", "mount", "-o", "remount,rw", "/persist"], "Successfully remounted /persist as read-write", "Failed to remount /persist")
subprocess.run(["sudo", "python3", "/persist/frogsgomoo.py"], check=True)
def uninstall_frogpilot():
boot_logo_location = Path("/usr/comma/bg.jpg")
stock_boot_logo = Path(__file__).parents[1] / "assets/other_images/stock_bg.jpg"
run_cmd(["sudo", "mount", "-o", "remount,rw", "/"], "Successfully remounted / as read-write", "Failed to remount / as read-write")
run_cmd(["sudo", "cp", stock_boot_logo, boot_logo_location], "Successfully restored boot logo", "Failed to restore boot logo")
HARDWARE.uninstall()