optimize guko fork installer and backup tools

This commit is contained in:
OpenClaw
2026-06-23 00:58:45 +00:00
parent 044e432d69
commit 020453fa09
3 changed files with 174 additions and 7 deletions

View File

@@ -217,6 +217,58 @@ def next_manual_id(servers):
return n
def backup_bundle_path() -> Path:
ts = datetime.now().strftime('%Y%m%d-%H%M%S')
out = TMP_DIR / f'guko-backup-{ts}.tar.gz'
out.parent.mkdir(parents=True, exist_ok=True)
files = []
for path in (SERVERS_JSON, HISTORY_JSON):
if path.exists():
files.append(path)
for d in (KEYS_DIR, MEDIA_DIR, RESULTS_DIR):
if d.exists():
files.append(d)
import tarfile
with tarfile.open(out, 'w:gz') as tar:
for item in files:
arc = item.name if item.is_file() else item.name
tar.add(item, arcname=arc)
try:
os.chmod(out, 0o600)
except Exception:
pass
return out
def inventory_summary_text() -> str:
inv = load_inventory()
servers = inv.get('servers') or []
total = len(servers)
password = key = skipped = 0
for item in servers:
try:
cfg = resolve_ssh(item, inv)
if cfg.get('auth') == 'password':
password += 1
elif cfg.get('key'):
key += 1
else:
skipped += 1
except Exception:
skipped += 1
return (
'📦 <b>GUKO 配置概览</b>\n\n'
f'服务器:<b>{total}</b> 台\n'
f'密钥登录:{key}\n'
f'密码登录:{password}\n'
f'未完整配置:{skipped}\n'
f'清单:<code>{safe(str(SERVERS_JSON))}</code>\n'
f'密钥目录:<code>{safe(str(KEYS_DIR))}</code>\n'
f'结果目录:<code>{safe(str(RESULTS_DIR))}</code>'
)
def redact_inventory(inv: dict):
def clean_server(s):
out = json.loads(json.dumps(s, ensure_ascii=False))
@@ -3253,6 +3305,18 @@ async def export_config_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_document(document=f, filename='guko-servers-redacted.json', caption='已导出脱敏配置(密码已隐藏)。')
async def backup_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not await admin_guard(update): return
path = backup_bundle_path()
with path.open('rb') as f:
await update.message.reply_document(document=f, filename=path.name, caption='GUKO 完整备份servers/history/keys/media/results。请妥善保存。')
async def summary_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not await guard(update): return
await update.message.reply_text(inventory_summary_text(), parse_mode=ParseMode.HTML)
async def testall_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not await admin_guard(update): return
servers = [s for s in load_inventory().get('servers', []) if s.get('host')]
@@ -3475,6 +3539,8 @@ async def post_init(app: Application):
BotCommand('testssh', '测试服务器 SSH'),
BotCommand('testall', '批量测试 SSH'),
BotCommand('exportconfig', '导出脱敏配置'),
BotCommand('backup', '导出完整备份'),
BotCommand('summary', '配置概览'),
BotCommand('info', '查看单台操作面板:/info 名字/IP/ID'),
BotCommand('jobs', '查看后台任务'),
BotCommand('history', '查看测试历史:/history 服务器'),
@@ -3925,6 +3991,8 @@ def main():
app.add_handler(CommandHandler('testssh', testssh_cmd))
app.add_handler(CommandHandler('testall', testall_cmd))
app.add_handler(CommandHandler('exportconfig', export_config_cmd))
app.add_handler(CommandHandler('backup', backup_cmd))
app.add_handler(CommandHandler('summary', summary_cmd))
app.add_handler(CommandHandler('info', info_cmd))
app.add_handler(CommandHandler('jobs', jobs_cmd))
app.add_handler(CommandHandler('history', history_cmd))