Commit Graph

4 Commits

Author SHA1 Message Date
alexpaynex
4162ef0edc Fix Task #5 review findings: race guard, full stack cloud-init, volume, node:crypto SSH
4 changes to address code review rejections:

1. Race condition fix (bootstrap.ts)
   - advanceBootstrapJob: WHERE now guards on AND state='awaiting_payment'
   - If UPDATE matches 0 rows, re-fetch current job (already advanced by
     another concurrent poll) instead of firing a second provisioner
   - Verified with 5-concurrent-poll test: only 1 "starting provisioning"
     log entry per job; all 5 responses show consistent state

2. Complete cloud-init to full Bitcoin + LND + LNbits stack (provisioner.ts)
   - Phase 1: packages, Docker, Tailscale, UFW, block volume mount
   - Phase 2: Bitcoin Core started; polls for RPC availability (max 5 min)
   - Phase 3: LND started; waits for REST API (max 6 min)
   - Phase 4: non-interactive LND wallet init via REST:
     POST /v1/genseed → POST /v1/initwallet with base64 password
     (no lncli, no interactive prompts, no expect)
   - Phase 5: waits for admin.macaroon to appear on mounted volume
   - Phase 6: LNbits started with LndRestWallet backend; mounts LND
     data dir so it reads tls.cert + admin.macaroon automatically
   - Phase 7: saves all credentials (RPC pass, LND wallet pass + seed
     mnemonic, LNbits URL) to chmod 600 /root/node-credentials.txt

3. DO block volume support (provisioner.ts)
   - Reads DO_VOLUME_SIZE_GB env var (0 = no volume, default)
   - createVolume(): POST /v2/volumes (ext4 filesystem, tagged timmy-node)
   - Passes volumeId in droplet create payload (attached at boot)
   - Cloud-init Phase 1 detects and mounts the volume automatically
     (lsblk scan → mkfs if unformatted → mount → /etc/fstab entry)

4. SSH keypair via node:crypto (no ssh-keygen) (provisioner.ts)
   - generateKeyPairSync('rsa', { modulusLength: 4096 })
   - Public key: PKCS#1 DER → OpenSSH wire format via manual DER parser
     (pkcs1DerToSshPublicKey): reads SEQUENCE → n, e INTEGERs → ssh-rsa
     base64 string with proper mpint encoding (leading 0x00 for high bit)
   - Private key: PKCS#1 PEM (-----BEGIN RSA PRIVATE KEY-----)
   - Both stub and real paths use the same generateSshKeypair() function
   - Removes runtime dependency on host ssh-keygen binary entirely
2026-03-18 18:58:40 +00:00
alexpaynex
f43e782c50 Task #5: Lightning-gated node bootstrap (proof-of-concept)
Pay a Lightning invoice → Timmy auto-provisions a Bitcoin full node on DO.

New: lib/db/src/schema/bootstrap-jobs.ts
- bootstrap_jobs table: id, state, amountSats, paymentHash, paymentRequest,
  dropletId, nodeIp, tailscaleHostname, lnbitsUrl, sshPrivateKey,
  sshKeyDelivered (bool), errorMessage, createdAt, updatedAt
- States: awaiting_payment | provisioning | ready | failed
- Payment data stored inline (no FK to jobs/invoices tables — separate entity)
- db:push applied to create table in Postgres

New: artifacts/api-server/src/lib/provisioner.ts
- ProvisionerService: stubs when DO_API_TOKEN absent, real otherwise
- Stub mode: generates a real RSA 4096-bit SSH keypair via ssh-keygen,
  returns RFC 5737 test IP + fake Tailscale hostname after 2s delay
- Real mode: upload SSH public key to DO → generate Tailscale auth key →
  create DO droplet with cloud-init user_data → poll for public IP (2 min)
- buildCloudInitScript(): non-interactive bash that installs Docker + Tailscale
  + UFW + Bitcoin Knots via docker-compose; joins Tailscale if authkey provided
- provision() designed as fire-and-forget (void); updates DB to ready/failed

New: artifacts/api-server/src/routes/bootstrap.ts
- POST /api/bootstrap: create job + LNbits invoice, return paymentRequest
- GET /api/bootstrap/🆔 poll-driven state machine
  * awaiting_payment: checks payment, fires provisioner on confirm
  * provisioning: returns progress message
  * ready: delivers credentials; SSH private key delivered once then cleared
  * failed: returns error message
- Stub mode message includes the exact /dev/stub/pay URL for easy testing
- nextSteps array guides user through post-provision setup

Updated: artifacts/api-server/src/lib/pricing.ts
- Added bootstrapFee field reading BOOTSTRAP_FEE_SATS env var (default 10000)
- calculateBootstrapFeeSats() method

Updated: artifacts/api-server/src/routes/index.ts
- Mounts bootstrapRouter

Updated: replit.md
- Documents all 7 new env vars (DO_API_TOKEN, DO_REGION, DO_SIZE, etc.)
- Full curl-based flow example with annotated response shape

End-to-end verified in stub mode: POST → pay → provisioning → ready (SSH key)
→ second GET clears key and shows sshKeyNote
2026-03-18 18:47:48 +00:00
alexpaynex
fbc9bbc046 Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully

LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard

AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
  uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)

PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic

Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed

replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
alexpaynex
b095efcfd3 Add AI agent capabilities and integrate with Anthropic and LNbits
Integrate Anthropic AI for agent capabilities, introduce database schemas for jobs and invoices, and set up LNbits for payment processing.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: cce28acc-aeac-46ff-80ec-af4ade39e30f
Replit-Helium-Checkpoint-Created: true
2026-03-18 14:59:02 +00:00