Skip to content

hayeah/devportv3

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

name devport
description Run per-project dev services from a single `devport.toml` — one verb (`devport up`) allocates ports, forks per-service tmux sidecars, waits for health, then either blocks until Ctrl-C (foreground) or exits leaving sidecars alive (`--daemon`). Inspect via `devport ls` (JSONL over `.devport/`), tear down via `devport down`. Use when starting, stopping, or inspecting a project's dev stack; configuring a new project's dev spec; or debugging why a supervised service isn't healthy.

devport

Ground-up rewrite of devportv2 on top of the state-dir + dir-flock substrate from hayeah-go/supervisor. One verb, one spec, per-project state.

Status: v1 under construction. Foreground + daemon lifecycles work end-to-end. ls is JSONL over .devport/.

Quick start

# ./devport.toml
tmux_session = "devport-myapp"
# host = "127.0.0.1"   # optional. Default "" binds all interfaces
                       # (Tailscale tailnet peers can reach the stack).

[[service]]
type = "proxy"

  [[service.routes]]
  name         = "api"
  path         = "/api/"
  strip_prefix = true
  type         = "exec"
  command      = ["./bin/api", "--port", "$${PORT}"]
    [service.routes.health]
    type     = "tcp"
    interval = "1s"
    timeout  = "30s"

  [[service.routes]]
  name    = "web"
  path    = "/"
  type    = "static"
  root    = "./web/dist"
$ devport up
devport up: stack is healthy
  API                   http://127.0.0.1:23001
  _proxy0               http://127.0.0.1:23000
# Ctrl-C tears everything down.
$ devport up --daemon
devport up: stack is healthy
  API                   http://127.0.0.1:23001
  _proxy0               http://127.0.0.1:23000
# up has exited; services are supervised by per-pane sidecars in the tmux session.
# A one-shot summary is appended to .devport/up.log.

$ devport ls | jq -c '{key: .supervisor.key, alive, state: .state.state, port: .state.port, sidecar: .supervisor.pid}'
{"key":"api","alive":true,"state":"healthy","port":23001,"sidecar":72341}
{"key":"_proxy0","alive":true,"state":"healthy","port":23000,"sidecar":72342}

$ devport down
devport: stopped 2 supervisor(s)

CLI verbs

Command Purpose
devport up [spec] Foreground (default). Blocks until SIGINT; first signal runs graceful teardown, second forces exit 130.
devport up [spec] --daemon Spawn sidecars, wait for healthy, exit. Services survive in the tmux session. One-shot summary at .devport/up.log.
devport up [spec] --host HOST Override the spec's host for proxy listeners. Default "" binds all interfaces.
devport down [spec] SIGTERM every sidecar listed in the spec's .devport/. SIGKILL stragglers after timeout.
devport ls [spec] JSONL of services in the spec's .devport/ (default ./.devport/). Pipe to duckql for interactive queries.
devport _supervise Hidden. One per service — spawned by devport up inside a fresh tmux pane. Owns the flock, writes state.json, serves rpc.sock, and (for exec leaves) spawns the child inline; (for proxies) runs the HTTP router inline.

Host precedence: --host flag > spec host > "" (all interfaces).

Deferred: logs (use tmux attach for now), status / doctor (redundant with ls | duckql), attach, freeport, ingress, include for cross-repo composition.

devport.toml shape

tmux_session = "devport-myapp"   # required. Session name for sidecar windows.
host         = "127.0.0.1"        # optional. Bind host for proxy listeners.
                                  # Default "" = all interfaces (tailnet-reachable).

[[service]]                       # one per top-level service. type = "exec" | "proxy".
type    = "exec"
name    = "worker"
command = ["./bin/worker", "--port", "$${PORT}"]
  [service.health]
  type = "tcp"; interval = "1s"; timeout = "30s"

[[service]]
type = "proxy"
  [[service.routes]]              # proxy children: exec / static / http.
  name         = "api"
  path         = "/api/"
  strip_prefix = true
  type         = "exec"
  command      = ["./bin/api", "--port", "$${PORT}"]
    [service.routes.health]
    type = "tcp"; interval = "1s"; timeout = "30s"
  • $${PORT} in command is substituted with the service's allocated port at spawn.
  • State-dir keys follow the env-var prefix rule, lowercased: APP_VITE_PORT.devport/app_vite/state.json, one-to-one.
  • Env fan-out to each service pane is shell-env forwarded via tmux new-window -e KEY=VAL, filtered by the ForwardableEnv deny-list (e.g. PATH, SHELL, TMUX* are dropped).

Architecture

Invariants the rest of the system falls out of:

  • One flocked directory = one port = one live owner. Every supervised service (exec leaf or proxy) gets a state directory flocked by a per-service devport _supervise process; killing a sidecar releases its flock. No SQLite, no global registry. State lives in ./.devport/<key>/state.json.
  • Tmux is the coordination layer. devport up launches each sidecar by calling tmux new-window -e KEY=VAL … devport _supervise … (env fan-out via argv, not a shared env file — no secrets on disk). __supervise runs inside the pane, so the child's stdio goes to the pane pty — tmux attach gives you the user-visible terminal. After health, up exits (or blocks on SIGINT in foreground mode) and plays no further role.
  • Proxies are supervised the same way as execs. Every type = "proxy" gets its own _supervise pane. The HTTP router runs inline inside _supervise (no _serve-proxy subprocess) — exec routes are pre-resolved by reading upstream ports from sibling state.json files before binding.
  • devport ls is JSONL verbatim. One line per state.json, each wrapped in { "dir", "alive", "supervisor", "state" }. state is the service section, refreshed every health tick.

Why v3

  • No SQLite. Every port-owning entity is a flocked directory. The registry = walking .devport/. ls, cat, jq are the debugger.
  • One CLI verb. devport up subsumes v2's run / proxy / up. Ephemeral vs. daemonized is a flag.
  • Service tree. Proxies and their children live in one TOML tree, same shape for ephemeral and persistent. Inspired by devportv2's docs/unified-design.md.
  • Supervised proxies. Each proxy has its own _supervise pane — so it has a home in daemon mode (observable via tmux attach), its own flock, and the same kill path as exec leaves. The router runs inline inside _supervise (no extra subprocess).

Layout

spec.go            TOML schema + validation + Phase-A env expansion
tree.go            Walk Spec → Nodes with env-prefix + state-dir keys
env.go             .env file writer driven by the tree
ports.go           FreePort / isPortFree
health.go          CheckHealth (tcp / http / process / none)
plugin.go          ServiceState (the state section of state.json)
exec.go            NewExecService + ExecService.Run (supervisor.Service impl for exec leaves)
proxy_service.go   NewProxyService + ProxyService.Run (supervisor.Service impl for proxies)
env_forward.go     ForwardableEnv deny-list filter for shell→pane env fan-out
supervise.go       RunSupervise: assemble Service + NewTmuxPTY, call supervisor.New/.Run
proxy.go           ProxyConfig + Router (served inline by ProxyService)
proxy_handlers.go  StaticHandler + HTTPHandler + declineOn404Writer
registry.go        WalkStateDir + WriteLs + UsedPorts
up.go              Up orchestrator: allocate ports, spawn tmux panes, waitHealthy, signals
down.go            Down (walk state dirs, SIGTERM every supervisor.pid, SIGKILL on timeout)
cli/devport/       stdlib-flag dispatcher: up / down / ls / _supervise

Development uses a go.work (gitignored) pointing at the local dotfiles/libs/hayeah-go checkout for the supervisor package.

Running tests

# Unit tests
go test ./...

# Full e2e (requires tmux)
go test -v -timeout 120s ./...

Related

  • hayeah-go/supervisor — the state-dir + dir-flock supervisor library devport builds on.
  • duckql — pipe devport ls JSONL through for interactive SQL queries.
  • devportv2 — the predecessor; see its docs/unified-design.md for the service-tree motivation.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages