Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

errand

Run the thing you would have run locally, on another machine you own, and get the result back: same command, same working tree, logs streaming to your terminal, real exit code. The heat, watts, and minutes are spent elsewhere.

Status: milestone 3. The transactional core works end to end over a tailnet, including durable detached jobs, restart reconciliation, and content-addressed snapshot reuse. The v0 design is frozen in docs/DESIGN.md.

Quickstart

# on a runner (Linux box on your tailnet)
errand serve                  # config: ~/.config/errand/errandd.toml

# on a caller
errand info                   # measured facts: arch, kvm, tools
errand -- python3 -m unittest # runs your working tree over there
errand --no-snapshot -- uname -a # runs in a fresh empty remote workspace
# Ctrl-D detaches without stopping the remote job; Ctrl-C interrupts it
job=$(errand --detach -- make build)
errand ps                     # list your jobs across configured peers
errand attach "$job"          # replay logs and follow to completion

Runner config authorizes callers by tailnet identity (whois): an ACL app capability or a local allow_users list. Destination-scoped capability checks require Tailscale 1.100 or newer. No keys, no credentials stored.

Run errand from a Git worktree for automatic snapshot selection. A non-Git directory requires an explicit .errandignore policy or --include-all. Errand always refuses a filesystem root, and snapshotting your home directory requires --include-all. The client prints the selected file count and byte total before remote admission. Use --no-snapshot when a command needs no local files; Errand then skips local inspection and runs it in an empty remote workspace. Because that workspace is empty, --workdir may only name its root.

Why not just ssh?

ssh gives you a remote shell and assumes the remote already has your project state: a checkout on the right branch, your uncommitted changes, no stale artifacts. Every ssh target is a pet you maintain.

errand ships the workspace with the job, so targets are stateless with respect to your projects: nothing checked out, nothing drifting, any peer equally valid at any moment. Around that round-trip it wraps a transaction ssh doesn't attempt. The implemented milestones provide:

  • At-most-once admission: network retries cannot run a command twice.
  • Durable execution: an admitted job survives caller disconnect, with ordered logs that the protocol can resume after a dropped connection.
  • Honest cleanup: errand removes the temporary workspace and inherited process scope it owns, then records whether cleanup completed.
  • A receipt: an append-only record of what was asked, who asked, what ran, and what happened.

The remaining v0 work includes declared output transfer with conflict detection, explicitly declared named caches, fact-based peer selection, and direct LAN pairing.

Shape

One binary, symmetric peers, no controller. Transport and identity come from your tailnet (WhoIs + ACL capability grants; errand stores zero credentials) or, later, from direct LAN pairing with pinned device keys. The current execution backend runs directly on the host. Rootless containers and Nix devshells are planned.

errand -- cargo test                   # configured default peer
errand --on buildbox -- cargo test     # named configured peer

An attached terminal can detach at any time with Ctrl-D and later resume with errand attach HANDLE. Ctrl-C keeps its Unix meaning: it sends SIGINT to the remote command, and a second Ctrl-C force-kills it. Interactive detachment returns 0 for the detach action; it is not the unfinished job's exit status. Non-terminal EOF is ignored, so scripts remain attached unless they request --detach explicitly.

Detached jobs, ps, attach, kill, snapshot-cache inspection, and cache GC are implemented. Planned v0 commands still include fact-based peer selection, output fetching, named-cache management, and pairing.

Capability-based runners must grant manage-caches to use errand caches and errand gc; the frozen design's ACL example includes the complete action set.

Linux and macOS first; Windows is a design constraint, not yet a deliverable.

Non-goals

Not a CI system, not interactive (no PTY in v0), not a security boundary against hostile code, no web UI, and no arbitrary-host discovery or scheduler fan-out. errand ps only queries explicitly configured peers. The design resists becoming ansible, nomad, or a scheduler on purpose.

License

MIT

About

Run it on another machine you own, get the result back — a personal job runner for your tailnet

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages