feat(opencode): añadir stack del agente de código OpenCode
OpenCode como agente de desarrollo con acceso al filesystem del host, expuesto vía Traefik con autenticación forward auth de Authentik. Incluye configuración de systemd para modo web y ejemplos de .env. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
TZ=Europe/Madrid
|
||||
|
||||
OPENCODE_DOMAIN=ocode.sherlockhomeless.net
|
||||
OPENCODE_HOME_PATH=/opt/opencode/home
|
||||
OPENCODE_CONFIG_PATH=/opt/opencode/config
|
||||
OPENCODE_WORKSPACE_PATH=/opt/opencode/workspace
|
||||
|
||||
OPENCODE_SERVER_USERNAME=colleague
|
||||
OPENCODE_SERVER_PASSWORD=change-me-very-long
|
||||
OPENCODE_DISABLE_AUTOUPDATE=true
|
||||
DEEPSEEK_API_KEY=sk-change-me
|
||||
|
||||
TRAEFIK_DOCKER_NETWORK=proxy
|
||||
TRAEFIK_ENTRYPOINT_SECURE=websecure
|
||||
TRAEFIK_CERTRESOLVER=letsencrypt
|
||||
TRAEFIK_AUTH_CHAIN=ths-authentik@docker,opencode-inject-auth@file
|
||||
@@ -0,0 +1,11 @@
|
||||
FROM node:22-bookworm-slim
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends bash git openssh-client ca-certificates \
|
||||
&& npm install -g opencode-ai \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
USER 1000:1000
|
||||
|
||||
ENTRYPOINT ["opencode"]
|
||||
@@ -0,0 +1,107 @@
|
||||
# OpenCode Web UI
|
||||
|
||||
Este stack ejecuta OpenCode en un contenedor con aislamiento fuerte para exponerlo solo por HTTPS detras de Traefik + Authentik.
|
||||
|
||||
## Objetivo
|
||||
|
||||
- Dominio sandboxed: `https://ocode.sherlockhomeless.net`
|
||||
- Dominio clasico host/systemd: `https://opencode.sherlockhomeless.net`
|
||||
- Acceso: solo usuarios autorizados en Authentik
|
||||
- Aislamiento: sin privilegios, filesystem raiz de solo lectura y workspace dedicado
|
||||
|
||||
## Archivos
|
||||
|
||||
- Compose: `opencode/docker-compose.yml`
|
||||
- Variables: `opencode/.env` (copiar desde `opencode/.env.example`)
|
||||
- Middleware Traefik (inyecta `Authorization`): `/opt/traefik/dynamic/opencode-auth.yml`
|
||||
- Config de legado host/systemd: `opencode/systemd/` y `opencode/traefik/opencode.yml`
|
||||
|
||||
## Despliegue en contenedor
|
||||
|
||||
```bash
|
||||
cp opencode/.env.example opencode/.env
|
||||
mkdir -p /opt/opencode/home /opt/opencode/config /opt/opencode/workspace
|
||||
docker compose --env-file opencode/.env -f opencode/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
Para SSO directo (sin segundo login de OpenCode), crea tambien este middleware dinamico en Traefik:
|
||||
|
||||
```yaml
|
||||
http:
|
||||
middlewares:
|
||||
opencode-inject-auth:
|
||||
headers:
|
||||
customRequestHeaders:
|
||||
Authorization: "Basic <base64(usuario:password-opencode)>"
|
||||
```
|
||||
|
||||
El router sandboxed usa la cadena `ths-authentik@docker,opencode-inject-auth@file`, asi Authentik valida al usuario y Traefik pasa despues la cabecera Basic al backend.
|
||||
|
||||
## Endurecimiento aplicado
|
||||
|
||||
- `user: 1000:1000`
|
||||
- `cap_drop: [ALL]`
|
||||
- `security_opt: [no-new-privileges:true]`
|
||||
- `read_only: true`
|
||||
- `/tmp` en `tmpfs` con `exec`, necesario para que Bun/OpenCode cargue librerias temporales
|
||||
- Home persistente aislada en `/opt/opencode/home`
|
||||
- Workspace aislado en `/opt/opencode/workspace`, montado dentro de la home como `/home/opencode/workspace`
|
||||
- Sin `docker.sock` ni montajes del host fuera de los paths definidos
|
||||
|
||||
## Authentik (acceso solo a esta app)
|
||||
|
||||
En Authentik, cada dominio necesita su propio `Proxy Provider` publicado en el `authentik Embedded Outpost`:
|
||||
|
||||
1. Provider/app para `ocode.sherlockhomeless.net` (sandboxed).
|
||||
2. Provider/app independiente para `opencode.sherlockhomeless.net` (clasico).
|
||||
3. Ambos providers deben estar anadidos al `authentik Embedded Outpost`.
|
||||
4. Para aislar el acceso sandboxed, crea un grupo exclusivo, por ejemplo `opencode-ocode-users`.
|
||||
5. Crea una `Policy Binding` para la app sandboxed que permita solo ese grupo.
|
||||
6. Crea el usuario del colega y anadelo solo a `opencode-ocode-users`.
|
||||
7. No lo anadas a grupos con acceso a otras aplicaciones.
|
||||
|
||||
Si el clasico devuelve una pagina `Not Found` de Authentik, normalmente significa que el provider de `opencode.sherlockhomeless.net` no esta asociado al outpost o fue sustituido por el provider de `ocode.sherlockhomeless.net`.
|
||||
|
||||
## Arquitectura de modelos LLM
|
||||
|
||||
Hay dos instancias de OpenCode con modelos distintos:
|
||||
|
||||
| Instancia | Tipo | Modelos |
|
||||
|-------------------------------|-------------- |----------------------------|
|
||||
| `opencode.sherlockhomeless.net` | Host (systemd) | DeepSeek + **Gemma 4** (Ollama) |
|
||||
| `ocode.sherlockhomeless.net` | Contenedor | Solo DeepSeek (colega) |
|
||||
|
||||
El contenedor sandboxed (`ocode`) **no** usa Ollama ni Gemma 4. Sus modelos ya estan configurados via `DEEPSEEK_API_KEY` en su `.env`.
|
||||
|
||||
### Modelos Gemma 4 via Ollama (para el host OpenCode)
|
||||
|
||||
Ollama corre en contenedor expuesto en `127.0.0.1:11434` (solo localhost).
|
||||
|
||||
```bash
|
||||
cp ollama/.env.example ollama/.env
|
||||
mkdir -p /opt/ollama/data
|
||||
docker compose --env-file ollama/.env -f ollama/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
Pull de modelos:
|
||||
|
||||
```bash
|
||||
docker exec -it ollama ollama pull gemma4:e2b # modelo principal
|
||||
docker exec -it ollama ollama pull gemma4:e4b # opcional si e2b no basta
|
||||
```
|
||||
|
||||
Activar provider en el OpenCode del host:
|
||||
|
||||
```bash
|
||||
sudo install -m 0644 opencode/opencode.json.example /home/felidae/.config/opencode/opencode.json
|
||||
sudo systemctl restart opencode-web
|
||||
```
|
||||
|
||||
Modelos recomendados para este VPS (8 vCPU, 22 GiB RAM, sin GPU):
|
||||
|
||||
| Modelo | RAM aprox | Uso |
|
||||
|------------- |---------- |--------------------- |
|
||||
| `gemma4:e2b` | ~3-4 GiB | Modelo principal |
|
||||
| `gemma4:e4b` | ~5-6 GiB | Solo si e2b no basta |
|
||||
| `gemma4:26b` | ~16 GiB | No recomendable |
|
||||
| `gemma4:31b` | ~20 GiB | No recomendable |
|
||||
@@ -0,0 +1,43 @@
|
||||
services:
|
||||
opencode:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: local/opencode-sandbox:latest
|
||||
container_name: opencode
|
||||
restart: unless-stopped
|
||||
stop_grace_period: 30s
|
||||
user: "1000:1000"
|
||||
command: ["web", "--hostname", "0.0.0.0", "--port", "4096"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
HOME: /home/opencode
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /.local:size=128m,mode=1777
|
||||
- /tmp:size=256m,mode=1777,exec
|
||||
volumes:
|
||||
- ${OPENCODE_HOME_PATH}:/home/opencode:Z
|
||||
- ${OPENCODE_CONFIG_PATH}:/home/opencode/.config/opencode:Z
|
||||
- ${OPENCODE_WORKSPACE_PATH}:/home/opencode/workspace:Z
|
||||
working_dir: /home/opencode/workspace
|
||||
networks:
|
||||
- proxy
|
||||
labels:
|
||||
traefik.enable: "true"
|
||||
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
|
||||
traefik.http.routers.ocode.rule: "Host(`${OPENCODE_DOMAIN}`)"
|
||||
traefik.http.routers.ocode.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
||||
traefik.http.routers.ocode.tls: "true"
|
||||
traefik.http.routers.ocode.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
|
||||
traefik.http.routers.ocode.middlewares: "crowdsec-bouncer@file,${TRAEFIK_AUTH_CHAIN}"
|
||||
traefik.http.services.ocode.loadbalancer.server.port: "4096"
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
@@ -0,0 +1,4 @@
|
||||
OPENCODE_SERVER_USERNAME=opencode
|
||||
OPENCODE_SERVER_PASSWORD=change-me
|
||||
OPENCODE_DISABLE_AUTOUPDATE=true
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"ollama-vps": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "Ollama VPS",
|
||||
"options": {
|
||||
"baseURL": "http://localhost:11434/v1",
|
||||
"apiKey": "ollama"
|
||||
},
|
||||
"models": {
|
||||
"gemma4:e2b": {
|
||||
"name": "Gemma 4 E2B VPS",
|
||||
"limit": {
|
||||
"context": 16384,
|
||||
"output": 2048
|
||||
}
|
||||
},
|
||||
"gemma4:e4b": {
|
||||
"name": "Gemma 4 E4B VPS",
|
||||
"limit": {
|
||||
"context": 16384,
|
||||
"output": 2048
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
[Unit]
|
||||
Description=OpenCode Web UI
|
||||
Documentation=https://opencode.ai/docs/web/
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=felidae
|
||||
Group=felidae
|
||||
WorkingDirectory=/home/eduardo/Portainer_repo
|
||||
EnvironmentFile=/etc/opencode-web.env
|
||||
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
Environment=HOME=/home/felidae
|
||||
ExecStart=/usr/local/bin/opencode-host web --hostname 10.0.0.1 --port 4096 --print-logs
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
# OpenCode necesita operar sobre repos y herramientas del host; se mantiene sin
|
||||
# sandboxing restrictivo de systemd a proposito.
|
||||
NoNewPrivileges=false
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,6 @@
|
||||
http:
|
||||
middlewares:
|
||||
opencode-inject-auth:
|
||||
headers:
|
||||
customRequestHeaders:
|
||||
Authorization: "Basic <redacted-opencode-basic-auth>"
|
||||
@@ -0,0 +1,27 @@
|
||||
http:
|
||||
routers:
|
||||
opencode:
|
||||
rule: Host(`opencode.sherlockhomeless.net`)
|
||||
entryPoints:
|
||||
- websecure
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
middlewares:
|
||||
- crowdsec-bouncer@file
|
||||
- ths-authentik@docker
|
||||
- opencode-basic-auth
|
||||
service: opencode
|
||||
|
||||
middlewares:
|
||||
opencode-basic-auth:
|
||||
headers:
|
||||
customRequestHeaders:
|
||||
# En el host desplegado se genera desde /etc/opencode-web.env.
|
||||
# No versionar el secreto real en el repo.
|
||||
Authorization: "Basic <base64(usuario:password-opencode)>"
|
||||
|
||||
services:
|
||||
opencode:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://host.docker.internal:4096
|
||||
Reference in New Issue
Block a user