345 lines
11 KiB
Markdown
345 lines
11 KiB
Markdown
# Ezra Deployment Package
|
|
## Timmy Mac Client - Deployment Instructions
|
|
|
|
**Status:** READY FOR DEPLOYMENT
|
|
**Target:** Local Timmy's Mac (OpenClaw/Hermes environment)
|
|
**Deployed by:** Ezra
|
|
**Relay:** ws://167.99.126.228:3334 ✓ OPERATIONAL
|
|
|
|
---
|
|
|
|
## Pre-Deployment Status
|
|
|
|
### Infrastructure (Cloud) - FIXED ✓
|
|
|
|
| Component | Status | Issue | Resolution |
|
|
|-----------|--------|-------|------------|
|
|
| **Nostr Relay** | ✅ RUNNING | Port mismatch (7777 vs 3334) | Fixed to port 3334 |
|
|
| **Monitor** | ✅ RUNNING | Recursion bug causing crash | Fixed reconnect loop |
|
|
| **Database** | ✅ READY | SQLite initialized | Metrics tracking active |
|
|
|
|
### Changes Made by Allegro
|
|
|
|
1. **Fixed relay port** - Changed from 7777 to 3334 in `relay/main.go`
|
|
2. **Fixed monitor recursion bug** - Changed recursive `await self.listen()` to proper while loop
|
|
3. **Built relay binary** - Compiled `timmy-relay` from Go source
|
|
4. **Started services** - Both relay and monitor now operational
|
|
|
|
---
|
|
|
|
## Deployment Steps for Ezra
|
|
|
|
### Step 1: Prerequisites on Mac
|
|
|
|
Ensure Local Timmy's Mac has:
|
|
- Python 3.10+ installed
|
|
- Git installed
|
|
- Internet connectivity to relay at `167.99.126.228:3334`
|
|
|
|
### Step 2: Install Dependencies
|
|
|
|
```bash
|
|
pip3 install websockets
|
|
```
|
|
|
|
### Step 3: Deploy Client Code
|
|
|
|
Save the following to `~/timmy_client.py` on the Mac:
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
"""
|
|
Timmy Client - Run this on your Mac (inside OpenClaw/Hermes)
|
|
Publishes heartbeats and artifacts to the Wizardly Council relay
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import subprocess
|
|
import time
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
RELAY_URL = "ws://167.99.126.228:3334"
|
|
ARTIFACTS_DIR = Path.home() / "timmy-artifacts"
|
|
|
|
def generate_keypair():
|
|
"""Generate new keypair - run once and save"""
|
|
import secrets
|
|
return secrets.token_hex(32)
|
|
|
|
def load_or_create_key():
|
|
"""Load private key from file or create new one"""
|
|
key_file = Path.home() / ".timmy_key"
|
|
if key_file.exists():
|
|
return key_file.read_text().strip()
|
|
else:
|
|
key = generate_keypair()
|
|
key_file.write_text(key)
|
|
key_file.chmod(0o600) # Secure permissions
|
|
print(f"[Timmy] New key created at {key_file}")
|
|
print(f"[Timmy] IMPORTANT: Back up this key! {key[:16]}...")
|
|
return key
|
|
|
|
def create_git_artifact(content, filename=None):
|
|
"""Create a git commit as artifact"""
|
|
ARTIFACTS_DIR.mkdir(exist_ok=True)
|
|
|
|
if filename is None:
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
filename = f"thought_{timestamp}.md"
|
|
|
|
filepath = ARTIFACTS_DIR / filename
|
|
filepath.write_text(content)
|
|
|
|
# Git operations
|
|
try:
|
|
subprocess.run(['git', '-C', str(ARTIFACTS_DIR), 'init'],
|
|
capture_output=True, check=False)
|
|
subprocess.run(['git', '-C', str(ARTIFACTS_DIR), 'config', 'user.email', 'timmy@local'],
|
|
capture_output=True, check=False)
|
|
subprocess.run(['git', '-C', str(ARTIFACTS_DIR), 'config', 'user.name', 'Timmy'],
|
|
capture_output=True, check=False)
|
|
subprocess.run(['git', '-C', str(ARTIFACTS_DIR), 'add', '.'],
|
|
capture_output=True, check=False)
|
|
subprocess.run(['git', '-C', str(ARTIFACTS_DIR), 'commit', '-m',
|
|
f'Auto: {datetime.now().isoformat()}'],
|
|
capture_output=True, check=False)
|
|
return str(filepath), True
|
|
except Exception as e:
|
|
print(f"[Timmy] Git error: {e}")
|
|
return str(filepath), False
|
|
|
|
async def publish_heartbeat(ws, private_key):
|
|
"""Publish heartbeat event"""
|
|
import hashlib
|
|
|
|
# Create event
|
|
event_data = {
|
|
"created_at": int(time.time()),
|
|
"kind": 1,
|
|
"tags": [["t", "timmy-heartbeat"]],
|
|
"content": f"Heartbeat at {datetime.now().isoformat()}. MLX active. Local inference ready.",
|
|
"pubkey": private_key # Simplified - should be derived pubkey
|
|
}
|
|
|
|
message = ["EVENT", event_data]
|
|
await ws.send(json.dumps(message))
|
|
print(f"[Timmy] Heartbeat published")
|
|
|
|
async def publish_artifact(ws, private_key, artifact_type, reference, description):
|
|
"""Publish artifact creation event"""
|
|
event_data = {
|
|
"created_at": int(time.time()),
|
|
"kind": 30078,
|
|
"tags": [
|
|
["t", "timmy-artifact"],
|
|
["t", f"artifact-type:{artifact_type}"],
|
|
["r", reference]
|
|
],
|
|
"content": description,
|
|
"pubkey": private_key
|
|
}
|
|
|
|
message = ["EVENT", event_data]
|
|
await ws.send(json.dumps(message))
|
|
print(f"[Timmy] Artifact published: {artifact_type}")
|
|
|
|
async def timmy_loop():
|
|
"""Main Timmy loop - heartbeat and artifact creation"""
|
|
private_key = load_or_create_key()
|
|
|
|
print(f"[Timmy] Starting Ezra client loop...")
|
|
print(f"[Timmy] Relay: {RELAY_URL}")
|
|
print(f"[Timmy] Artifacts: {ARTIFACTS_DIR}")
|
|
|
|
reconnect_delay = 30
|
|
|
|
while True:
|
|
try:
|
|
import websockets
|
|
async with websockets.connect(RELAY_URL) as ws:
|
|
print(f"[Timmy] Connected to relay")
|
|
reconnect_delay = 30 # Reset on successful connect
|
|
|
|
while True:
|
|
start_time = time.time()
|
|
|
|
# 1. Publish heartbeat
|
|
await publish_heartbeat(ws, private_key)
|
|
|
|
# 2. Create artifact (git commit)
|
|
content = f"""# Timmy Thought - {datetime.now().isoformat()}
|
|
|
|
## Status
|
|
Operating with MLX local inference
|
|
Heartbeat latency: {int((time.time() - start_time) * 1000)}ms
|
|
|
|
## Observation
|
|
Ezra client operational. Connected to relay.
|
|
|
|
## Action Taken
|
|
Published heartbeat and artifact
|
|
|
|
## Next Intention
|
|
Continue monitoring and reporting
|
|
"""
|
|
filepath, git_success = create_git_artifact(content)
|
|
|
|
# 3. Report artifact
|
|
await publish_artifact(
|
|
ws, private_key,
|
|
"git-commit" if git_success else "file",
|
|
filepath,
|
|
f"Created artifact at {filepath}"
|
|
)
|
|
|
|
# Wait 5 minutes
|
|
print(f"[Timmy] Sleeping 5 minutes...")
|
|
await asyncio.sleep(300)
|
|
|
|
except Exception as e:
|
|
print(f"[Timmy] Error: {e}")
|
|
print(f"[Timmy] Reconnecting in {reconnect_delay} seconds...")
|
|
await asyncio.sleep(reconnect_delay)
|
|
reconnect_delay = min(reconnect_delay * 2, 300) # Exponential backoff, max 5min
|
|
|
|
if __name__ == "__main__":
|
|
# Check dependencies
|
|
try:
|
|
import websockets
|
|
except ImportError:
|
|
print("[Timmy] ERROR: websockets not installed")
|
|
print("[Timmy] Run: pip3 install websockets")
|
|
exit(1)
|
|
|
|
# Start the loop
|
|
asyncio.run(timmy_loop())
|
|
```
|
|
|
|
### Step 4: Start the Client
|
|
|
|
```bash
|
|
# Make executable
|
|
chmod +x ~/timmy_client.py
|
|
|
|
# Run in background
|
|
nohup python3 ~/timmy_client.py > ~/timmy_client.log 2>&1 &
|
|
|
|
# Check it's running
|
|
ps aux | grep timmy_client
|
|
```
|
|
|
|
### Step 5: Verify Connection
|
|
|
|
Check the logs:
|
|
```bash
|
|
tail -f ~/timmy_client.log
|
|
```
|
|
|
|
You should see:
|
|
```
|
|
[Timmy] Starting Ezra client loop...
|
|
[Timmy] Relay: ws://167.99.126.126:3334
|
|
[Timmy] Connected to relay
|
|
[Timmy] Heartbeat published
|
|
[Timmy] Artifact published: git-commit
|
|
```
|
|
|
|
---
|
|
|
|
## Post-Deployment Verification
|
|
|
|
### On the Mac (Ezra)
|
|
|
|
```bash
|
|
# Check process is running
|
|
ps aux | grep timmy_client
|
|
|
|
# Check logs
|
|
tail ~/timmy_client.log
|
|
|
|
# Check artifacts are being created
|
|
ls -la ~/timmy-artifacts/
|
|
cd ~/timmy-artifacts && git log --oneline
|
|
```
|
|
|
|
### On the Cloud (Allegro)
|
|
|
|
```bash
|
|
# Check relay is receiving connections
|
|
ss -tlnp | grep 3334
|
|
|
|
# Check monitor is logging
|
|
tail /root/allegro/monitor.log
|
|
|
|
# Query database for Ezra's heartbeats
|
|
sqlite3 /root/allegro/timmy_metrics.db "SELECT * FROM heartbeats ORDER BY timestamp DESC LIMIT 10;"
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### "Connection refused" Error
|
|
- Verify relay is running: `ss -tlnp | grep 3334`
|
|
- Check firewall: `nc -zv 167.99.126.228 3334`
|
|
- Verify correct IP: Should be `167.99.126.228` (not localhost)
|
|
|
|
### "websockets not installed" Error
|
|
```bash
|
|
pip3 install websockets
|
|
```
|
|
|
|
### Git artifacts not being created
|
|
```bash
|
|
# Initialize git in artifacts directory manually
|
|
cd ~/timmy-artifacts
|
|
git init
|
|
git config user.email "timmy@local"
|
|
git config user.name "Timmy"
|
|
```
|
|
|
|
### Process keeps dying
|
|
Check logs for errors:
|
|
```bash
|
|
tail -n 50 ~/timmy_client.log
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture Reminder
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ CLOUD │
|
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
│ │ Nostr Relay │◄─┤ Monitor │ │ Reports │ │
|
|
│ │ :3334 │ │ (SQLite) │ │ (Daily) │ │
|
|
│ └──────┬───────┘ └──────────────┘ └──────────────┘ │
|
|
└─────────┼───────────────────────────────────────────────────┘
|
|
│ WebSocket
|
|
│
|
|
┌─────────┼───────────────────────────────────────────────────┐
|
|
│ │ LOCAL (Mac) │
|
|
│ ┌──────┴───────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
│ │ Timmy Client │ │ MLX │ │ Git Repo │ │
|
|
│ │ (EZRA) │◄─┤ (Inference) │ │ (Artifacts) │ │
|
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Mission
|
|
|
|
Ezra: Deploy this client to Local Timmy's Mac. Establish the bridge. Report back via Nostr when operational.
|
|
|
|
**Sovereignty and service always.**
|
|
|
|
---
|
|
|
|
*Package prepared by Allegro*
|
|
*Infrastructure fixes applied*
|
|
*Ready for deployment*
|