fix(ci): trigger workflow on main branch to enable :latest tag
Changes:
- Create Gitea workflow for ai-stack-deployer
- Trigger on main branch (default branch)
- Use oussamadouhou + REGISTRY_TOKEN for authentication
- Build from ./Dockerfile
This enables :latest tag creation via {{is_default_branch}}.
Tags created:
- git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:latest
- git.app.flexinit.nl/oussamadouhou/ai-stack-deployer:<sha>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
250
docs/AGENTS.md
Normal file
250
docs/AGENTS.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# AI Agent Instructions - AI Stack Deployer
|
||||
|
||||
**Project-specific guidelines for AI coding agents**
|
||||
|
||||
---
|
||||
|
||||
## Project Context
|
||||
|
||||
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`.
|
||||
|
||||
**Key Technologies:**
|
||||
- Bun + Hono (backend)
|
||||
- Vanilla HTML/CSS/JS (frontend)
|
||||
- Docker + Dokploy (deployment)
|
||||
- Hetzner DNS API + Traefik (networking)
|
||||
|
||||
---
|
||||
|
||||
## Critical Information
|
||||
|
||||
### API Endpoints
|
||||
|
||||
#### 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 API
|
||||
```bash
|
||||
# Base URL
|
||||
http://10.100.0.20:3000/api
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
### BWS Secrets
|
||||
|
||||
| Purpose | BWS ID |
|
||||
|---------|--------|
|
||||
| Dokploy Token | `6b3618fc-ba02-49bc-bdc8-b3c9004087bc` |
|
||||
| Hetzner Token | Search BWS or ask user |
|
||||
|
||||
### Infrastructure IPs
|
||||
|
||||
- **Traefik**: 144.76.116.169 (public, SSL termination)
|
||||
- **Dokploy**: 10.100.0.20:3000 (internal)
|
||||
- **DNS Zone**: flexinit.nl, ID 343733
|
||||
|
||||
---
|
||||
|
||||
## Implementation Guidelines
|
||||
|
||||
### Backend (Bun + Hono)
|
||||
|
||||
```typescript
|
||||
// Use Hono for routing
|
||||
import { Hono } from 'hono';
|
||||
import { cors } from 'hono/cors';
|
||||
import { serveStatic } from 'hono/bun';
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
// Serve frontend
|
||||
app.use('/*', serveStatic({ root: './src/frontend' }));
|
||||
|
||||
// API routes
|
||||
app.post('/api/deploy', deployHandler);
|
||||
app.get('/api/status/:id', statusHandler);
|
||||
app.get('/api/check/:name', checkHandler);
|
||||
```
|
||||
|
||||
### SSE Implementation
|
||||
|
||||
```typescript
|
||||
// Server-Sent Events for progress updates
|
||||
app.get('/api/status/:id', (c) => {
|
||||
return streamSSE(c, async (stream) => {
|
||||
// Send progress updates
|
||||
await stream.writeSSE({
|
||||
event: 'progress',
|
||||
data: JSON.stringify({ step: 'dns', status: 'completed' })
|
||||
});
|
||||
|
||||
// ... more updates
|
||||
|
||||
await stream.writeSSE({
|
||||
event: 'complete',
|
||||
data: JSON.stringify({ url: 'https://...' })
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Frontend (Vanilla JS State Machine)
|
||||
|
||||
```javascript
|
||||
// State: 'idle' | 'deploying' | 'success' | 'error'
|
||||
let state = 'idle';
|
||||
|
||||
function setState(newState, data = {}) {
|
||||
state = newState;
|
||||
render(state, data);
|
||||
}
|
||||
|
||||
// SSE connection
|
||||
const eventSource = new EventSource(`/api/status/${deploymentId}`);
|
||||
eventSource.addEventListener('progress', (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
updateProgress(data);
|
||||
});
|
||||
eventSource.addEventListener('complete', (e) => {
|
||||
setState('success', JSON.parse(e.data));
|
||||
});
|
||||
```
|
||||
|
||||
### Docker Stack Template
|
||||
|
||||
The user stack needs:
|
||||
1. OpenCode server (port 8080)
|
||||
2. ttyd web terminal (port 7681)
|
||||
|
||||
```dockerfile
|
||||
FROM git.app.flexinit.nl/oussamadouhou/oh-my-opencode-free:latest
|
||||
|
||||
# Install ttyd
|
||||
RUN apt-get update && apt-get install -y ttyd
|
||||
|
||||
# Expose ports
|
||||
EXPOSE 8080 7681
|
||||
|
||||
# Start both services
|
||||
CMD ["sh", "-c", "opencode serve --host 0.0.0.0 --port 8080 & ttyd -W -p 7681 opencode attach http://localhost:8080"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Code Style
|
||||
|
||||
### TypeScript
|
||||
- Use strict mode
|
||||
- Prefer `const` over `let`
|
||||
- Use async/await over callbacks
|
||||
- Handle all errors explicitly
|
||||
- Type all function parameters and returns
|
||||
|
||||
### CSS
|
||||
- Use CSS variables for theming
|
||||
- Mobile-first responsive design
|
||||
- BEM-like naming: `.component__element--modifier`
|
||||
- Dark theme as default
|
||||
|
||||
### JavaScript (Frontend)
|
||||
- No frameworks, vanilla only
|
||||
- Module pattern for organization
|
||||
- Event delegation where possible
|
||||
- Graceful degradation
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
Before considering implementation complete:
|
||||
|
||||
- [ ] Name validation works (alphanumeric, 3-20 chars)
|
||||
- [ ] Reserved names blocked (admin, api, www, root, etc.)
|
||||
- [ ] DNS record created successfully
|
||||
- [ ] Dokploy project created
|
||||
- [ ] Application deployed and healthy
|
||||
- [ ] SSL certificate provisioned
|
||||
- [ ] ttyd accessible in browser
|
||||
- [ ] Error states handled gracefully
|
||||
- [ ] 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
|
||||
|
||||
When starting implementation, read in this order:
|
||||
1. `README.md` - Full project specification
|
||||
2. `AGENTS.md` - This file
|
||||
3. Check existing oh-my-opencode-free for reference patterns
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
- `~/projecten/infrastructure` - Infrastructure patterns and docs
|
||||
Reference in New Issue
Block a user