feat: production-ready deployment with multi-language UI
- Add multi-language support (NL, AR, EN) with RTL
- Improve health checks (SSL-tolerant, multi-endpoint)
- Add DELETE /api/stack/:name for cleanup
- Add persistent storage (portal-ai-workspace-{name})
- Improve rollback (delete domain, app, project)
- Increase SSE timeout to 255s
- Add deployment strategy documentation
This commit is contained in:
235
docs/AGENTS.md
235
docs/AGENTS.md
@@ -1,94 +1,170 @@
|
||||
# AI Agent Instructions - AI Stack Deployer
|
||||
# AI Stack Deployer - Agent Instructions
|
||||
|
||||
**Project-specific guidelines for AI coding agents**
|
||||
**MANDATORY: Read before implementing**
|
||||
|
||||
---
|
||||
|
||||
## Project Context
|
||||
## PROJECT IDENTITY
|
||||
|
||||
This is a self-service portal for deploying OpenCode AI stacks. Users enter their name and get a fully deployed AI assistant at `{name}.ai.flexinit.nl`.
|
||||
Self-service portal: Users enter name → Get deployed AI assistant at `{name}.ai.flexinit.nl`
|
||||
|
||||
**Key Technologies:**
|
||||
- Bun + Hono (backend)
|
||||
- Vanilla HTML/CSS/JS (frontend)
|
||||
- Docker + Dokploy (deployment)
|
||||
- Hetzner DNS API + Traefik (networking)
|
||||
**Stack:**
|
||||
- Backend: Bun + Hono
|
||||
- Frontend: Vanilla HTML/CSS/JS (NO frameworks)
|
||||
- Deploy: Docker + Dokploy
|
||||
- Network: Hetzner DNS + Traefik
|
||||
|
||||
---
|
||||
|
||||
## Critical Information
|
||||
## MANDATORY: DEPLOYMENT WORKFLOW
|
||||
|
||||
### API Endpoints
|
||||
**User submits name → Deploy AI stack (5 steps):**
|
||||
|
||||
1. **Validate** - Check name format, reserved names, availability
|
||||
2. **DNS** - Create `{name}.ai.flexinit.nl` → 144.76.116.169 (or rely on wildcard)
|
||||
3. **Dokploy Project** - Create project via API
|
||||
4. **Dokploy App** - Create application, add domain, deploy
|
||||
5. **Verify** - Wait for SSL, test OpenCode + ttyd endpoints
|
||||
|
||||
**CRITICAL:** Each step MUST succeed before proceeding. No parallel execution.
|
||||
|
||||
---
|
||||
|
||||
## MANDATORY: SECRETS (Get FIRST)
|
||||
|
||||
#### Hetzner Cloud API (DNS)
|
||||
```bash
|
||||
# Base URL
|
||||
https://api.hetzner.cloud/v1
|
||||
|
||||
# IMPORTANT: Use /zones/{zone_id}/rrsets NOT /dns/zones
|
||||
# The old dns.hetzner.com API is DEPRECATED
|
||||
|
||||
# List records (RRSets)
|
||||
GET /zones/343733/rrsets
|
||||
Authorization: Bearer {HETZNER_API_TOKEN}
|
||||
|
||||
# Create DNS record (individual A record for user)
|
||||
# NOTE: Wildcard *.ai.flexinit.nl already exists pointing to Traefik
|
||||
# For per-user records (optional, wildcard handles it):
|
||||
POST /zones/343733/rrsets
|
||||
Authorization: Bearer {HETZNER_API_TOKEN}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "{name}.ai",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"records": [
|
||||
{
|
||||
"value": "144.76.116.169",
|
||||
"comment": "AI Stack for {name}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Zone ID for flexinit.nl: 343733
|
||||
# Traefik IP: 144.76.116.169
|
||||
# Wildcard *.ai.flexinit.nl -> 144.76.116.169 (already configured)
|
||||
DOKPLOY_TOKEN=$(bws-wrapper get 6b3618fc-ba02-49bc-bdc8-b3c9004087bc)
|
||||
HETZNER_TOKEN=$(bws-wrapper get <HETZNER_BWS_ID>) # Ask user or search BWS
|
||||
```
|
||||
|
||||
#### Dokploy API
|
||||
---
|
||||
|
||||
## MANDATORY: API COMMANDS (COPY EXACTLY)
|
||||
|
||||
### Hetzner DNS API (MODIFY ONLY: name, comment)
|
||||
|
||||
**CRITICAL:** Use `api.hetzner.cloud/v1`, NOT `dns.hetzner.com` (deprecated)
|
||||
|
||||
```bash
|
||||
# Base URL
|
||||
http://10.100.0.20:3000/api
|
||||
# List existing DNS records
|
||||
curl -s "https://api.hetzner.cloud/v1/zones/343733/rrsets" \
|
||||
-H "Authorization: Bearer $HETZNER_TOKEN"
|
||||
|
||||
# All requests need:
|
||||
Authorization: Bearer {DOKPLOY_API_TOKEN}
|
||||
Content-Type: application/json
|
||||
|
||||
# Key endpoints:
|
||||
POST /project.create # Create project
|
||||
POST /application.create # Create application
|
||||
POST /domain.create # Add domain to application
|
||||
POST /application.deploy # Trigger deployment
|
||||
GET /application.one # Get application status
|
||||
# Create A record (OPTIONAL - wildcard *.ai.flexinit.nl already exists)
|
||||
curl -X POST "https://api.hetzner.cloud/v1/zones/343733/rrsets" \
|
||||
-H "Authorization: Bearer $HETZNER_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "{name}.ai",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"records": [{"value": "144.76.116.169", "comment": "AI Stack for {name}"}]
|
||||
}'
|
||||
```
|
||||
|
||||
### BWS Secrets
|
||||
**Constants:**
|
||||
- Zone ID: `343733` (flexinit.nl)
|
||||
- Traefik IP: `144.76.116.169`
|
||||
- Wildcard: `*.ai.flexinit.nl` → 144.76.116.169 (pre-configured)
|
||||
|
||||
| Purpose | BWS ID |
|
||||
|---------|--------|
|
||||
| Dokploy Token | `6b3618fc-ba02-49bc-bdc8-b3c9004087bc` |
|
||||
| Hetzner Token | Search BWS or ask user |
|
||||
### Dokploy API (MODIFY ONLY: projectId, applicationId, name, domain)
|
||||
|
||||
### Infrastructure IPs
|
||||
**Base:** `http://10.100.0.20:3000/api`
|
||||
|
||||
- **Traefik**: 144.76.116.169 (public, SSL termination)
|
||||
- **Dokploy**: 10.100.0.20:3000 (internal)
|
||||
- **DNS Zone**: flexinit.nl, ID 343733
|
||||
```bash
|
||||
# Create project
|
||||
curl -X POST "http://10.100.0.20:3000/api/project.create" \
|
||||
-H "Authorization: Bearer $DOKPLOY_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "{username}-ai-stack"}'
|
||||
|
||||
# Create application
|
||||
curl -X POST "http://10.100.0.20:3000/api/application.create" \
|
||||
-H "Authorization: Bearer $DOKPLOY_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"projectId": "...", "name": "{username}-opencode"}'
|
||||
|
||||
# Add domain
|
||||
curl -X POST "http://10.100.0.20:3000/api/domain.create" \
|
||||
-H "Authorization: Bearer $DOKPLOY_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"applicationId": "...", "host": "{name}.ai.flexinit.nl"}'
|
||||
|
||||
# Deploy application
|
||||
curl -X POST "http://10.100.0.20:3000/api/application.deploy" \
|
||||
-H "Authorization: Bearer $DOKPLOY_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"applicationId": "..."}'
|
||||
|
||||
# Check status
|
||||
curl -s "http://10.100.0.20:3000/api/application.one?applicationId=..." \
|
||||
-H "Authorization: Bearer $DOKPLOY_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Guidelines
|
||||
## CRITICAL: MANDATORY RULES
|
||||
|
||||
**NEVER:**
|
||||
- Use frontend frameworks (React, Vue, Svelte, etc.)
|
||||
- Use old Hetzner API (`dns.hetzner.com`)
|
||||
- Create Dokploy domain BEFORE application exists
|
||||
- Skip error handling on ANY API call
|
||||
- Store secrets in code
|
||||
- Proceed to next step if current step fails
|
||||
|
||||
**ALWAYS:**
|
||||
- Validate name (alphanumeric, 3-20 chars, not reserved)
|
||||
- Get tokens from BWS first
|
||||
- Wait for SSL certificate (30-60 seconds)
|
||||
- Verify each deployment step
|
||||
- Handle errors gracefully with user feedback
|
||||
- Mobile-responsive design
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL: GOTCHAS
|
||||
|
||||
1. **Hetzner API** - Use `api.hetzner.cloud`, NOT `dns.hetzner.com` (deprecated)
|
||||
2. **Dokploy domain** - MUST create domain AFTER application exists (or 404)
|
||||
3. **SSL delay** - Let's Encrypt cert takes 30-60 seconds to provision
|
||||
4. **ttyd WebSocket** - Requires Traefik WebSocket support configured
|
||||
5. **Container startup** - OpenCode server takes ~10 seconds to be ready
|
||||
6. **Reserved names** - Block: admin, api, www, root, test, staging, prod, etc.
|
||||
|
||||
---
|
||||
|
||||
## MANDATORY: VALIDATION
|
||||
|
||||
**Before deployment, check:**
|
||||
```javascript
|
||||
// Name validation regex
|
||||
const isValid = /^[a-z0-9]{3,20}$/.test(name);
|
||||
|
||||
// Reserved names
|
||||
const reserved = ['admin', 'api', 'www', 'root', 'test', 'staging', 'prod'];
|
||||
const isReserved = reserved.includes(name);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MANDATORY: VERIFICATION
|
||||
|
||||
**After deployment, verify:**
|
||||
```bash
|
||||
# 1. SSL certificate provisioned
|
||||
curl -I "https://{name}.ai.flexinit.nl" # Should return 200
|
||||
|
||||
# 2. OpenCode server responding
|
||||
curl "https://{name}.ai.flexinit.nl" # Should show OpenCode UI
|
||||
|
||||
# 3. ttyd terminal accessible
|
||||
curl -I "https://{name}.ai.flexinit.nl:7681" # Should return 200
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Patterns
|
||||
|
||||
### Backend (Bun + Hono)
|
||||
|
||||
@@ -197,9 +273,9 @@ CMD ["sh", "-c", "opencode serve --host 0.0.0.0 --port 8080 & ttyd -W -p 7681 op
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
## MANDATORY: TESTING CHECKLIST
|
||||
|
||||
Before considering implementation complete:
|
||||
**Before marking complete, ALL must pass:**
|
||||
|
||||
- [ ] Name validation works (alphanumeric, 3-20 chars)
|
||||
- [ ] Reserved names blocked (admin, api, www, root, etc.)
|
||||
@@ -212,17 +288,7 @@ Before considering implementation complete:
|
||||
- [ ] Mobile responsive
|
||||
- [ ] Loading states smooth (no flicker)
|
||||
|
||||
---
|
||||
|
||||
## Common Gotchas
|
||||
|
||||
1. **Hetzner API** - Use `api.hetzner.cloud`, NOT `dns.hetzner.com` (deprecated)
|
||||
2. **Dokploy domain** - Must create domain AFTER application exists
|
||||
3. **SSL delay** - Let's Encrypt cert may take 30-60 seconds
|
||||
4. **ttyd WebSocket** - Needs proper Traefik WebSocket support
|
||||
5. **Container startup** - OpenCode server takes ~10 seconds to be ready
|
||||
|
||||
---
|
||||
|
||||
## File Reading Order
|
||||
|
||||
@@ -233,17 +299,6 @@ When starting implementation, read in this order:
|
||||
|
||||
---
|
||||
|
||||
## Do NOT
|
||||
|
||||
- Do NOT use any frontend framework (React, Vue, etc.)
|
||||
- Do NOT add unnecessary dependencies
|
||||
- Do NOT store secrets in code
|
||||
- Do NOT skip error handling
|
||||
- Do NOT make the UI overly complex
|
||||
- Do NOT forget mobile responsiveness
|
||||
|
||||
---
|
||||
|
||||
## Reference Projects
|
||||
|
||||
- `~/locale-projects/oh-my-opencode-free` - The stack being deployed
|
||||
|
||||
Reference in New Issue
Block a user