Zum Hauptinhalt springen
M

dsh-farm

marchliu/dsh-farm

Service farm for DSH: register, start, stop and restart long-running project services, with agent tools (farm_status/start/stop/restart/logs/register), farm.yaml declarations and an overview drawer UI with live log follow, search and export.

Installation

dsh plugin --profile web add github:marchliu/dsh-farm

README

dsh-farm 🚜

DSH service farm plugin: register, start, stop, restart and watch long-running project services — the agent can do it through tools, you can do it through the web UI.

Install

dsh plugin --profile <name> add /path/to/dsh-farm

Restart the DSH web surface afterwards.

What you get

  • UI: a 🚜 button at the sidebar foot with a running-services badge; clicking it opens the overview drawer — services grouped by workspace with status dots, start/stop/restart buttons, and a log view with live follow (SSE), substring search and download/export. With DSH-better-sidebar installed the same panel becomes a Farm tab in that sidebar instead — see Where the UI lives.

  • Delete: 🗑 on a row removes that one service. ☑ select switches the drawer into multi-select — tick rows (or select all), then delete (n) opens a confirmation dialog listing the targets; nothing is deleted until you confirm it. farm.yaml services are greyed out in both paths: the file owns them, so you remove them by editing it.

  • Agent tools: farm_status / farm_start / farm_stop / farm_restart / farm_logs(service, tail, search) / farm_register(name, workspace, command, ...) / farm_unregister(service | services). Say "帮我把 dev server 注册到 farm 并启动" in a session and the agent does the rest.

  • farm.yaml (optional, per workspace, commit-friendly):

    services:
      dev-server:
        command: pnpm dev
        # cwd: sub/dir        # optional, resolved against the workspace
        autoRestart: true
        env:
          PORT: 5173
    

    Declared services appear in the UI and farm_status automatically (source: yaml); the file is always the source of truth.

Where the UI lives

dsh-farm renders its panel in whichever home is available, decided at runtime:

DSH-better-sidebarWhere the panel appears
installeda 🚜 Farm tab in better-sidebar (single-instance, with a running-count badge); dsh-farm's own footer button and drawer stand down
not installeddsh-farm's own 🚜 footer button + right-hand drawer

Nothing to configure, and better-sidebar is not a dependency — dsh-farm looks the service up by name at runtime and keeps working when it is absent. It also follows better-sidebar being enabled or disabled while DSH is running, in both directions.

Implementation note for anyone extending this: betterSidebar is deliberately not in the client half's inject. In DSH's cordis a declared-but-missing service parks the whole plugin, which would take the fallback UI down with it — the plugin would vanish entirely rather than fall back. The runtime's optional-lookup hook is ctx.get('betterSidebar') (undefined when absent), and ctx.on('internal/service', …) catches better-sidebar arriving or leaving later. That event can fire more than once for the same arrival, so registration is idempotent.

Behavior notes

  • Services are spawned as children of the DSH process (/bin/sh -c), so they are reclaimed when DSH exits. Dynamic registrations persist across restarts in $DSH_HOME/storages/dsh-farm/services.json (state does not).
  • Stop = SIGTERM, then SIGKILL after a 3s grace.
  • autoRestart retries an abnormal exit up to 5 times with exponential backoff (1s → 16s).
  • Logs: 5000-line in-memory ring per service plus an append-only file under $DSH_HOME/storages/dsh-farm/logs/<id>.log.
  • Deleting a service stops it first, then drops its registry row, its live log stream and its log file. Ids are derived from workspace + name, so this keeps a later re-registration of the same name from inheriting stale logs. Project files are never touched.

HTTP API (localhost only, prefix /farm)

RouteMeaning
GET /farm/services?workspace=list (farm.yaml entries merged in)
POST /farm/servicesregister/update a dynamic service
GET /farm/services/:idone service
DELETE /farm/services/:idunregister one (stops it first; 400 for farm.yaml services)
POST /farm/services/batch-deleteunregister many — { ids: [] }{ ok, deleted, failed }
POST /farm/services/:id/start|stop|restartlifecycle
GET /farm/services/:id/logs?tail=&search=&export=1logs, export=1 downloads
GET /farm/services/:id/logs/streamSSE live follow

Config (override in the profile patch)

- id: farm
  config:
    dataDir: /abs/path   # default $DSH_HOME/storages/dsh-farm
    ringLines: 5000
    stopGraceMs: 3000

Ähnliche Plugins