feat(telegram): implement automatic scheduled backups
This commit is contained in:
@ -3,6 +3,7 @@
|
|||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
core_scripts_dir = Path(__file__).resolve().parents[1]
|
core_scripts_dir = Path(__file__).resolve().parents[1]
|
||||||
if str(core_scripts_dir) not in sys.path:
|
if str(core_scripts_dir) not in sys.path:
|
||||||
@ -10,12 +11,10 @@ if str(core_scripts_dir) not in sys.path:
|
|||||||
|
|
||||||
from paths import TELEGRAM_ENV
|
from paths import TELEGRAM_ENV
|
||||||
|
|
||||||
|
def update_env_file(api_token, admin_user_ids, backup_interval):
|
||||||
|
|
||||||
|
|
||||||
def update_env_file(api_token, admin_user_ids):
|
|
||||||
TELEGRAM_ENV.write_text(f"""API_TOKEN={api_token}
|
TELEGRAM_ENV.write_text(f"""API_TOKEN={api_token}
|
||||||
ADMIN_USER_IDS=[{admin_user_ids}]
|
ADMIN_USER_IDS=[{admin_user_ids}]
|
||||||
|
BACKUP_INTERVAL_HOUR={backup_interval}
|
||||||
""")
|
""")
|
||||||
|
|
||||||
def create_service_file():
|
def create_service_file():
|
||||||
@ -32,12 +31,12 @@ Restart=always
|
|||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
""")
|
""")
|
||||||
|
|
||||||
def start_service(api_token, admin_user_ids):
|
def start_service(api_token, admin_user_ids, backup_interval=12):
|
||||||
if subprocess.run(["systemctl", "is-active", "--quiet", "hysteria-telegram-bot.service"]).returncode == 0:
|
if subprocess.run(["systemctl", "is-active", "--quiet", "hysteria-telegram-bot.service"]).returncode == 0:
|
||||||
print("The hysteria-telegram-bot.service is already running.")
|
print("The hysteria-telegram-bot.service is already running.")
|
||||||
return
|
return
|
||||||
|
|
||||||
update_env_file(api_token, admin_user_ids)
|
update_env_file(api_token, admin_user_ids, backup_interval)
|
||||||
create_service_file()
|
create_service_file()
|
||||||
|
|
||||||
subprocess.run(["systemctl", "daemon-reload"])
|
subprocess.run(["systemctl", "daemon-reload"])
|
||||||
@ -55,20 +54,60 @@ def stop_service():
|
|||||||
TELEGRAM_ENV.unlink(missing_ok=True)
|
TELEGRAM_ENV.unlink(missing_ok=True)
|
||||||
print("\nHysteria bot service stopped and disabled. .env file removed.")
|
print("\nHysteria bot service stopped and disabled. .env file removed.")
|
||||||
|
|
||||||
|
def set_backup_interval(backup_interval):
|
||||||
|
if not os.path.exists(TELEGRAM_ENV):
|
||||||
|
print("Error: The .env file does not exist. Please start the bot first.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
with open(TELEGRAM_ENV, 'r') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
with open(TELEGRAM_ENV, 'w') as f:
|
||||||
|
found = False
|
||||||
|
for line in lines:
|
||||||
|
if line.strip().startswith("BACKUP_INTERVAL_HOUR"):
|
||||||
|
f.write(f"BACKUP_INTERVAL_HOUR={backup_interval}\n")
|
||||||
|
found = True
|
||||||
|
else:
|
||||||
|
f.write(line)
|
||||||
|
if not found:
|
||||||
|
f.write(f"BACKUP_INTERVAL_HOUR={backup_interval}\n")
|
||||||
|
|
||||||
|
print(f"Backup interval has been set to {backup_interval} hour(s). Restarting the bot to apply changes...")
|
||||||
|
subprocess.run(["systemctl", "restart", "hysteria-telegram-bot.service"])
|
||||||
|
|
||||||
|
def print_usage():
|
||||||
|
print("Usage:")
|
||||||
|
print(" python3 runbot.py start <API_TOKEN> <ADMIN_USER_IDS> [BACKUP_INTERVAL_HOUR]")
|
||||||
|
print(" python3 runbot.py stop")
|
||||||
|
print(" python3 runbot.py set_backup_interval <BACKUP_INTERVAL_HOUR>")
|
||||||
|
print("\nDefault BACKUP_INTERVAL_HOUR is 12.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
print("Usage: python3 runbot.py {start|stop} <API_TOKEN> <ADMIN_USER_IDS>")
|
print_usage()
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
action = sys.argv[1]
|
action = sys.argv[1]
|
||||||
|
|
||||||
if action == "start":
|
if action == "start":
|
||||||
if len(sys.argv) != 4:
|
if not (4 <= len(sys.argv) <= 5):
|
||||||
print("Usage: python3 runbot.py start <API_TOKEN> <ADMIN_USER_IDS>")
|
print_usage()
|
||||||
sys.exit(1)
|
|
||||||
start_service(sys.argv[2], sys.argv[3])
|
api_token = sys.argv[2]
|
||||||
|
admin_user_ids = sys.argv[3]
|
||||||
|
|
||||||
|
if len(sys.argv) == 5:
|
||||||
|
backup_interval = sys.argv[4]
|
||||||
|
start_service(api_token, admin_user_ids, backup_interval)
|
||||||
|
else:
|
||||||
|
start_service(api_token, admin_user_ids)
|
||||||
|
|
||||||
elif action == "stop":
|
elif action == "stop":
|
||||||
stop_service()
|
stop_service()
|
||||||
|
elif action == "set_backup_interval":
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print_usage()
|
||||||
|
set_backup_interval(sys.argv[2])
|
||||||
else:
|
else:
|
||||||
print("Usage: python3 runbot.py {start|stop} <API_TOKEN> <ADMIN_USER_IDS>")
|
print_usage()
|
||||||
sys.exit(1)
|
|
||||||
@ -6,10 +6,13 @@ import json
|
|||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from telebot import types
|
from telebot import types
|
||||||
from utils.command import *
|
from utils.command import *
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == '💾 Backup Server')
|
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == '💾 Backup Server')
|
||||||
def backup_server(message):
|
def backup_server(message):
|
||||||
@ -21,9 +24,9 @@ def backup_server(message):
|
|||||||
|
|
||||||
if "Error" in result:
|
if "Error" in result:
|
||||||
bot.reply_to(message, f"Backup failed: {result}")
|
bot.reply_to(message, f"Backup failed: {result}")
|
||||||
else:
|
return
|
||||||
bot.reply_to(message, "Backup completed successfully!")
|
|
||||||
|
|
||||||
|
# bot.reply_to(message, "Backup completed successfully!")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
files = [f for f in os.listdir(BACKUP_DIRECTORY) if f.endswith('.zip')]
|
files = [f for f in os.listdir(BACKUP_DIRECTORY) if f.endswith('.zip')]
|
||||||
@ -36,6 +39,61 @@ def backup_server(message):
|
|||||||
if latest_backup_file:
|
if latest_backup_file:
|
||||||
backup_file_path = os.path.join(BACKUP_DIRECTORY, latest_backup_file)
|
backup_file_path = os.path.join(BACKUP_DIRECTORY, latest_backup_file)
|
||||||
with open(backup_file_path, 'rb') as f:
|
with open(backup_file_path, 'rb') as f:
|
||||||
bot.send_document(message.chat.id, f, caption=f"Backup completed: {latest_backup_file}")
|
bot.send_document(message.chat.id, f, caption=f"Manual backup completed: {latest_backup_file}")
|
||||||
else:
|
else:
|
||||||
bot.reply_to(message, "No backup file found after the backup process.")
|
bot.reply_to(message, "No backup file found after the backup process.")
|
||||||
|
|
||||||
|
def perform_and_send_backup():
|
||||||
|
# print("Starting automatic backup...")
|
||||||
|
|
||||||
|
backup_command = f"python3 {CLI_PATH} backup-hysteria"
|
||||||
|
result = run_cli_command(backup_command)
|
||||||
|
|
||||||
|
if "Error" in result:
|
||||||
|
print(f"Automatic backup failed: {result}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
files = [f for f in os.listdir(BACKUP_DIRECTORY) if f.endswith('.zip')]
|
||||||
|
files.sort(key=lambda x: os.path.getctime(os.path.join(BACKUP_DIRECTORY, x)), reverse=True)
|
||||||
|
latest_backup_file = files[0] if files else None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to locate the backup file during automatic backup: {str(e)}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if latest_backup_file:
|
||||||
|
backup_file_path = os.path.join(BACKUP_DIRECTORY, latest_backup_file)
|
||||||
|
admin_ids_str = os.getenv("ADMIN_USER_IDS", "[]").strip('[]')
|
||||||
|
admin_ids = [int(uid.strip()) for uid in admin_ids_str.split(',') if uid.strip()]
|
||||||
|
|
||||||
|
if not admin_ids:
|
||||||
|
print("No admin user IDs found for automatic backup.")
|
||||||
|
return
|
||||||
|
|
||||||
|
for admin_id in admin_ids:
|
||||||
|
try:
|
||||||
|
with open(backup_file_path, 'rb') as f:
|
||||||
|
bot.send_document(admin_id, f, caption=f"Automatic hourly backup: {latest_backup_file}")
|
||||||
|
print(f"Automatic backup sent to admin: {admin_id}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to send automatic backup to admin {admin_id}: {e}")
|
||||||
|
else:
|
||||||
|
print("No backup file found after automatic backup process.")
|
||||||
|
|
||||||
|
|
||||||
|
def backup_scheduler():
|
||||||
|
interval_hours_str = os.getenv("BACKUP_INTERVAL_HOUR")
|
||||||
|
if not interval_hours_str or not interval_hours_str.isdigit() or int(interval_hours_str) <= 0:
|
||||||
|
print("Automatic backup interval is not set or is invalid. Scheduler will not run.")
|
||||||
|
return
|
||||||
|
|
||||||
|
interval_hours = int(interval_hours_str)
|
||||||
|
interval_seconds = interval_hours * 3600
|
||||||
|
print(f"Automatic backup scheduler started. Interval: {interval_hours} hour(s).")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(interval_seconds)
|
||||||
|
perform_and_send_backup()
|
||||||
|
|
||||||
|
scheduler_thread = threading.Thread(target=backup_scheduler, daemon=True)
|
||||||
|
scheduler_thread.start()
|
||||||
Reference in New Issue
Block a user