Implements weeks 1-4 of the multi-provider architecture: Week 1 - Foundation: - Add domain models (AgentProvider, AgentRequest, AgentEvent, AgentResult) - Define CodeAgent port interface with Execute, Cancel, Capabilities - Create thread-safe provider registry with first-registered default Week 2 - Claude Code Adapter: - Extract kubectl exec logic into CodeAgent implementation - Parse stream-json output format (init, message, tool_use, result) - Support session continuation via --resume flag Week 3 - OpenCode Adapter: - HTTP/SSE client for opencode serve API - Session management (create, send message, abort) - Event streaming with documented buffer rationale Week 4 - Quality & Polish: - Fix race condition in OpenCode Cancel method - Add AgentRequest.Validate() with ErrPromptRequired, ErrInvalidTimeout - Document DefaultAvailabilityTimeout constants - Add HTTP error context for debugging Also includes: - Work queue system with PostgreSQL adapter - Credential store for infrastructure secrets - Project templates with Woodpecker CI integration - Comprehensive test coverage Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
343 lines
11 KiB
Markdown
343 lines
11 KiB
Markdown
# Landing Page Cookbook
|
|
|
|
> Deploy a static landing page through the threesix.ai infrastructure with agent-driven development.
|
|
|
|
## Overview
|
|
|
|
This cookbook creates and deploys a simple landing page using the full threesix.ai autonomous infrastructure:
|
|
|
|
```
|
|
rdev-api → Gitea repo → Claude agent → push → Woodpecker CI → K8s deployment
|
|
```
|
|
|
|
**Target:** `landing.threesix.ai` (with future DNS aliases for www/root)
|
|
**Stack:** Astro (static site generator)
|
|
**Status:** Coming Soon page
|
|
|
|
---
|
|
|
|
## Current Architecture Gap
|
|
|
|
**Two separate systems that need bridging:**
|
|
|
|
| System | Endpoint | What it manages |
|
|
|--------|----------|-----------------|
|
|
| Project Management | `POST /project` | Gitea repos, DNS records, K8s deployments |
|
|
| Claudebox Execution | `POST /projects/{id}/claude` | Code generation in existing claudebox pods |
|
|
|
|
**The problem:** Creating a project via `POST /project` creates a Gitea repo, but there's no claudebox to generate code for it. The claudebox system only knows about pre-existing pods (pantheon, aeries).
|
|
|
|
**The solution:** Use an existing claudebox as a "worker" to clone, build, and push to any project repo.
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
### Credentials Required
|
|
|
|
| Secret | Location | Purpose |
|
|
|--------|----------|---------|
|
|
| RDEV_ADMIN_KEY | `rdev-credentials` secret | rdev-api authentication |
|
|
| GITEA_TOKEN | `rdev-credentials` secret | Gitea API access |
|
|
| WOODPECKER_API_TOKEN | `.secrets` file | Woodpecker repo activation |
|
|
| CLOUDFLARE_API_TOKEN | `rdev-credentials` secret | DNS management |
|
|
|
|
### Infrastructure Required
|
|
|
|
- [x] rdev-api running with infrastructure handlers (v0.7.1+)
|
|
- [x] Gitea at https://git.threesix.ai
|
|
- [x] Woodpecker CI at https://ci.threesix.ai
|
|
- [x] Zot registry at zot.threesix.svc.cluster.local:5000
|
|
- [x] `projects` namespace in K8s with RBAC
|
|
- [x] Wildcard TLS cert for *.threesix.ai
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ Landing Page Flow │
|
|
│ │
|
|
│ 1. Create Project │
|
|
│ POST /project {"name": "www"} │
|
|
│ │ │
|
|
│ ├──▶ Creates Gitea repo: jordan/www │
|
|
│ └──▶ Creates DNS: www.threesix.ai → 208.122.204.172 │
|
|
│ │
|
|
│ 2. Activate Woodpecker │
|
|
│ POST /api/repos?forge_remote_id={id} │
|
|
│ │ │
|
|
│ └──▶ Creates webhook in Gitea │
|
|
│ │
|
|
│ 3. Generate Code (Claude Agent) │
|
|
│ claudebox or local Claude Code │
|
|
│ │ │
|
|
│ ├──▶ Creates Astro project │
|
|
│ ├──▶ Creates Dockerfile │
|
|
│ ├──▶ Creates .woodpecker.yml │
|
|
│ └──▶ Pushes to Gitea │
|
|
│ │
|
|
│ 4. CI/CD Pipeline (automatic) │
|
|
│ Woodpecker triggered by push │
|
|
│ │ │
|
|
│ ├──▶ Kaniko builds Docker image │
|
|
│ ├──▶ Pushes to Zot registry │
|
|
│ └──▶ Webhook triggers rdev-api deploy │
|
|
│ │
|
|
│ 5. Live at https://www.threesix.ai │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Step-by-Step Implementation
|
|
|
|
### Step 1: Create Project via rdev-api
|
|
|
|
```bash
|
|
RDEV_KEY="rdev_sk_prod_7f3a9c2e1d8b4a6f0e5c9d2b7a1f8e4c"
|
|
|
|
curl -X POST https://rdev.masq-ops.orchard9.ai/project \
|
|
-H "Authorization: Bearer $RDEV_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name": "landing", "description": "threesix.ai landing page"}'
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"name": "landing",
|
|
"domain": "landing.threesix.ai",
|
|
"git": {
|
|
"clone_ssh": "git@git.threesix.ai:jordan/landing.git",
|
|
"clone_http": "https://git.threesix.ai/jordan/landing.git"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Step 2: Activate Woodpecker CI
|
|
|
|
```bash
|
|
GITEA_TOKEN="5508ff241943e84aad0ced3559f5fbd311a2fb81"
|
|
WOODPECKER_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoidXNlciIsInVzZXItaWQiOiIxIn0.LcyVHcZ_gSvVH1w3y6TUCp_Jg9ubfsebOAVo-MtiNP8"
|
|
|
|
# Get Gitea repo ID
|
|
REPO_ID=$(curl -s https://git.threesix.ai/api/v1/repos/jordan/landing \
|
|
-H "Authorization: token $GITEA_TOKEN" | jq '.id')
|
|
|
|
# Activate in Woodpecker (creates webhook automatically)
|
|
curl -X POST "https://ci.threesix.ai/api/repos?forge_remote_id=$REPO_ID" \
|
|
-H "Authorization: Bearer $WOODPECKER_TOKEN"
|
|
```
|
|
|
|
### Step 3: Generate Code via Claudebox
|
|
|
|
Use the `pantheon` claudebox as a worker to generate code for the landing project:
|
|
|
|
```bash
|
|
RDEV_KEY="rdev_sk_prod_7f3a9c2e1d8b4a6f0e5c9d2b7a1f8e4c"
|
|
|
|
# Tell Claude to build the landing page in /tmp/landing
|
|
curl -X POST "https://rdev.masq-ops.orchard9.ai/projects/pantheon/claude" \
|
|
-H "Authorization: Bearer $RDEV_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"prompt": "Clone https://git.threesix.ai/jordan/landing.git to /tmp/landing, then create a simple Astro landing page with: Coming Soon message, threesix.ai branding (dark theme), responsive layout, Dockerfile (nginx), and .woodpecker.yml for CI/CD. Commit and push when done."
|
|
}'
|
|
```
|
|
|
|
**What happens:**
|
|
1. Claude receives the prompt in the claudebox
|
|
2. Claude clones the repo to `/tmp/landing`
|
|
3. Claude generates the Astro project files
|
|
4. Claude commits and pushes to Gitea
|
|
|
|
### Step 4: Monitor Build
|
|
|
|
Watch Woodpecker for the build:
|
|
- https://ci.threesix.ai/jordan/landing
|
|
|
|
Or via API:
|
|
```bash
|
|
curl -s "https://ci.threesix.ai/api/repos/jordan/landing/pipelines" \
|
|
-H "Authorization: Bearer $WOODPECKER_TOKEN" | jq '.[0] | {number, status, started}'
|
|
```
|
|
|
|
### Step 5: Verify Deployment
|
|
|
|
```bash
|
|
curl -s "https://rdev.masq-ops.orchard9.ai/project/landing" \
|
|
-H "Authorization: Bearer $RDEV_KEY" | jq '.data.deployment'
|
|
```
|
|
|
|
Site live at: https://landing.threesix.ai
|
|
|
|
### Step 6: Configure DNS Aliases (Optional)
|
|
|
|
Point `www.threesix.ai` and `threesix.ai` to the landing page:
|
|
|
|
```bash
|
|
CF_TOKEN="nGoDhG6Za66XsKMl6W7LNXuowc5EM00glHxkq1KK"
|
|
CF_ZONE="e0bc8d510f62807b360db0c5994964c5"
|
|
|
|
# Update root A record to point to k3s cluster
|
|
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE/dns_records/{record_id}" \
|
|
-H "Authorization: Bearer $CF_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"type": "A",
|
|
"name": "threesix.ai",
|
|
"content": "208.122.204.172",
|
|
"proxied": false
|
|
}'
|
|
```
|
|
|
|
---
|
|
|
|
## File Templates
|
|
|
|
### .woodpecker.yml
|
|
|
|
```yaml
|
|
steps:
|
|
build:
|
|
image: gcr.io/kaniko-project/executor:latest
|
|
settings:
|
|
registry: zot.threesix.svc.cluster.local:5000
|
|
tags:
|
|
- ${CI_COMMIT_SHA:0:8}
|
|
- latest
|
|
repo: zot.threesix.svc.cluster.local:5000/${CI_REPO_NAME}
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
insecure: true
|
|
when:
|
|
branch: main
|
|
|
|
notify:
|
|
image: alpine/curl:latest
|
|
commands:
|
|
- echo "Build complete, webhook will trigger deployment"
|
|
when:
|
|
branch: main
|
|
status: success
|
|
```
|
|
|
|
### Dockerfile (Astro + Nginx)
|
|
|
|
```dockerfile
|
|
# Build stage
|
|
FROM node:20-alpine AS builder
|
|
WORKDIR /app
|
|
COPY package*.json ./
|
|
RUN npm ci
|
|
COPY . .
|
|
RUN npm run build
|
|
|
|
# Production stage
|
|
FROM nginx:alpine
|
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
EXPOSE 80
|
|
CMD ["nginx", "-g", "daemon off;"]
|
|
```
|
|
|
|
### nginx.conf
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name _;
|
|
root /usr/share/nginx/html;
|
|
index index.html;
|
|
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
|
|
# Cache static assets
|
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Current Gaps & Future Automation
|
|
|
|
### What's Manual Today
|
|
|
|
| Step | Status | Automation Path |
|
|
|------|--------|-----------------|
|
|
| Create project | ✅ API | Already automated |
|
|
| Activate Woodpecker | 🔧 API call needed | Add to rdev-api |
|
|
| Generate code | ❌ Manual Claude | Claudebox integration |
|
|
| Push to Gitea | ❌ Manual git | Claudebox with SSH key |
|
|
| Deploy | ✅ Webhook | Already automated |
|
|
|
|
### To Fully Automate (Future Work)
|
|
|
|
1. **Add Woodpecker activation to rdev-api**
|
|
- Store WOODPECKER_API_TOKEN in secrets
|
|
- Call Woodpecker API after creating Gitea repo
|
|
- Create webhook automatically
|
|
|
|
2. **Claudebox code generation**
|
|
- Spawn claudebox with project context
|
|
- Claudebox has Gitea SSH key
|
|
- Claude Code generates code based on prompt
|
|
- Auto-push to Gitea
|
|
|
|
3. **Single API call**
|
|
```
|
|
POST /project/create-and-build
|
|
{
|
|
"name": "www",
|
|
"prompt": "Create an Astro landing page with coming soon message",
|
|
"stack": "astro"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Verification
|
|
|
|
After deployment, verify:
|
|
|
|
```bash
|
|
# Check DNS
|
|
dig www.threesix.ai
|
|
|
|
# Check site
|
|
curl -I https://www.threesix.ai
|
|
|
|
# Check deployment status
|
|
curl https://rdev.masq-ops.orchard9.ai/project/www \
|
|
-H "Authorization: Bearer $RDEV_KEY"
|
|
```
|
|
|
|
---
|
|
|
|
## Rollback
|
|
|
|
To remove the landing page:
|
|
|
|
```bash
|
|
# Delete via rdev-api (removes Gitea repo, DNS, K8s deployment)
|
|
curl -X DELETE https://rdev.masq-ops.orchard9.ai/project/www \
|
|
-H "Authorization: Bearer $RDEV_KEY"
|
|
```
|
|
|
|
---
|
|
|
|
## Related
|
|
|
|
- [THREESIX_INFRASTRUCTURE.md](/Users/jordanwashburn/Workspace/orchard9/rdev/docs/plans/THREESIX_INFRASTRUCTURE.md) - Infrastructure plan
|
|
- [woodpecker-pipeline-template.yaml](../deployments/k8s/base/threesix/woodpecker-pipeline-template.yaml) - CI template
|