Refactor bot and integrate services
This commit is contained in:
163
handlers/backup.py
Normal file
163
handlers/backup.py
Normal file
@@ -0,0 +1,163 @@
|
||||
import asyncio
|
||||
import json
|
||||
from datetime import datetime
|
||||
from aiogram import F
|
||||
from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from app import dp
|
||||
from auth import is_admin_msg
|
||||
from keyboards import backup_kb
|
||||
from lock_utils import acquire_lock, release_lock
|
||||
from services.backup import backup_badge, last_backup, restore_help
|
||||
from services.runner import run_cmd
|
||||
|
||||
|
||||
async def cmd_repo_stats(msg: Message):
|
||||
await msg.answer("⏳ Loading repo stats…", reply_markup=backup_kb)
|
||||
|
||||
# --- restore-size stats ---
|
||||
rc1, raw1 = await run_cmd(
|
||||
["restic", "stats", "--json"],
|
||||
use_restic_env=True,
|
||||
timeout=30
|
||||
)
|
||||
if rc1 != 0:
|
||||
await msg.answer(raw1, reply_markup=backup_kb)
|
||||
return
|
||||
|
||||
restore = json.loads(raw1)
|
||||
|
||||
# --- raw-data stats ---
|
||||
rc2, raw2 = await run_cmd(
|
||||
["restic", "stats", "--json", "--mode", "raw-data"],
|
||||
use_restic_env=True,
|
||||
timeout=30
|
||||
)
|
||||
if rc2 != 0:
|
||||
await msg.answer(raw2, reply_markup=backup_kb)
|
||||
return
|
||||
|
||||
raw = json.loads(raw2)
|
||||
|
||||
# --- snapshots count ---
|
||||
rc3, raw_snaps = await run_cmd(
|
||||
["restic", "snapshots", "--json"],
|
||||
use_restic_env=True,
|
||||
timeout=20
|
||||
)
|
||||
snaps = len(json.loads(raw_snaps)) if rc3 == 0 else "n/a"
|
||||
|
||||
msg_text = (
|
||||
"📦 **Repository stats**\n\n"
|
||||
f"🧉 Snapshots: {snaps}\n"
|
||||
f"📁 Files: {restore.get('total_file_count', 'n/a')}\n"
|
||||
f"💽 Logical size: {restore.get('total_size', 0) / (1024**3):.2f} GiB\n"
|
||||
f"🧱 Stored data: {raw.get('total_pack_size', 0) / (1024**2):.2f} MiB\n"
|
||||
)
|
||||
|
||||
await msg.answer(msg_text, reply_markup=backup_kb, parse_mode="Markdown")
|
||||
|
||||
|
||||
async def cmd_backup_status(msg: Message):
|
||||
await msg.answer("⏳ Loading snapshots…", reply_markup=backup_kb)
|
||||
|
||||
async def worker():
|
||||
rc, raw = await run_cmd(
|
||||
["restic", "snapshots", "--json"],
|
||||
use_restic_env=True,
|
||||
timeout=30
|
||||
)
|
||||
if rc != 0:
|
||||
await msg.answer(raw, reply_markup=backup_kb)
|
||||
return
|
||||
|
||||
snaps = json.loads(raw)
|
||||
if not snaps:
|
||||
await msg.answer("📦 Snapshots: none", reply_markup=backup_kb)
|
||||
return
|
||||
|
||||
snaps.sort(key=lambda s: s["time"], reverse=True)
|
||||
|
||||
# --- badge ---
|
||||
last = snaps[0]
|
||||
last_time = datetime.fromisoformat(
|
||||
last["time"].replace("Z", "+00:00")
|
||||
)
|
||||
badge = backup_badge(last_time)
|
||||
|
||||
# --- buttons ---
|
||||
rows = []
|
||||
for s in snaps[:5]:
|
||||
t = datetime.fromisoformat(
|
||||
s["time"].replace("Z", "+00:00")
|
||||
)
|
||||
rows.append([
|
||||
InlineKeyboardButton(
|
||||
text=f"🧉 {s['short_id']} | {t:%Y-%m-%d %H:%M}",
|
||||
callback_data=f"snap:{s['short_id']}"
|
||||
)
|
||||
])
|
||||
|
||||
kb = InlineKeyboardMarkup(inline_keyboard=rows)
|
||||
|
||||
await msg.answer(
|
||||
f"📦 Snapshots ({len(snaps)})\n{badge}",
|
||||
reply_markup=kb
|
||||
)
|
||||
|
||||
asyncio.create_task(worker())
|
||||
|
||||
|
||||
async def cmd_backup_now(msg: Message):
|
||||
if not acquire_lock("backup"):
|
||||
await msg.answer("⚠️ Backup уже выполняется", reply_markup=backup_kb)
|
||||
return
|
||||
|
||||
await msg.answer("▶️ Backup запущен", reply_markup=backup_kb)
|
||||
|
||||
async def worker():
|
||||
try:
|
||||
rc, out = await run_cmd(["sudo", "/usr/local/bin/backup.py", "restic-backup"], timeout=6 * 3600)
|
||||
await msg.answer(("✅ OK\n" if rc == 0 else "❌ FAIL\n") + out, reply_markup=backup_kb)
|
||||
finally:
|
||||
release_lock("backup")
|
||||
|
||||
asyncio.create_task(worker())
|
||||
|
||||
|
||||
async def cmd_last_backup(msg: Message):
|
||||
try:
|
||||
text = await asyncio.to_thread(last_backup)
|
||||
except Exception as e:
|
||||
await msg.answer(f"❌ Last backup failed: {type(e).__name__}: {e}", reply_markup=backup_kb)
|
||||
return
|
||||
await msg.answer(text, reply_markup=backup_kb)
|
||||
|
||||
|
||||
@dp.message(F.text == "📦 Status")
|
||||
async def bs(msg: Message):
|
||||
if is_admin_msg(msg):
|
||||
await cmd_backup_status(msg)
|
||||
|
||||
|
||||
@dp.message(F.text == "📊 Repo stats")
|
||||
async def rs(msg: Message):
|
||||
if is_admin_msg(msg):
|
||||
await cmd_repo_stats(msg)
|
||||
|
||||
|
||||
@dp.message(F.text == "📦 Last backup")
|
||||
async def lb(msg: Message):
|
||||
if is_admin_msg(msg):
|
||||
await cmd_last_backup(msg)
|
||||
|
||||
|
||||
@dp.message(F.text == "▶️ Run backup")
|
||||
async def br(msg: Message):
|
||||
if is_admin_msg(msg):
|
||||
await cmd_backup_now(msg)
|
||||
|
||||
|
||||
@dp.message(F.text == "🧯 Restore help")
|
||||
async def rh(msg: Message):
|
||||
if is_admin_msg(msg):
|
||||
await msg.answer(restore_help(), reply_markup=backup_kb)
|
||||
Reference in New Issue
Block a user