Skip to content

Deployment

june is webhook-driven. Linear delivers a webhook → the orchestrator spawns a runner → the runner posts a comment → the issue moves state. If the host is asleep when the webhook fires, the request is dropped or retried on Linear’s schedule, and the user-facing latency is bad. Async tools want an always-on host.

This page covers where to run june and how to keep the host awake.

TL;DR

HostBest forNotes
Linux VPS (Hetzner, DO, Linode)Cheapest stable option. €4–8/mo.jun install-service writes a systemd --user unit. Reverse proxy or tailscale funnel for ingress.
Dedicated Mac mini at homeFree if you already own one. Closest to Claude Code’s UX.Auto-login + pmset settings below. Cloudflare Tunnel or Tailscale for ingress.
Hosted Mac (Scaleway, MacStadium, AWS mac1.metal)When you need macOS-specific runners but no hardware at home.Pricier than Linux. Same setup as a local Mac.
Your laptop, lid openTrying it out. Demo days.Works while awake and on power. Lid-close = sleep = dropped webhooks unless you do clamshell.

The orchestrator itself is tiny (no native deps, runs on Node 22.11+ with built-in SQLite). Pick the host on cost and tunnel preference, not on june’s resource footprint.

Why always-on matters

A webhook is a one-way push from Linear to your POST /webhook/linear. Linear’s delivery policy retries on connection failures, but:

  • Sleeping macOS / Linux hosts don’t answer until they wake up — which can be minutes after the webhook fires (or never, if Wake-on-LAN isn’t configured).
  • Failed deliveries eventually get marked stale by Linear; the issue’s first response is delayed by the retry window.
  • In-flight runs (a Claude session mid-task) die when the host sleeps. The orchestrator’s reconciliation will mark them Blocked on next boot — but you’ve lost the work.

Keep the host awake; you get the snappy “comment → 5-second-later reply” experience Linear users expect from a synchronous control plane.

The cheapest and easiest unattended host. Any cloud Linux box with Node 22.11+ works:

Terminal window
# On the VPS (assumes a non-root user `june` with sudo)
sudo apt install -y nodejs npm # or use nvm / fnm / nodesource for 22.11+
npm i -g june # see README "Private install" while the repo is private
jun setup # walk the wizard
jun install-service # writes ~/.config/systemd/user/june.service, enables --now
systemctl --user status june

jun install-service on Linux writes a systemd --user unit. After install:

  • systemctl --user start|stop|restart june — control the daemon.
  • journalctl --user -fu june — follow logs (or jun logs -f).
  • loginctl enable-linger <user> — make the unit survive logout (required on most distros).

Ingress: any of caddy / nginx / traefik reverse-proxying 127.0.0.1:8787 with a Let’s Encrypt cert, or tailscale funnel 8787 if you already use Tailscale. Cloudflare Tunnel works too but is overkill on a public host.

Mac mini (or any always-on Mac)

If you already own a Mac you can leave plugged in, this is the lowest-cost option. The mini doesn’t have a battery to worry about; just configure power + auto-login and forget it.

1. Power settings — never sleep on AC

System Settings → Battery → Options (or older macOS: System Preferences → Energy Saver). Set:

  • Prevent automatic sleeping when the display is off: ON.
  • Wake for network access: ON (lets WoL / mDNS find the box).
  • Start up automatically after a power failure: ON (Mac mini only — laptops don’t expose this).

Or do it from the terminal once and never touch the GUI again:

Terminal window
sudo pmset -c sleep 0 # never sleep on AC power
sudo pmset -c disksleep 0 # don't spin down disks
sudo pmset -c displaysleep 10 # screen off after 10 min (saves the panel; CPU stays awake)
sudo pmset -c womp 1 # wake-on-network
sudo pmset -c autorestart 1 # auto-restart after a power blip (Mac mini)
sudo pmset -c standby 0 # disable deep sleep
sudo pmset -c hibernatemode 0 # no hibernation

Verify:

Terminal window
pmset -g # current settings
pmset -g assertions # what's preventing sleep right now

2. Auto-login (so launchd agents come back after a restart)

jun install-service writes a launchd user agent (lives in ~/Library/LaunchAgents/). User agents only run for a logged-in user — so after a power blip, the Mac needs to log back in by itself.

System Settings → Users & Groups → Automatic login → pick the june user. (On Apple Silicon you may need to first disable FileVault to enable auto-login; trade-off is yours.)

3. Caffeinate as a safety net

If you don’t want to touch pmset and your only goal is “keep this Mac awake for the next 8 hours of demo,” caffeinate is the one-liner:

Terminal window
caffeinate -dimsu & # disables display/idle/system sleep; survives until you `kill` it
caffeinate -dimsu -t 28800 # for 8 hours, then stops

This is the “I just want this to work for now” path. Use pmset for the permanent setup.

4. Laptops: clamshell mode

If the host is a MacBook Pro you want closed:

  1. Plug it in.
  2. Connect an external display, keyboard, and mouse.
  3. Close the lid. macOS keeps running and uses the external display.

Without an external display, macOS sleeps when you close the lid even on AC. There’s no system-supported “lid-closed without external display” mode — third-party tools (Amphetamine, InsomniaX) override the lid-close signal, but they’re brittle across macOS updates. Just don’t close the lid, or use clamshell mode, or use a mini.

5. Ingress from a home network

You need a public URL to give Linear’s webhook. Options, in order of how much I’d recommend them:

  • Cloudflare Tunnel (recommended). Install cloudflared, run as a launchd service yourself, point Linear at the tunnel URL. Stable as long as cloudflared is running. Free.
  • Tailscale Funnel. If you already use Tailscale: tailscale funnel 8787https://<mac>.<tailnet>.ts.net. Free, stable URL.
  • Port-forward on your router + dynamic DNS. Works, but exposes your home IP, and you have to manage TLS yourself.

jun setup’s tunnel step covers the first two.

Hosted Mac

When you need macOS-specific runners (e.g. an Xcode-touching Claude session) but don’t have hardware at home:

  • Scaleway Apple silicon — bare-metal Mac mini M1/M2, hourly billing. Cheapest of the three.
  • MacStadium — enterprise-priced; monthly contracts.
  • AWS EC2 mac1.metal / mac2.metal — minimum 24-hour billing units; you pay for whole days even if you used 30 min.

Treat these like a remote Mac mini — same pmset + auto-login setup, except pmset settings often persist across the host’s life because the provider images them that way. SSH in, run jun setup, run jun install-service. Use Tailscale or Cloudflare Tunnel for ingress; the provider’s public IP is usually fine too.

Your laptop

This is the “I’m just trying it” tier. Things to know:

  • june runs fine on a MacBook while you’re using it. Lid open, plugged in, screen awake = the orchestrator answers webhooks.
  • The moment the laptop sleeps (lid close, idle timeout, screen off) the webhook server stops answering. Linear’s retries may eventually land — or may not.
  • If you run unattended jobs on a laptop, run caffeinate -dimsu & before walking away, and don’t close the lid.
  • Use jun install-service if you want the orchestrator to come back automatically after a restart. Skip it if you want to babysit.

If you find yourself using caffeinate & every day, you’re past the laptop tier — get a Mac mini or rent a Linux VPS.

Verify it stays up

After whichever setup you picked:

Terminal window
jun status # is it running? listening where? last webhook seen when?
jun doctor # full re-probe; safe to wire into cron / launchd as a heartbeat

A useful smoke check on a remote host: from your laptop, hit the public webhook URL’s /health:

Terminal window
curl -fsS https://<your-public-url>/health
# {"status":"ok"}

If /health is reachable from the public internet, Linear’s webhook will reach the orchestrator too. The rest is jun doctor confirming the in-process state machine.