No description
  • Shell 95%
  • Dockerfile 4.3%
  • jq 0.7%
Find a file
2026-08-25 21:57:05 +02:00
bin Fixed creation of new repository 2026-08-25 15:35:52 +02:00
completions Add --version flag. 2026-08-24 15:19:10 +02:00
config Add managed OpenCode agents and installation handling 2026-08-25 21:11:01 +02:00
container Add managed OpenCode agents and installation handling 2026-08-25 21:11:01 +02:00
tests Add managed OpenCode agents and installation handling 2026-08-25 21:11:01 +02:00
.dockerignore Initial commit 2026-08-10 15:36:56 +02:00
.env.example Add environment configuration and instructions for development setup 2026-08-11 10:09:33 +02:00
.gitignore Initial commit 2026-08-10 15:36:56 +02:00
AGENTS.md Updated agents and readme 2026-08-25 21:57:05 +02:00
compose.yml Add managed OpenCode agents and installation handling 2026-08-25 21:11:01 +02:00
Dockerfile Add managed OpenCode agents and installation handling 2026-08-25 21:11:01 +02:00
README.md Updated agents and readme 2026-08-25 21:57:05 +02:00

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 sudo ausführen
  • NetBird und zmx sind auf dem Host bereits eingerichtet
  • nmc-macmini2.netbird.cloud:11434 ist vom Host und aus Docker erreichbar
  • curl, git, jq, ssh-keygen, ssh-keyscan und der Forgejo-Client fj sind 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:

  1. prüft den Docker-Zugriff und übernimmt UID/GID des aktuellen Benutzers;
  2. legt standardmäßig ~/Workspace und den persistenten State an;
  3. liest die Ollama-Modellliste und fragt nach dem Defaultmodell;
  4. erzeugt einen eigenen, passphrasenlosen Forgejo-Entwicklungsschlüssel;
  5. zeigt den Forgejo-Hostkey zur expliziten Prüfung;
  6. verlinkt dev nach ~/.local/bin/dev;
  7. 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:

  • chat ist 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 mit Tab zwischen chat und den anderen Primary Agents gewechselt.
  • project-analyzer ist ein gezielt aufrufbarer Subagent. Zum Beispiel startet @project-analyzer Analysiere dieses Projekt und aktualisiere die Dokumentation eine evidenzbasierte Bestandsaufnahme. Er darf Projektdateien lesen und suchen, aber ausschließlich AGENTS.md und README.md im 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, /tmp als temporäres Dateisystem, no-new-privileges und 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.cloud auf dem Host, danach denselben Check über dev doctor prüfen.
  • Defaultmodell fehlt: dev models ausführen und OPENCODE_DEFAULT_MODEL in .env auf einen exakt angezeigten Tag setzen.
  • Workspace-Dateien gehören root: Image nach UID/GID-Änderungen mit dev update neu bauen.
  • Forgejo lehnt den Key ab: Public Key aus secrets/forgejo/id_ed25519.pub registrieren 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.