Initial resurrection kit — 81 scripts, 62 references, configs, systemd units, crons, Docker compose files, Caddy config, master README
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
#!/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)}")
|
||||
Reference in New Issue
Block a user