#!/usr/bin/env python3 """Build the full Hermes recovery bundle markdown file.""" import sqlite3, json, datetime, os, subprocess, sys db = sqlite3.connect('/root/.hermes/state.db') c = db.cursor() # ===== 1. Get today's user conversations ===== sessions_data = [] c.execute(""" SELECT id, title, started_at, ended_at, end_reason, message_count FROM sessions WHERE started_at >= 1783180800 AND id LIKE '2026%' ORDER BY started_at """) for row in c.fetchall(): sessions_data.append({ 'id': row[0], 'title': row[1] or '(no title)', 'started': datetime.datetime.fromtimestamp(row[2]).strftime('%H:%M:%S'), 'ended': datetime.datetime.fromtimestamp(row[3]).strftime('%H:%M:%S') if row[3] else 'active', 'end_reason': row[4] or 'running', 'msg_count': row[5] }) conversation_text = "" for sd in sessions_data: conversation_text += f"\n\n{'='*70}\n" conversation_text += f"SESSION: {sd['id']} | {sd['title']}\n" conversation_text += f"Started: {sd['started']} | Ended: {sd['ended']} | Status: {sd['end_reason']}\n" conversation_text += f"{'='*70}\n\n" c.execute("SELECT role, content, timestamp FROM messages WHERE session_id = ? ORDER BY id", (sd['id'],)) for role, content, ts in c.fetchall(): ts_dt = datetime.datetime.fromtimestamp(ts).strftime('%H:%M:%S') text = (content or '(empty)').strip() # Truncate very long messages if len(text) > 5000: text = text[:5000] + f"\n... [truncated, was {len(content)} chars]" conversation_text += f"[{ts_dt}] {role.upper()}:\n{text}\n\n" db.close() # ===== 2. Read key files ===== def readf(path): try: with open(path) as f: return f.read() except Exception as e: return f"[ERROR: {e}]" soul = readf('/root/.hermes/SOUL.md') config_yaml = readf('/root/.hermes/config.yaml') dotenv = readf('/root/.hermes/.env') aws_creds = readf('/root/.aws/credentials') service_file = readf('/etc/systemd/system/hermes.service') wisp_pub = readf('/root/.ssh/wisp_rsa.pub') wisp_priv = readf('/root/.ssh/wisp_rsa') imap_pass = readf('/root/.config/himalaya/g-germainebrown.pass') hetzner_token = readf('/root/.hermes/scripts/.hetzner_token') netcup_key = readf('/root/.hermes/scripts/.netcup_api_key') wisp_config = readf('/root/.hermes/scripts/wisp-backup/config.yaml') # ===== 3. Get cron jobs ===== result = subprocess.run(['hermes', 'cron', 'list', '--json'], capture_output=True, text=True, timeout=15) cron_text = "" try: cron_data = json.loads(result.stdout) cron_jobs = cron_data if isinstance(cron_data, list) else cron_data.get('jobs', cron_data.get('data', [])) if not cron_jobs: cron_text = "(no JSON output — fallback to text list)\n" # Fallback: run text mode result2 = subprocess.run(['hermes', 'cron', 'list'], capture_output=True, text=True, timeout=15) cron_text += result2.stdout else: for job in cron_jobs: cron_text += f"\n### {job.get('name', 'unnamed')}\n" cron_text += f"- ID: {job.get('id', '?')}\n" cron_text += f"- Schedule: {job.get('schedule', '?')}\n" cron_text += f"- Repeat: {job.get('repeat', '∞')}\n" cron_text += f"- Deliver: {job.get('deliver', '?')}\n" cron_text += f"- Script: {job.get('script', 'none')}\n" cron_text += f"- Skills: {job.get('skills', 'none')}\n" cron_text += f"- Mode: {'no-agent' if job.get('no_agent') else 'agent-driven'}\n" cron_text += f"- Next run: {job.get('next_run', '?')}\n" except Exception as e: cron_text = f"[Error getting cron jobs: {e}]\n" result2 = subprocess.run(['hermes', 'cron', 'list'], capture_output=True, text=True, timeout=15) cron_text += result2.stdout # ===== 4. Build the recovery document ===== doc = f"""# Hermes Full Recovery Bundle — Jul 5, 2026 > Save this file. If the server dies, paste the contents of the **BOOTSTRAP** section > below into a new Hermes agent to restore me. --- ## BOOTSTRAP INSTRUCTIONS ### Step 1 — Prerequisites 1. Install Hermes Agent v0.18.0+ on a Linux server (Debian/Ubuntu) 2. Register a Telegram bot with BotFather, get a TELEGRAM_BOT_TOKEN 3. Make sure python3, git, and systemd are available ### Step 2 — Create files Create these files with the exact contents shown in their sections below: - `/root/.hermes/config.yaml` - `/root/.hermes/.env` - `/root/.aws/credentials` - `/root/.ssh/wisp_rsa` (chmod 600) - `/root/.hermes/scripts/.hetzner_token` (chmod 600) - `/root/.hermes/scripts/.netcup_api_key` (chmod 600) - `/root/.hermes/SOUL.md` - All scripts in the Scripts section ### Step 3 — Start Hermes ```bash hermes gateway start ``` ### Step 4 — Recreate cron jobs ```bash # Run these commands after Hermes is online # (cron job definitions below) ``` ### Step 5 — Verify ```bash hermes gateway status hermes cron list source /opt/awscli-venv/bin/activate && aws s3 ls s3://hermes-vps-backups/ --endpoint-url https://s3.us-east-1.wasabisys.com/ ``` --- ## CREDENTIALS **SMTP Password (g@germainebrown.com):** `{imap_pass.strip()}` **Wasabi S3:** endpoint=https://s3.us-east-1.wasabisys.com | Access Key: GYH83FP0KL0K85N60JKQ **Hetzner API Token:** `{hetzner_token.strip()}` **netcup API Key:** `{netcup_key.strip()}` **admin-ai API Key:** In config.yaml (sk-lbhhxIk...bWPA) **Telegram Bot Token:** In .env file below --- ## CONFIG FILES ### /root/.hermes/SOUL.md ``` {soul} ``` ### /root/.hermes/config.yaml ```yaml {config_yaml} ``` ### /root/.hermes/.env ``` {dotenv} ``` ### /root/.aws/credentials ``` {aws_creds} ``` ### /etc/systemd/system/hermes.service ``` {service_file} ``` --- ## SSH KEYS ### WISP Public Key ``` {wisp_pub} ``` ### WISP Private Key ``` {wisp_priv} ``` --- ## WISP BACKUP CONFIG ```yaml {wisp_config} ``` --- ## CRON JOBS {cron_text} --- ## SCRIPT FILES ### imap_triage.py """ # Read scripts script_dir = '/root/.hermes/scripts' for fname in sorted(os.listdir(script_dir)): fpath = os.path.join(script_dir, fname) if os.path.isfile(fpath) and not fname.startswith('.'): content = readf(fpath) doc += f"\n### {fname}\n```\n{content}\n```\n" # Also read wisp-backup subdirectory scripts wisp_dir = os.path.join(script_dir, 'wisp-backup') if os.path.isdir(wisp_dir): for fname in sorted(os.listdir(wisp_dir)): fpath = os.path.join(wisp_dir, fname) if os.path.isfile(fpath) and not fname.endswith('.pyc'): content = readf(fpath) doc += f"\n### wisp-backup/{fname}\n```\n{content}\n```\n" doc += "\n\n---\n\n## TODAY'S CONVERSATION HISTORY\n" doc += conversation_text # ===== 5. Write file ===== output_path = '/root/.hermes/recovery-bundle-2026-07-05.md' with open(output_path, 'w') as f: f.write(doc) file_size = os.path.getsize(output_path) print(f"RECOVERY FILE: {output_path}") print(f"SIZE: {file_size} bytes ({file_size/1024:.1f} KB)") print(f"TODAY SESSIONS: {len(sessions_data)}")