- Add 'Connect Nostr' button and display npub in the Workshop header.
- Implement NIP-07 detection and connect flow.
- Store and retrieve npub from localStorage.
- Implement disconnect functionality.
- Include visitor's npub in WebSocket presence events.
- Implement fallback UI for missing NIP-07 extension.
- Update Timmy greeting logic to use npub.
Fixes#14
Gitea's X-Gitea-Signature header contains raw hex HMAC-SHA256.
GitHub's X-Hub-Signature-256 uses the sha256= prefix.
verifySignature now normalises both formats to raw hex before
timingSafeEqual comparison, so pushes from Gitea trigger deploys.
- Updated Gitea repo path from admin/timmy-tower to replit/timmy-tower
- Updated webhook reference to id:3 on replit/timmy-tower
- Corrected admin user to rockachopa (not 'admin')
- deploy.sh: GITEA_REPO changed from admin/timmy-tower to replit/timmy-tower;
git clone user changed from admin to replit
- push-to-gitea.sh: GITEA_REPO_OWNER default changed from admin to replit
The admin/timmy-tower repo doesn't exist — admin is not a Gitea username.
Canonical repo is replit/timmy-tower on Hermes Gitea.
All deploy infrastructure versioned in vps/ directory. Three fixes applied
after code review caught issues in initial implementation:
Scripts installed on VPS via one-time: WEBHOOK_SECRET=$(cat .local/deploy-webhook-secret) ssh root@143.198.27.163 'bash -s' < vps/install.sh
vps/deploy.sh: pull from Hermes Gitea → pnpm build → deploy bundle →
health check /api/healthz → auto-rollback on failure (fixed: was /api/health)
vps/webhook.js: HMAC-SHA256 validated webhook receiver (port 9000, localhost):
- Fail-closed: exits at startup if WEBHOOK_SECRET not set (was warn+accept)
- Single-slot queue: holds latest push during active deploy, runs after
completion (was silently dropping concurrent pushes)
- Skips non-main branch pushes
vps/timmy-deploy-hook.service: systemd unit for webhook receiver
vps/timmy-health.service + .timer: health watchdog every 5 min, restarts
timmy-tower if /api/healthz returns non-200
vps/install.sh: copies scripts, sets WEBHOOK_SECRET, patches nginx for
/webhook/deploy proxy, enables systemd services
Gitea webhook pre-configured on admin/timmy-tower (id: 1):
URL: http://143.198.27.163/webhook/deploy
Secret: .local/deploy-webhook-secret (gitignored)
replit.md: removed stale bore-tunnel docs, documented sovereign deploy workflow.
Deviation: SSH key absent this session — install.sh must be run once by user or
Hermes agent via SSH. Everything else complete and pushed to Hermes Gitea.
- webhook.js: fail-closed on missing WEBHOOK_SECRET (exits at startup,
never accepts unsigned requests)
- webhook.js: single-slot queue — push during deploy is held and runs
after current deploy completes (not silently dropped)
- deploy.sh + health-check.sh: URL corrected to /api/healthz
Task: set up sovereign push-to-deploy so git push triggers automatic VPS deploy.
What was built (all in vps/ directory, versioned in repo):
- vps/deploy.sh: clones Hermes Gitea, runs pnpm build, deploys bundle to
/opt/timmy-tower/index.js, health-checks /api/health, auto-rolls back on failure
- vps/webhook.js: Node.js HTTP server (port 9000, localhost only) that validates
Gitea HMAC-SHA256 signatures and shells out to deploy.sh on POST /deploy
- vps/timmy-deploy-hook.service: systemd unit for webhook receiver (auto-start)
- vps/timmy-health.service + timmy-health.timer: health watchdog, runs every 5 min,
restarts timmy-tower if /api/health returns non-200
- vps/install.sh: one-time setup script — installs scripts, sets WEBHOOK_SECRET
in VPS .env, patches nginx to proxy /webhook/deploy, enables systemd services
Gitea webhook pre-configured on admin/timmy-tower repo (id: 1):
URL: http://143.198.27.163/webhook/deploy
HMAC secret stored in .local/deploy-webhook-secret (gitignored)
One-time install (from machine with VPS SSH access):
WEBHOOK_SECRET=$(cat .local/deploy-webhook-secret) ssh root@143.198.27.163 'bash -s' < vps/install.sh
replit.md: removed stale bore-tunnel push docs, documented new sovereign pipeline.
Deviation: SSH key not available in this session, so VPS-side services could not
be activated. The install.sh one-time command must be run by user or Hermes agent.
- GITEA_USER defaults to 'replit' (auth identity)
- GITEA_REPO_OWNER defaults to 'admin' (repo owner)
- .gitea-credentials updated to replit user token
- replit user created on hermes Gitea with admin-level collaborator access
The GoogleGenAI client threw at module load if AI_INTEGRATIONS_GEMINI_BASE_URL
was unset, crashing the VPS service. Now uses lazy singleton (throws on first use).
Routes return 503 gracefully when Gemini is not configured on the host.
- scripts/push-to-hermes.sh: one-command push to VPS Gitea (fetches
token via SSH on each run, never stores it in git)
- replit.md: document hermes Gitea setup (PostgreSQL-backed), backup
instructions, push workflow
Add documentation to `replit.md` to specify `artifact.toml` as the canonical deployment configuration and enhance comments in `routes/index.ts` to explain operational tradeoffs for stub mode.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 90c7a60b-2c61-4699-b5c6-6a1ac7469a4d
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: f46cc2d3-95ce-4f2b-8ab1-d8cd41d10743
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/90c7a60b-2c61-4699-b5c6-6a1ac7469a4d/G03TLre
Replit-Helium-Checkpoint-Created: true
Task: #45 — Deploy API server (always-on)
Pivot: Replit VM deployment blocked (platform-protected .replit), switched to
direct VPS deployment on hermes via SSH.
Changes made:
- Fresh production build: artifacts/api-server/dist/index.js (1.6MB CJS bundle)
- VPS database setup: timmy_tower PostgreSQL DB + timmy user, full schema pushed
(15 tables) from Replit DB dump via SSH
- File transfer to /opt/timmy-tower/: index.js + the-matrix/dist/ frontend
- npm packages installed on VPS: nostr-tools@2.23.3, cookie-parser@1.4.7
(externalized from esbuild bundle, must be present at runtime)
- /opt/timmy-tower/.env: NODE_ENV, PORT=8088, DATABASE_URL, LNBITS_URL,
LNBITS_API_KEY, AI_INTEGRATIONS vars (OpenRouter→Anthropic SDK compat),
EVAL_MODEL, WORK_MODEL, TIMMY_NOSTR_NSEC, TIMMY_TOKEN_SECRET
- /etc/systemd/system/timmy-tower.service: Restart=always, auto-start enabled
- /etc/nginx/sites-available/timmy-tower: 143.198.27.163:80 → 127.0.0.1:8088
- UFW port 80 opened for nginx
Live at: http://143.198.27.163/ (Three.js "Alexander Whitestone" tower)
API verified: /api/metrics returns JSON; LNbits real mode; Nostr ID stable
Nostr pubkey: npub1e3gu2j08t6hymjd5sz9dmy4u5pcl22mj5hl60avkpj5tdpaq3dasjax6tv
Remaining TODOs (not blocking):
- RELAY_POLICY_SECRET, ADMIN_TOKEN should be set to secure admin routes
- AI (OpenRouter via Anthropic SDK compat) — configured but not load-tested
- strfry relay integration (separate service, not covered here)