Skip to content

v2.26.0: Automatic proxy hostnames and oneshot daemons

Choose a tag to compare

@jdx jdx released this 19 Sep 10:37
· 62 commits to main since this release
Immutable release. Only release title and notes can be modified.
590fc1b

The reverse proxy now gives every project daemon a stable hostname without registering a slug, oneshot = true turns a daemon into a run-to-completion task that dependents wait for, and the supervisor no longer mistakes a recycled pid for a live supervisor after a reboot.

Added

  • Automatic proxy hostnames for projects and worktrees (#880) — @jdx. With the proxy enabled, any project daemon that has a port is reachable at <daemon>.<project>.<tld>, and the same daemon in a linked git worktree at <daemon>.<worktree>.<project>.<tld>, so two projects with an api daemon no longer collide and no slug is required. URLs stay stable when port bumping moves the daemon, and requests can still auto-start a stopped daemon.

    # ~/.config/pitchfork/config.toml
    [settings.proxy]
    enable = true
    https = false
    port = 8088
    
    # myproject/pitchfork.toml
    [daemons.api]
    run = "node server.js"
    port = { expect = [3000], bump = 10 }
    proxy = "web"      # optional: use the label "web" instead of "api"; `proxy = false` opts out

    After pitchfork supervisor start --force and pitchfork start api, the daemon is served at:

    api.myproject.localhost:8088             # primary checkout
    api.fix-login.myproject.localhost:8088   # linked worktree checked out as fix-login
    

    The project label comes from the primary checkout's namespace or directory name; a worktree's label comes from its directory name or a checkout-local worktree_label. pitchfork start, list, status, and the web UI display the URLs, list --json / status --json gain a url field (proxy_url is kept), daemons receive PITCHFORK_URL, and templates can use {{ url }}, {{ host }}, and {{ daemons.api.url }}. pitchfork proxy status groups hostnames by project and worktree and lists label conflicts; conflicting labels are not routed.

    Compatibility notes: the proxy remains disabled by default. Existing [slugs] and pitchfork proxy add keep working and take precedence over automatic hostnames. Routed daemons now receive HOST=127.0.0.1 for automatic hostnames as well as slugs (except in LAN mode). Automatic hostnames are not written to /etc/hosts or published over mDNS, so use a browser that resolves .localhost, configure DNS for a custom TLD, or use a slug for LAN discovery; custom TLS certificates must cover the project and worktree names (*.localhost alone is not enough). Checkouts that share an explicit namespace still share daemon identity, so give them distinct namespaces to run same-named daemons at once. See the port management guide.

  • Oneshot daemons: tasks that are ready when they exit 0 (#876) — @jdx. Set oneshot = true on a daemon to run migrations, seeds, or other setup to completion before dependents start, replacing the migrate && exec sleep infinity workaround.

    [daemons.db]
    run = "postgres -D ./data"
    ready_cmd = { run = "pg_isready -h 127.0.0.1", timeout = "30s" }
    
    [daemons.migrate]
    run = "npm run migrate"
    oneshot = true
    depends = ["db"]
    
    [daemons.api]
    run = "node server.js"
    depends = ["migrate"]

    pitchfork start api starts db, waits for it to be ready, runs migrate, and starts api only after it exits 0. A clean exit is recorded with the new completed status (shown in list, list --status completed, status, wait, the TUI, and the web UI; JSON output only gains a new status value). A nonzero exit is errored, follows the normal retry policy, and blocks dependents. ready_* and health_* fields are rejected on a oneshot daemon. Explicit start/restart re-run a completed task (including via depends), so the command must be safe to repeat; directory entry via auto = ["start"] leaves completed tasks alone. pitchfork start waits up to supervisor.oneshot_timeout (default 1h; 0 waits indefinitely), configurable per project. Regular daemons are unaffected and still report stopped on a clean exit. See the oneshot tasks guide.

  • general.windows_shell setting, defaulting to cmd /C (#852) — @JamBalaya56562. On Windows without sh on PATH (the default Git for Windows install), every daemon failed to start with program not found because general.shell defaulted to sh -c everywhere. Windows now uses general.windows_shell (PITCHFORK_WINDOWS_SHELL) for daemon spawns, ready_cmd/health_cmd probes, lifecycle hooks, and the log archive hook. An explicitly set general.shell (config file or PITCHFORK_SHELL) still wins as a cross-platform override, which you need if a shared pitchfork.toml uses POSIX syntax in run strings.

Fixed

  • A stale supervisor pid is no longer trusted after a reboot or pid reuse (#878) — @jdx. If the supervisor died or the machine rebooted and the OS handed its pid to another process, every command failed with Failed to connect to supervisor, supervisor start demanded --force, and supervisor stop sent SIGTERM to the unrelated process (reported in #877). The supervisor's recorded start time and boot time are now checked before the pid is believed: stale records are cleaned up and auto-start proceeds, supervisor start/run replace them without --force, supervisor status --json reports down, and any real kill is bound to the recorded process generation (pidfd on Linux, process handle on Windows, start-token re-check elsewhere) so a bare pid is never signalled.

  • Proxy slugs and worktree names match case-insensitively (#858) — @jweslley. Hostnames are case-insensitive and browsers lowercase the Host header, but the proxy compared slugs and worktree prefixes exactly, so a worktree directory such as Feature-A silently served the main checkout instead of the worktree (with a 200). Lookups now fold ASCII case. Slugs or worktree prefixes that differ only by case are ambiguous and are refused rather than guessed: pitchfork proxy status shows them with status collision and no URL, no /etc/hosts entry is written for them, and a request for a colliding worktree prefix returns an error instead of the wrong checkout.

Changed

  • The README, docs site, web UI, favicons, and social previews now share a single devil-and-pitchfork logo (#860) — @jdx.

New Contributors

Full Changelog: v2.25.0...v2.26.0

💚 Sponsor pitchfork

pitchfork is built and maintained by @jdx, an open source developer at entire.io, the title sponsor of his open source work.

If pitchfork has a place in your development workflow, please consider becoming an individual or company sponsor. Your support funds ongoing development and helps keep pitchfork fast, free, and independent.