Implement robust scheduler for traffic monitoring and backups

- Replace cron jobs with a systemd service for better reliability
- Add file locking mechanism to prevent conflicts on users.json
- Schedule traffic status updates every minute with proper error handling
- Maintain 6-hour backup schedule with independent lock management
- Add comprehensive logging for easier troubleshooting

This change resolves issues where system updates or other programs
would cause conflicts with the traffic monitoring process accessing
the users.json file simultaneously.
This commit is contained in:
Whispering Wind
2025-05-15 21:18:23 +03:30
committed by GitHub
parent 70b16efedf
commit 08f90a3246

111
core/scripts/scheduler.py Normal file
View File

@ -0,0 +1,111 @@
#!/usr/bin/env python3
import os
import sys
import time
import schedule
import logging
import subprocess
import fcntl
import datetime
from pathlib import Path
from paths import *
logging.basicConfig(
level=logging.WARNING,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("/var/log/hysteria_scheduler.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger("HysteriaScheduler")
# Constants
BASE_DIR = Path("/etc/hysteria")
VENV_ACTIVATE = BASE_DIR / "hysteria2_venv/bin/activate"
# CLI_PATH = BASE_DIR / "core/cli.py"
LOCK_FILE = "/tmp/hysteria_scheduler.lock"
def acquire_lock():
try:
lock_fd = open(LOCK_FILE, 'w')
fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
return lock_fd
except IOError:
logger.warning("Another process is already running and has the lock")
return None
def release_lock(lock_fd):
if lock_fd:
fcntl.flock(lock_fd, fcntl.LOCK_UN)
lock_fd.close()
def run_command(command, log_success=False):
activate_cmd = f"source {VENV_ACTIVATE}"
full_cmd = f"{activate_cmd} && {command}"
try:
result = subprocess.run(
full_cmd,
shell=True,
executable="/bin/bash",
capture_output=True,
text=True
)
if result.returncode != 0:
logger.error(f"Command failed: {full_cmd}")
logger.error(f"Error: {result.stderr}")
elif log_success:
logger.info(f"Command executed successfully: {full_cmd}")
return result.returncode == 0
except Exception as e:
logger.exception(f"Exception running command: {full_cmd}")
return False
def check_traffic_status():
lock_fd = acquire_lock()
if not lock_fd:
return
try:
success = run_command(f"python3 {CLI_PATH} traffic-status --no-gui", log_success=False)
if not success:
pass
finally:
release_lock(lock_fd)
def backup_hysteria():
lock_fd = acquire_lock()
if not lock_fd:
logger.warning("Skipping backup due to lock")
return
try:
run_command(f"python3 {CLI_PATH} backup-hysteria", log_success=True)
finally:
release_lock(lock_fd)
def main():
logger.info("Starting Hysteria Scheduler")
schedule.every(1).minutes.do(check_traffic_status)
schedule.every(6).hours.do(backup_hysteria)
backup_hysteria()
while True:
try:
schedule.run_pending()
time.sleep(1)
except KeyboardInterrupt:
logger.info("Shutting down scheduler")
break
except Exception as e:
logger.exception("Error in main loop")
time.sleep(60)
if __name__ == "__main__":
main()