import os import json import secrets from pathlib import Path from typing import Dict, Any # Project base directory: can be overridden via APP_DIR or auto-detected from file location DEFAULT_APP_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(os.environ.get("APP_DIR", str(DEFAULT_APP_DIR))) # Persistent data directory: holds settings, SSH keys, job history, and backup index DATA_DIR = Path(os.environ.get("DATA_DIR", str(BASE_DIR / "data"))) CONFIG_FILE = DATA_DIR / "settings.json" BACKUP_INDEX_FILE = DATA_DIR / "backups_index.json" JOBS_FILE = DATA_DIR / "jobs_history.json" DEFAULT_KEY_FILE = DATA_DIR / "id_ed25519" DEFAULT_PUB_KEY_FILE = DATA_DIR / "id_ed25519.pub" def get_default_backup_dir() -> str: if "BACKUP_DIR" in os.environ and os.environ["BACKUP_DIR"].strip(): return os.environ["BACKUP_DIR"].strip() if os.path.exists("/backups"): return "/backups" if os.path.exists("/mnt/user"): return "/mnt/user/backups/pangolin" if os.path.exists("/var/backups"): return "/var/backups/pangolin" return str(DATA_DIR / "backups") DEFAULT_LOCAL_BACKUP = get_default_backup_dir() DEFAULT_SETTINGS = { "auth_enabled": True, "admin_password_hash": "", "admin_password_salt": "", "jwt_secret": secrets.token_hex(32), # Remote VPS Configuration "vps_host": "", # IP or Hostname of the Pangolin VPS "vps_port": 22, # SSH Port "vps_user": "root", # SSH Username "vps_auth_method": "key", # "key" or "password" "vps_ssh_key_path": str(DEFAULT_KEY_FILE), "vps_ssh_password": "", # SSH Password (if method == "password") "vps_pangolin_dir": "/opt/pangolin", # Path to Pangolin on remote VPS # Local Unraid Storage & Retention "backup_dir": DEFAULT_LOCAL_BACKUP, "retention_count": 10, # Automation & Scheduler "scheduled_backup_enabled": True, "scheduled_backup_cron": "0 3 * * *", "scheduled_backup_scope": "pangolin", "scheduled_update_check_enabled": True, # Webhooks "webhook_url": "", "webhook_type": "discord", "telegram_bot_token": "", "telegram_chat_id": "", "notify_on_backup": True, "notify_on_update": True, # App daemon "app_port": int(os.environ.get("PORT", 8888)), "app_host": os.environ.get("HOST", "0.0.0.0") } def load_settings() -> Dict[str, Any]: DATA_DIR.mkdir(parents=True, exist_ok=True) if not CONFIG_FILE.exists(): save_settings(DEFAULT_SETTINGS) return DEFAULT_SETTINGS.copy() try: with open(CONFIG_FILE, "r", encoding="utf-8") as f: data = json.load(f) for k, v in DEFAULT_SETTINGS.items(): if k not in data: data[k] = v # Auto-heal SSH key path if configured path does not exist but default key does cur_key_path = data.get("vps_ssh_key_path", "") if (not cur_key_path or not os.path.exists(cur_key_path)) and DEFAULT_KEY_FILE.exists(): data["vps_ssh_key_path"] = str(DEFAULT_KEY_FILE) # Auto-heal backup_dir if set to old non-existent path and /backups is mounted in container cur_backup_dir = data.get("backup_dir", "") if os.path.exists("/backups") and (not cur_backup_dir or not os.path.exists(cur_backup_dir)): data["backup_dir"] = "/backups" return data except Exception as e: print(f"Error loading settings: {e}. Using defaults.") return DEFAULT_SETTINGS.copy() def save_settings(settings: Dict[str, Any]) -> None: DATA_DIR.mkdir(parents=True, exist_ok=True) with open(CONFIG_FILE, "w", encoding="utf-8") as f: json.dump(settings, f, indent=2) def get_setting(key: str, default: Any = None) -> Any: settings = load_settings() return settings.get(key, default) def update_setting(key: str, value: Any) -> None: settings = load_settings() settings[key] = value save_settings(settings)