diff --git a/docker/setup_host/config_system.sh b/docker/setup_host/config_system.sh index 8ff3b8c4..3461bad6 100755 --- a/docker/setup_host/config_system.sh +++ b/docker/setup_host/config_system.sh @@ -613,6 +613,37 @@ configure_headless_mode() { } # Configures Linux PTP (ptp4l and phc2sys) as Master. +# Sets up automatic disk cleaning service +setup_disk_clean() { + info "Configuring automated disk cleanup service..." + + local service_src="${APOLLO_ROOT_DIR}/docker/setup_host/etc/systemd/system/disk-clean.service" + local timer_src="${APOLLO_ROOT_DIR}/docker/setup_host/etc/systemd/system/disk-clean.timer" + local target_service="/etc/systemd/system/disk-clean.service" + local target_timer="/etc/systemd/system/disk-clean.timer" + + if [[ ! -f "${service_src}" ]] || [[ ! -f "${timer_src}" ]]; then + error "Disk cleanup service files not found in ${APOLLO_ROOT_DIR}/docker/setup_host/etc/systemd/system/." + return 1 + fi + + info "Copying service and timer files to systemd..." + sudo sed "s#__APOLLO_ROOT_DIR__#${APOLLO_ROOT_DIR}#g" "${service_src}" | sudo tee "${target_service}" > /dev/null + sudo cp "${timer_src}" "${target_timer}" + + info "Reloading systemd, enabling and starting disk-clean.timer..." + sudo systemctl daemon-reload + sudo systemctl enable --now disk-clean.timer + + if [ $? -ne 0 ]; then + error "Failed to enable disk-clean timer." + return 1 + fi + + success "Disk cleanup service configured successfully." + return 0 +} + configure_ptp() { info "Configuring Linux PTP as Master..." @@ -753,6 +784,15 @@ setup_host_machine() { fi # 8. Install and enable the autostart service (optional) + if prompt_yes_no "Configure automated disk cleanup service?" Y; then + if ! setup_disk_clean; then + error "Failed to configure disk cleanup service." + return 1 + fi + else + info "Skipping disk cleanup service configuration." + fi + if prompt_yes_no "Install autostart service?" N; then if ! install_autostart_service; then error "Failed to install the autostart service." diff --git a/docker/setup_host/disk_clean.sh b/docker/setup_host/disk_clean.sh new file mode 100755 index 00000000..d86ec655 --- /dev/null +++ b/docker/setup_host/disk_clean.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +set -euo pipefail + +LOG_PREFIX="[WheelOS Disk Clean]" +logit() { + echo "$(date '+%Y-%m-%d %H:%M:%S') $LOG_PREFIX $1" +} + +# 1. Resolve APOLLO_ROOT from docker/.env +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +APOLLO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ENV_FILE="${APOLLO_ROOT}/docker/.env" + +if [[ -f "${ENV_FILE}" ]]; then + # Extract APOLLO_ROOT from .env safely + ENV_APOLLO_ROOT=$(grep -E "^APOLLO_ROOT=" "${ENV_FILE}" | cut -d '=' -f 2 | tr -d '"'\') + if [[ -n "${ENV_APOLLO_ROOT}" ]]; then + APOLLO_ROOT="${ENV_APOLLO_ROOT}" + fi +else + logit "Warning: ${ENV_FILE} not found. Using script relative path: ${APOLLO_ROOT}" +fi + +# Remove trailing slashes +APOLLO_ROOT="${APOLLO_ROOT%/}" + +if [[ -z "${APOLLO_ROOT}" || ! -d "${APOLLO_ROOT}" ]]; then + logit "ERROR: Invalid or missing APOLLO_ROOT: ${APOLLO_ROOT}" + exit 1 +fi + +# 2. Strict Target Directories +TARGET_BAG_DIR="${APOLLO_ROOT}/data/bag" +TARGET_LOG_DIR="${APOLLO_ROOT}/data/log" +TARGET_CORE_DIR="${APOLLO_ROOT}/data/core" + +BAG_RETENTION_DAYS=7 +LOG_RETENTION_DAYS=14 +CORE_RETENTION_DAYS=3 +MIN_FREE_SPACE=20 # Percentage + +# 3. Space Check +if df -h "${APOLLO_ROOT}" &>/dev/null; then + CURRENT_USE=$(df --output=pcent "${APOLLO_ROOT}" | sed '1d' | tr -d ' %') + CURRENT_FREE=$((100 - CURRENT_USE)) + + if [[ "$CURRENT_FREE" -gt "$MIN_FREE_SPACE" ]]; then + logit "Space is sufficient (${CURRENT_FREE}% free > ${MIN_FREE_SPACE}%). Exit." + exit 0 + fi +fi + +# 4. Check if recording is active to avoid corrupting running tasks +if pgrep -f "cyber_recorder" > /dev/null; then + logit "Warning: AD system is recording (cyber_recorder is running). Abort." + exit 0 +fi + +# 5. Clean function with strict directory validation +AUTHORIZED_DIRS=("${TARGET_BAG_DIR}" "${TARGET_LOG_DIR}" "${TARGET_CORE_DIR}") + +clean_directory() { + local target_dir=$1 + local max_days=$2 + local pattern=$3 + + # Safety Check: Prevent relative paths or path traversal + # Ensure canonical path + local real_target_dir + real_target_dir=$(realpath "${target_dir}" 2>/dev/null || echo "") + + local is_authorized=false + for auth_dir in "${AUTHORIZED_DIRS[@]}"; do + local real_auth_dir + real_auth_dir=$(realpath "${auth_dir}" 2>/dev/null || echo "") + if [[ -n "${real_target_dir}" && "${real_target_dir}" == "${real_auth_dir}" ]]; then + is_authorized=true + break + fi + done + + if [[ "${is_authorized}" != true ]]; then + logit "ERROR: Unauthorized access directory ${target_dir}. Aborting." + return 1 + fi + + if [[ ! -d "${target_dir}" ]]; then + logit "Directory ${target_dir} does not exist. Skipping." + return 0 + fi + + logit "Cleaning ${target_dir} | older than ${max_days} days | pattern: ${pattern}" + + # Use find to locate and safely remove files + find "${target_dir}" -maxdepth 3 -type f -name "${pattern}" -mtime +"${max_days}" -print0 | while IFS= read -r -d '' file; do + if pgrep -f "cyber_recorder" > /dev/null; then + logit "AD task started! Abort deletion of $file." + exit 0 + fi + rm -f "${file}" + logit "Deleted: ${file}" + sleep 0.1 # I/O throttling + done +} + +logit "Initiating cleanup..." + +clean_directory "${TARGET_BAG_DIR}" "${BAG_RETENTION_DAYS}" "*.record.*" + +clean_directory "${TARGET_LOG_DIR}" "${LOG_RETENTION_DAYS}" "*.log.*" +clean_directory "${TARGET_LOG_DIR}" "${LOG_RETENTION_DAYS}" "*.out" +clean_directory "${TARGET_LOG_DIR}" "${LOG_RETENTION_DAYS}" "*.INFO*" +clean_directory "${TARGET_LOG_DIR}" "${LOG_RETENTION_DAYS}" "*.WARNING*" +clean_directory "${TARGET_LOG_DIR}" "${LOG_RETENTION_DAYS}" "*.ERROR*" +clean_directory "${TARGET_LOG_DIR}" "${LOG_RETENTION_DAYS}" "*.FATAL*" + +clean_directory "${TARGET_CORE_DIR}" "${CORE_RETENTION_DAYS}" "core_*" +clean_directory "${TARGET_CORE_DIR}" "${CORE_RETENTION_DAYS}" "core.*" + +logit "Cleanup finished." diff --git a/docker/setup_host/etc/systemd/system/disk-clean.service b/docker/setup_host/etc/systemd/system/disk-clean.service new file mode 100644 index 00000000..e2e0fd35 --- /dev/null +++ b/docker/setup_host/etc/systemd/system/disk-clean.service @@ -0,0 +1,22 @@ +[Unit] +Description=WheelOS Disk Cleanup Service +After=local-fs.target + +[Service] +Type=oneshot +# We use __APOLLO_ROOT_DIR__ which will be replaced during setup +ExecStart=/bin/bash __APOLLO_ROOT_DIR__/docker/setup_host/disk_clean.sh +StandardOutput=syslog +StandardError=syslog +SyslogIdentifier=wheelos-disk-clean + +# Resource control and limits (production safe) +Nice=19 +IOSchedulingClass=idle +CPUSchedulingPolicy=idle +OOMScoreAdjust=1000 + +# Security (prevent destructive path operations even as root) +ProtectSystem=full +NoNewPrivileges=true +PrivateTmp=true diff --git a/docker/setup_host/etc/systemd/system/disk-clean.timer b/docker/setup_host/etc/systemd/system/disk-clean.timer new file mode 100644 index 00000000..655e4b86 --- /dev/null +++ b/docker/setup_host/etc/systemd/system/disk-clean.timer @@ -0,0 +1,18 @@ +[Unit] +Description=Timer for WheelOS Disk Cleanup +After=local-fs.target + +[Timer] +# Wait 5 minutes after boot before running +OnBootSec=5min +# Run every day at 03:00 AM +# OnCalendar=*-*-* 03:00:00 + +OnUnitActiveSec=2h +# Run immediately if a scheduled run was missed +Persistent=true +# Randomize execution time slightly to avoid I/O spikes +RandomizedDelaySec=15m + +[Install] +WantedBy=timers.target