152 lines
4.7 KiB
Python
152 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
import os
|
|
import sys
|
|
import time
|
|
import schedule
|
|
import logging
|
|
import subprocess
|
|
import fcntl
|
|
import datetime
|
|
import json
|
|
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"
|
|
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:
|
|
logger.error("Failed to run traffic-status command. Aborting check.")
|
|
return
|
|
|
|
if not os.path.exists(USERS_FILE):
|
|
logger.warning(f"{USERS_FILE} not found. Skipping on-hold user check.")
|
|
return
|
|
|
|
try:
|
|
with open(USERS_FILE, 'r') as f:
|
|
users_data = json.load(f)
|
|
except (json.JSONDecodeError, IOError) as e:
|
|
logger.error(f"Error reading or parsing {USERS_FILE}: {e}")
|
|
return
|
|
|
|
users_updated = False
|
|
today_date = datetime.datetime.now().strftime("%Y-%m-%d")
|
|
|
|
for username, user_data in users_data.items():
|
|
is_on_hold = not user_data.get("account_creation_date")
|
|
|
|
if is_on_hold:
|
|
is_online = user_data.get("status") == "Online"
|
|
|
|
if is_online:
|
|
logger.info(f"On-hold user '{username}' connected. Activating account with creation date {today_date}.")
|
|
user_data["account_creation_date"] = today_date
|
|
users_updated = True
|
|
else:
|
|
if user_data.get("status") != "On-hold":
|
|
user_data["status"] = "On-hold"
|
|
users_updated = True
|
|
|
|
if users_updated:
|
|
try:
|
|
with open(USERS_FILE, 'w') as f:
|
|
json.dump(users_data, f, indent=4)
|
|
logger.info("Successfully updated users.json for on-hold users.")
|
|
except IOError as e:
|
|
logger.error(f"Error writing updates to {USERS_FILE}: {e}")
|
|
|
|
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)
|
|
|
|
# logger.info("Performing initial runs on startup...")
|
|
check_traffic_status()
|
|
backup_hysteria()
|
|
# logger.info("Initial runs complete. Entering main loop.")
|
|
|
|
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() |