godo

command module
v1.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 2, 2026 License: MIT Imports: 2 Imported by: 0

README

godo

A small CLI for running and managing long-lived background processes — like pueue plus per-process PTY attach, in a single static Go binary.

godo runs a command as a supervised background process that survives shell logout. Listing, restarting, stopping, and tailing logs all go through one local daemon over a Unix socket. The daemon auto-spawns the first time you run anything, so installation is just dropping the binary on $PATH.

Status

v1 feature-complete on Linux and macOS. v2 (live job-to-job piping, remote / agent-oriented surfaces, ...) starts after a dogfooding window — see Roadmap.

Install

One-liner (Go 1.25+ required):

go install github.com/uhryniuk/godo@latest

This fetches, builds, and drops godo into $GOPATH/bin (typically ~/go/bin). Make sure that's on your $PATH:

export PATH="$PATH:$(go env GOPATH)/bin"

From source:

git clone https://github.com/uhryniuk/godo
cd godo
go install .

Quick tour

# Run a command in the background. Returns the job id and pid.
godo sh -c "while true; do date; sleep 1; done"
# d2a91e0c  pid=12345  sh -c while true; do date; sleep 1; done

# List everything the daemon knows about.
godo list
# ID        NAME                                           STATE    PID    UPTIME  EXIT
# d2a91e0c  sh -c while true; do date; sleep 1; done       running  12345  4s      -

# Tail the combined output (PTY merges stdout + stderr).
godo logs d2a91e0c
godo logs -f d2a91e0c        # follow: replays + streams live; Ctrl+C to detach

# Drop into the job's PTY. Type to send to its stdin; see what it prints.
# Default detach is Ctrl+X then d (chosen to avoid tmux/screen/zellij mode
# prefixes). Override with $GODO_DETACH, e.g. GODO_DETACH="Ctrl+B,d" if you
# prefer the tmux convention.
godo attach d2a91e0c

# Stop, restart, remove.
godo stop d2a91e0c           # SIGTERM, marks Cancelled
godo restart d2a91e0c        # same hash, fresh PID
godo rm d2a91e0c             # only when stopped; deletes the log dir

# godo does NOT shell-tokenize: don't quote the whole command.
#   bad:  godo "python3 -m http.server"   ← passed as one filename, exec fails
#   good: godo python3 -m http.server     ← shell tokenizes, godo passes through
#   good: godo sh -c "python3 -m http.server"   ← when you need shell features

# Name an ad-hoc command — pass --name (or -n) BEFORE the command. Anything
# after the command name is passed to the child untouched, so a --name in
# the child's own args is preserved.
godo --name web sh -c "python3 -m http.server 8888"
godo rm web

# For richer flags (--restart / --nice / --env / --working-dir), use the
# explicit form — `--` separates godo flags from the child's:
godo run --name web --restart on-failure -- node server.js --port 8080

# Then target by name:
godo stop web
godo logs -f web

# Start a registered service by name (service must exist in ~/.godo/services/):
godo start my-svc

# Scaffold a new declarative service file (opens it in $EDITOR):
godo template my-svc

# List all installed service files:
godo services          # or: godo svc / godo svcs / godo service

# JSON output for scripting:
godo list -o json | jq '.[] | select(.state=="running") | .name'

# Replace the running daemon with a freshly built godo (kills running jobs).
# Subsequent CLI calls also print a dim notice when the supervisor binary
# doesn't match the CLI build, prompting an upgrade.
godo upgrade

# Shut everything down (no respawn until the next godo invocation):
godo shutdown

Targets accept either an exact name or a hash prefix.

How it works

   CLI invocation                     Supervisor daemon (auto-spawned)
   ┌─────────────┐    Unix socket    ┌──────────────────────────────┐
   │ godo run … │ ─────────────────► │ accept → dispatch → respond │
   └─────────────┘  length-prefixed  │                              │
                        JSON         │  Registry  Runner  Reaper   │
                                     │     │        │       │       │
                                     │     ▼        ▼       ▼       │
                                     │  registry.json   /bin/sh ... │
                                     │                    │ PTY     │
                                     │                    ▼         │
                                     │             Multiplexer ────┐│
                                     │             ├─► output.log  ││
                                     │             └─► logs -f tail││
                                     └──────────────────────────────┘
  • One daemon per user, listening on ~/.godo/state/godo.sock.
  • First godo invocation acquires a flock on ~/.godo/state/godo.sock.lock, double-forks godo supervisor with Setsid, and polls until the socket is ready. Concurrent invocations spawn exactly one daemon.
  • Children run under a real PTY (creack/pty) so jobs see a TTY, and stdout/stderr land in one combined output.log.
  • The output Multiplexer fans PTY reads out to the log writer plus any active logs -f and attach clients with a drop-on-full policy — slow consumers can never block the producer.
  • An InputMerger sits in front of the PTY master for the input direction. Today only attach registers a source; the v2 godo pipe will register sending jobs as additional sources without touching the write path.
  • Daemon state persists to ~/.godo/state/registry.json (atomic write, corrupt-tolerant load) so a restarted daemon remembers what was previously running. Children themselves do not yet survive a daemon restart.
  • Logout-only by design: jobs survive your shell exit but are gone after a reboot. No system-level installation required.

v1 CLI

Command Behavior
godo [--name X] <cmd> [args...] Run <cmd> as a supervised job. --name/-n (before the command) labels the job for stop/rm/etc. Auto-spawns the daemon.
godo list [-o table|json] / godo ps Print all jobs as a table (default) or full JSON records.
godo stop <id|name> SIGTERM the job's process group; sets cancelled. Sends SIGCONT first so paused jobs unfreeze and receive the signal.
godo restart <id|name> Stop, wait for actual exit (escalating to SIGKILL after a grace window), then start again with the same spec and hash.
godo pause <id|name> / godo resume <id|name> Freeze a running job (SIGSTOP) / continue a paused one (SIGCONT). The process holds its memory, fds, and ports while paused — handy for "let me check something" without losing state.
godo rm <id|name> Drop a stopped job from the registry and delete its log dir.
godo logs <id|name> Print the job's combined output.
godo logs -f <id|name> Stream the log: replays existing content then forwards live writes.
godo attach <id|name> Take over the job's PTY. Default detach: Ctrl+X then d ($GODO_DETACH overrides).
godo load <file.toml> Import a service file into ~/.godo/services/ and register it.
godo reload Rescan ~/.godo/services/; new files autostart, removed files stop.
godo start <name> Start a registered service by name. Errors if already running — use godo restart to force.
godo template <name> Scaffold ~/.godo/services/<name>.toml and open it in $EDITOR. --force overwrites; --no-edit skips the editor.
godo services (aliases: svc, svcs, service) List all installed service files as a table showing command, autostart, restart policy, cron schedule, and parse status.
godo monit / godo -i Bubble Tea dashboard. j/k move, r restart, p pause/resume, K kill, d remove (confirm with y), Enter attaches a running job or opens the persisted log for a non-running one (j/k/g/G/PgUp/PgDn to scroll, q back).
godo run [flags] -- <cmd>... Explicit form with flags (--name, --restart, --nice, --env).
godo upgrade Stop the running supervisor so the next CLI call spawns the new binary. Lists running jobs that will die and prompts (--yes / --force).
godo shutdown Tell the daemon to stop all children, persist state, and exit.
godo version Print the daemon version and build SHA.
godo daemon Run the supervisor in the foreground (debug / dev).

Hidden: godo supervisor is the double-fork target invoked by auto-spawn.

Service files

For long-lived workloads it's nicer to declare them than to remember the godo run … command line. Drop a TOML file in ~/.godo/services/ and the daemon picks it up on boot.

The fastest way to author one is godo template <name>, which writes a fully-commented starter and opens it in $EDITOR. The editor is resolved as $EDITOReditor = "..." in ~/.godo/config.tomlvim.

# ~/.godo/services/web.toml
name        = "web"             # optional; defaults to filename without .toml
command     = "node"
args        = ["server.js"]
working_dir = "/srv/web"
autostart   = true              # start on daemon boot / reload
restart     = "on-failure"      # no | on-failure | always
nice        = 5                 # POSIX priority

[env]
PORT = "8080"

[cron]                          # optional
schedule = "0 4 * * *"          # 5-field cron OR @hourly / @daily / @every 5m
overlap  = false                # default: skip a tick if a prior run is still active

godo load /path/to/file.toml imports a file from anywhere into the services dir (the destination basename is derived from name, so source paths from temp dirs / version control are normalized cleanly). godo reload rescans the dir and applies the diff:

  • New file → registered, autostarted if flagged.
  • Removed file → matching job is stopped (kept in the registry as cancelled so logs are still readable; godo rm to drop).
  • Modified file → in-memory spec updated, but not auto-restarted. Run godo restart <name> when you're ready for the new spec to take effect (matches systemd's daemon-reload semantics).

Service identity is the file path. A given file always maps to the same registry entry across daemon restarts, so log dirs and short hashes stay stable.

Upgrading the binary

The supervisor process is the binary that was running when it first auto-spawned — installing a newer godo doesn't replace it in place. To pick up new behavior:

godo upgrade        # confirms, lists jobs that'll die, shuts the daemon down

The next godo call auto-spawns the new binary. While the CLI and daemon disagree, every non-management godo command prints a dim one-line stderr notice with the two build hashes and a hint to run godo upgrade. Both sides identify themselves by vcs.revision (with a +dirty suffix when the working tree was modified at build time), so locally hacked binaries don't accidentally compare equal to their parent commit.

Cron

Service files can declare a schedule and the daemon's internal scheduler fires the command on each tick. Each fire creates a fresh registry entry named <service>@<unix-second> so multiple runs don't collide. By default overlap = false suppresses a new tick if the previous instance is still running — bump to true for fan-out workloads.

# ~/.godo/services/cleanup.toml
name    = "cleanup"
command = "/usr/local/bin/prune-old-logs"

[cron]
schedule = "0 3 * * *"          # 3am every day

@every 30s, @hourly, and @daily work too.

Roadmap

v1 is feature-complete. Things on deck for v2, scoped after a dogfooding window:

  • Live job-to-job pipinggodo pipe A B fans A's output into B's input. The daemon's output multiplexer and input merger are already shaped for this; v2 just adds the RPC, cycle detection, and a TUI wire-view.
  • Remote / agent-oriented surfaces — HTTP frontend over the existing wire protocol so the TUI (or a web view, à la zellij) can connect to a daemon on another machine, plus exploration of agent-driven workflows where godo is both the runtime and the introspection surface.
  • Reusable command aliases — a lightweight godo alias <name> -- <cmd>... that survives godo rm and lets you re-launch by name (today the closest equivalent is a service file).
  • Log truncation on attach — cap the history replayed by godo attach / TUI Enter to the last N lines (configurable in config.toml, default ~1 000). Both surfaces should share the same limit so they're consistent. The in-TUI log viewer and godo logs one-shot are unaffected — those always show the full file.
  • Log rotation — instead of a single ever-growing output.log, rotate at a configurable byte threshold (e.g. 50 MB default). Rotation happens in the log-writer goroutine on each write, producing numbered segments (output.log, output.log.1, output.log.2, …) up to a configurable keep count. godo logs and logs -f reassemble segments in order; oldest segments beyond the keep count are deleted. Config knobs: log_max_bytes, log_max_files.
  • Ergonomic gaps--quiet and richer --output formats elsewhere, real ionice plumbing on Linux, live-upgrade that preserves running jobs across godo upgrade.

See TODO.md (gitignored) for the running ledger.

Where state lives

~/.godo/
├── config.toml           # user-level defaults (Step 7)
├── services/             # one declarative service per file (Step 7)
└── state/
    ├── godo.sock         # daemon listener
    ├── godo.sock.lock    # auto-spawn flock
    ├── registry.json     # snapshotted registry
    └── <hash>/
        └── output.log    # PTY-merged stdout + stderr

Development

go test -race ./...        # unit + functional, with race detector
go vet ./...
gofmt -l .                 # exits clean if everything is formatted

CI on push runs all four on Linux + macOS. See .github/workflows/test.yml.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
autospawn
Package autospawn brokers between a CLI invocation and a running godo daemon.
Package autospawn brokers between a CLI invocation and a running godo daemon.
buildinfo
Package buildinfo exposes the binary's build identity (the short git revision it was compiled from).
Package buildinfo exposes the binary's build identity (the short git revision it was compiled from).
daemon
Package daemon implements the godo supervisor daemon.
Package daemon implements the godo supervisor daemon.
job
Package job defines the Job model and its state machine.
Package job defines the Job model and its state machine.
proto
Package proto implements godo's CLI<->daemon wire protocol.
Package proto implements godo's CLI<->daemon wire protocol.
ptyproxy
Package ptyproxy is the client-side PTY proxy used by `godo attach` (and the in-pane attach inside the TUI).
Package ptyproxy is the client-side PTY proxy used by `godo attach` (and the in-pane attach inside the TUI).
service
Package service defines the on-disk service-file format that godo consumes from ~/.godo/services/*.toml.
Package service defines the on-disk service-file format that godo consumes from ~/.godo/services/*.toml.
tui
Package tui is the godo monit dashboard.
Package tui is the godo monit dashboard.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL