- Shell 95%
- Dockerfile 4.3%
- jq 0.7%
| bin | ||
| completions | ||
| config | ||
| container | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| compose.yml | ||
| Dockerfile | ||
| README.md | ||
OpenCode Dev Server
Eine persistente, isolierte Entwicklungsumgebung für OpenCode. Der typische Einstieg ist:
Android/Conduit oder Laptop
│
SSH über NetBird
│
bestehende zmx-Session
│
dev <projekt>
│
OpenCode im Dev-Container ──HTTP/NetBird──> Ollama
│
~/Workspace/<projekt>
zmx läuft bewusst auf dem Host. Dieses Repository verändert weder die SSH-Konfiguration noch die vorhandene zmx-Session-Auswahl.
Voraussetzungen
- Linux-Server mit Docker Engine und Docker Compose v2
- der tatsächliche Serverbenutzer darf Docker ohne
sudoausführen - NetBird und zmx sind auf dem Host bereits eingerichtet
nmc-macmini2.netbird.cloud:11434ist vom Host und aus Docker erreichbarcurl,git,jq,ssh-keygen,ssh-keyscanund der Forgejo-Clientfjsind auf dem Host vorhanden
Die Mitgliedschaft in der Docker-Gruppe entspricht praktisch Root-Rechten auf dem Host. Der Docker-Socket wird deshalb trotzdem nicht in den Dev-Container gemountet.
Installation
Repository als derselbe Benutzer klonen, der später den Container startet:
git clone https://forgejo.infra.benediktwilli.ch/Hopiu/opencode-dev-server.git
cd opencode-dev-server
./bin/setup
setup erledigt Folgendes:
- prüft den Docker-Zugriff und übernimmt UID/GID des aktuellen Benutzers;
- legt standardmäßig
~/Workspaceund den persistenten State an; - liest die Ollama-Modellliste und fragt nach dem Defaultmodell;
- erzeugt einen eigenen, passphrasenlosen Forgejo-Entwicklungsschlüssel;
- zeigt den Forgejo-Hostkey zur expliziten Prüfung;
- verlinkt
devnach~/.local/bin/dev; - baut und startet den Container.
Den angezeigten Public Key anschließend in Forgejo registrieren. Er sollte nur
Zugriff auf die tatsächlich benötigten Repositories erhalten. Falls
~/.local/bin noch nicht im PATH liegt, muss dies in der Host-Shell ergänzt
werden.
Für eine reine Konfigurationsvorbereitung ohne Image-Build:
./bin/setup --no-build
Eine vorhandene .env wird nur mit --force neu geschrieben. Private Keys
werden dabei nie überschrieben.
Verwendung mit zmx
Nach dem SSH-Login wie gewohnt eine zmx-Session auswählen oder anlegen. Danach:
dev tales
Das startet OpenCode direkt in /Workspace/tales. Wenn SSH oder Conduit die
Verbindung verliert, bleibt der docker compose exec-Prozess innerhalb der
zmx-Session bestehen.
Existiert ~/Workspace/tales noch nicht, fragt dev nach und erstellt ein
leeres Git-Repository auf Branch main sowie ein privates Forgejo-Repository
im mit fj angemeldeten Benutzerkonto. origin verwendet SSH, damit Git im
Container weiterhin den dedizierten Forgejo-Key nutzt. Für nicht-interaktive
Aufrufe bestätigt dev tales --yes die Erstellung explizit. Vor der ersten
Erstellung muss fj am Forgejo-Host angemeldet sein, etwa mit:
fj auth login --host forgejo.infra.benediktwilli.ch
Auf einem Server ohne Browser kann stattdessen ein Forgejo-Token über
fj auth add-token --host forgejo.infra.benediktwilli.ch hinterlegt werden.
Scheitert die Remote-Erstellung, startet OpenCode nicht und es wird kein
Projekt unter dem endgültigen Workspace-Namen angelegt.
Verfügbare Kommandos:
dev --version [--check] Version des dev-Skripts anzeigen; optional OpenCode prüfen
dev [PROJECT] OpenCode in einem Projekt bzw. im Workspace starten
dev PROJECT --yes fehlendes Projekt ohne Rückfrage erstellen
dev shell [PROJECT] interaktive zsh im Container öffnen
dev models Ollama-Katalog aktualisieren und anzeigen
dev doctor Installation, Netzwerk und Isolation prüfen
dev update Image mit aktuellem Stable OpenCode neu bauen
dev restart Container ohne Image-Neubau neu erzeugen
dev stop Dev-Container stoppen
dev projects verfügbare Workspace-Projekte anzeigen
dev completion <SHELL> Completion für zsh oder bash ausgeben
Tab-Completion wird in der Host-Shell aktiviert. Für zsh folgende Zeile in
~/.zshrc aufnehmen:
source <(dev completion zsh)
Für bash gehört das Gegenstück nach ~/.bashrc:
source <(dev completion bash)
Nach dem Start einer neuen Shell vervollständigt dev Unterkommandos,
Optionen und Projektnamen aus WORKSPACE_PATH.
dev update und dev restart beenden beim Recreate alle noch laufenden OpenCode-Exec-Sessions
und verlangt deshalb interaktiv eine Bestätigung. Für Automatisierung existiert
dev update --yes.
dev --version zeigt ausschließlich die Version des Host-Skripts und funktioniert
auch vor dem Setup. Mit dev --version --check wird zusätzlich die im Dev-Container
installierte OpenCode-Version mit dem aktuellen Stable Release verglichen. Der Check
führt kein Upgrade aus; bei einem verfügbaren Update bleibt dev update der
bewusste Aktualisierungsschritt.
Verzeichnisse und Persistenz
Der Host sieht nach dem Setup ungefähr so aus:
~/Workspace/ Git-Repositories
opencode-dev-server/
├── .env lokale Pfade und Defaultmodell
├── state/home/ Container-Home und OpenCode-Sessions
└── secrets/forgejo/ eigener SSH-Key und known_hosts
Im Container liegt der Workspace unter /Workspace. Das persistente
Container-Home enthält insbesondere:
~/.config/opencode/für persönliche Provider- und TUI-Konfiguration;~/.local/share/opencode/für Credentials und Sitzungsdaten;~/.local/share/mise/für projektbezogene Laufzeiten;~/.cache/opencode-dev-server/für den generierten Ollama-Katalog.
Das State-Verzeichnis darf nicht gleichzeitig von einer OpenCode-Installation auf dem Host oder einem zweiten Dev-Container verwendet werden.
mise, Python und Node
Das Basisimage installiert absichtlich keine globale Python- oder Node-Version. Jedes Projekt definiert seine Laufzeiten selbst, zum Beispiel:
[tools]
python = "3.13"
node = "22"
Im Projekt anschließend einmalig:
dev shell tales
mise trust
mise install
Fehlt eine Deklaration, fügt mise use das Werkzeug dem Projekt hinzu und
installiert es zugleich, zum Beispiel:
mise use python@3.13 uv@latest
Eine leere Ausgabe von mise current oder mise list bedeutet, dass noch
keine Werkzeuge aktiv beziehungsweise installiert sind; mise selbst ist im
Image unter /usr/local/bin/mise vorhanden. Python-Versionen werden mit
mise, Abhängigkeiten und virtuelle Umgebungen anschließend mit uv
verwaltet. uv python install wird deshalb nicht verwendet.
Die installierten Laufzeiten bleiben im Container-State erhalten. Virtuelle
Umgebungen und node_modules können direkt im jeweiligen Workspace-Projekt
liegen.
OpenCode erhält über config/environment.md globale
Hinweise zu dieser Umgebung. Der Agent prüft dadurch zuerst die vorhandenen
Projektdateien und mise, bevor er ein Werkzeug als fehlend meldet. Eigene
projektbezogene Anweisungen gehören in AGENTS.md im jeweiligen Repository;
persönliche globale Regeln können unter ~/.config/opencode/AGENTS.md liegen.
Mitgelieferte Agents
Die Umgebung stellt zwei globale, modellunabhängige OpenCode-Agents bereit:
chatist ein Primary Agent für normale Unterhaltungen. Er behandelt den aktuellen Workspace nicht automatisch als Gesprächsgegenstand und hat keinen Zugriff auf Dateien, Shell, Websuche, Skills oder Subagents. In der TUI wird mitTabzwischenchatund den anderen Primary Agents gewechselt.project-analyzerist ein gezielt aufrufbarer Subagent. Zum Beispiel startet@project-analyzer Analysiere dieses Projekt und aktualisiere die Dokumentationeine evidenzbasierte Bestandsaufnahme. Er darf Projektdateien lesen und suchen, aber ausschließlichAGENTS.mdundREADME.mdim Projektstamm schreiben. Shell, Netzwerk, Skills und weitere Subagents bleiben gesperrt.
Die Definitionen liegen unter config/agents/ und werden
read-only in den Container gemountet. Beim Containerstart werden fehlende Namen
nach ~/.config/opencode/agents/ verlinkt. Eine dort bereits vorhandene Datei
oder ein abweichender Symlink gleichen Namens wird nicht überschrieben; weitere
persönliche Agents können im selben Verzeichnis liegen. Beim ersten Wechsel von
einer älteren Installation auf eine Version mit diesen Agents muss dev update
den Installer in das Image übernehmen. Spätere Änderungen an den gemounteten
Agent-Definitionen benötigen keinen erneuten Build; bei neu hinzugefügten Namen
genügt dev restart, damit der Installer die fehlenden Links anlegt.
Ollama und weitere Provider
Die lokale .env enthält:
OLLAMA_URL="http://nmc-macmini2.netbird.cloud:11434"
OPENCODE_DEFAULT_MODEL="qwen3-coder:30b"
Bei dev, dev models und nach einem Update fragt der Container
$OLLAMA_URL/api/tags ab. Alle gemeldeten Tags werden als Modelle des Providers
ollama erzeugt; der Default lautet ollama/$OPENCODE_DEFAULT_MODEL.
Ein neuer Ollama-Tag erscheint damit ohne Image-Neubau in OpenCodes /models.
Zum Wechseln des Defaults genügt eine Änderung in .env. Existiert der Tag
nicht, wird OpenCode nicht mit einem stillen Fallback gestartet. Ist Ollama nur
vorübergehend nicht erreichbar, bleibt der zuletzt gültige Katalog nutzbar.
Weitere Provider werden in OpenCode mit /connect eingerichtet. Credentials
landen im persistenten Container-Home. Die generierte Konfiguration belegt nur
den Provider ollama; persönliche und projektspezifische OpenCode-Konfiguration
wird gemäß OpenCodes normaler Prioritätsregeln dazugemischt.
Berechtigungen erweitern
config/opencode.base.json enthält eine geordnete
Allowlist. Lesen, Ändern, Suche sowie übliche Test-, Lint- und Build-Kommandos
laufen ohne ständige Nachfragen. Unbekannte Shell-Kommandos fragen nach.
Um einen regelmäßig verwendeten Befehl freizugeben, wird unter
permission.bash ein möglichst enges Muster ergänzt:
"ruff check *": "allow"
Die letzte passende Regel gewinnt. Deny-Regeln für rm -rf,
git reset --hard, sudo, Docker und Podman müssen deshalb am Ende bleiben.
git commit und git push bleiben bestätigungspflichtig. Projektspezifische
Regeln können zusätzlich in ~/Workspace/<projekt>/opencode.json liegen.
Die Allowlist ist keine vollständige Sandbox: freigegebene Task-Runner wie
mise run oder npm run können ihrerseits beliebigen Projektcode ausführen.
Die eigentliche Sicherheitsgrenze bleibt der reduzierte Container-Mount.
Sicherheitsmodell
- Container läuft mit derselben numerischen UID/GID wie der Hostbenutzer, aber nicht als Root.
- Nur Workspace, dediziertes Container-Home, OpenCode-Basiskonfiguration und Forgejo-Key werden gemountet.
- Kein
/var/run/docker.sock, kein/root, kein komplettes Host-Home. - Keine veröffentlichten Ports; OpenCode läuft ausschließlich als TUI.
- Root-Dateisystem read-only,
/tmpals temporäres Dateisystem,no-new-privilegesund keine zusätzlichen Linux-Capabilities. - Session-Sharing und OpenCode-Auto-Updates sind deaktiviert.
Der Agent kann den Forgejo-Key lesen und besitzt ausgehend Internetzugriff. Ein geleakter Schlüssel ist deshalb durch Forgejo-Rechte und regelmäßige Rotation zu begrenzen.
Diagnose
dev doctor
Geprüft werden Compose, Verzeichnisse, UID, DNS, Ollama, Modellgenerierung,
OpenCode, mise, die fj-Authentifizierung sowie das Fehlen von Docker-Socket
und Portfreigaben.
Typische Probleme:
- Ollama-DNS schlägt nur im Container fehl: Docker muss den NetBird-DNS des
Hosts verwenden können. Zuerst
getent hosts nmc-macmini2.netbird.cloudauf dem Host, danach denselben Check überdev doctorprüfen. - Defaultmodell fehlt:
dev modelsausführen undOPENCODE_DEFAULT_MODELin.envauf einen exakt angezeigten Tag setzen. - Workspace-Dateien gehören root: Image nach UID/GID-Änderungen mit
dev updateneu bauen. - Forgejo lehnt den Key ab: Public Key aus
secrets/forgejo/id_ed25519.pubregistrieren und Berechtigungen kontrollieren.
Backup und Wiederherstellung
Workspace-Projekte gehören weiterhin in Git. Für OpenCode-Sitzungen,
Credentials und mise-Installationen wird state/home gesichert. Vor einem
konsistenten Dateibackup den Container stoppen:
dev stop
# state/home mit dem bestehenden Server-Backup sichern
.env und secrets/forgejo gehören in ein verschlüsseltes Backup. Sie dürfen
nicht in Git committed werden. Nach einer Schlüsselrotation den Container mit
dev restart neu erzeugen, damit der neue read-only Dateimount verwendet wird.
Entwicklung und Tests
Die lokalen Tests benötigen Bash und jq; die Compose-Prüfung zusätzlich Docker Compose, aber keinen laufenden Daemon:
./tests/run.sh
Ein vollständiger Laufzeittest erfolgt nach ./bin/setup mit dev doctor.