Run PurePoint on a Linux server
The PurePoint daemon (pu-engine) can run on any Linux machine you can reach over SSH: a cloud VM, a home server, a build box. It owns that machine's projects, worktrees and agents, and keeps running when you disconnect. You drive it from another computer with pu --machine or the PurePoint app's Machines section (see Machines over SSH), or with pu in an SSH session on the server.
1. Install
On the server:
curl -fsSL https://raw.githubusercontent.com/2witstudios/PurePoint/main/scripts/install.sh | sh
This puts static pu and pu-engine binaries (x86_64 or aarch64) in ~/.pu/bin. Set PU_VERSION=1.2.3 to pin a release. Before installing, the script checks the release signature against the PurePoint key it embeds (using minisign, or OpenSSL 3, which most distributions ship), and, if the GitHub CLI is installed and signed in, the build provenance with gh attestation verify. If it can't verify the download it stops; set PU_INSTALL_SKIP_VERIFY=1 to install anyway (it warns). A failed check always stops the install. Add ~/.pu/bin to your PATH in ~/.profile, or call it by full path.
To pair a phone or iPad directly with this server, also install phone access:
curl -fsSL https://raw.githubusercontent.com/2witstudios/PurePoint/main/scripts/install.sh | PU_WITH_PHONE=1 sh
pu phone install --now
Phone access adds the Point Guard bridge, with its own copy of Node.js, in ~/.pu/pointguard/<version>/. It needs glibc 2.28 or newer, so it doesn't run on Alpine or other musl systems. On those hosts the installer says so, and pu and pu-engine still install and work.
The payload is verified like pu itself: its SHA-256, then the release signature (with minisign or OpenSSL 3) or its build provenance (with a signed-in gh). Installation stops if a check fails or none can run, unless you set PU_INSTALL_SKIP_VERIFY=1. An existing version directory is never modified. Set PU_PHONE_ARCHIVE to install a local payload tarball.
Also install git and the agent CLIs you use (claude, codex, …) as that same user, and sign in to them.
To build from source instead, run cargo install --path crates/pu-cli and cargo install --path crates/pu-engine.
2. Keep the daemon running
pu daemon install --now
sudo loginctl enable-linger "$USER"
pu daemon install writes a systemd user unit (~/.config/systemd/user/pu-engine.service) and enables it. It starts the daemon only with --now; without it, run pu daemon start or wait for the next boot or login. It also tells you if lingering is off. Without lingering, systemd stops your user services when your last SSH session ends and doesn't start them at boot.
| Command | What it does |
|---|---|
pu daemon status | Daemon health, plus the service state |
pu daemon start / stop | Start or stop the daemon; through systemd when the unit is installed |
pu daemon uninstall | Disable and remove the unit |
journalctl --user -u pu-engine | Daemon logs when it runs under systemd |
You can also skip systemd. Any pu command starts the daemon on demand, detached from your shell (in its own session, ignoring SIGHUP), so it survives you logging out. It won't restart after a crash or reboot, though. If logind is configured with KillUserProcesses=yes, only a lingering systemd unit survives logout.
3. Register projects
cd ~/src/my-repo && pu init
Each project is registered with the server's daemon, so every client of that daemon sees it.
4. Connect from your computer
On your Mac (or any machine with pu and SSH access to the server):
pu machine add server me@server.example.com # or an ~/.ssh/config alias
pu machine status server
pu --machine server --project ~/src/my-repo status
The client forwards its connection over SSH to ~/.pu/daemon.sock on the server, using your own ~/.ssh/config, keys and agent. It never starts the remote daemon on its own; if the daemon is stopped, run pu machine start server. In the PurePoint app, add the same machine from the sidebar footer; its projects then appear under MACHINES.
5. Pair a phone or iPad (optional)
With phone access installed (step 1), the phone talks to this server directly over your tailnet. No Mac is involved. Both devices need Tailscale connected.
pu phone install --now # systemd user service pu-pointguard.service
pu phone login # list providers; then: pu phone login <provider>
pu phone restart # apply the sign-in
pu phone model # list models; then: pu phone model <provider>/<model>
pu phone pair # show a one-time QR code in this terminal
In the PurePoint app, open Connections → Pair a computer → Scan pairing code. If the terminal can't show the QR (it's too narrow, or output isn't a terminal), or you pass --text, pu phone pair prints a purepoint-pair: code instead. Copy it to the phone and use Paste code. The code works once and expires within 2 minutes (--ttl, at most 300 s). Leaving pu phone pair early in any way (Ctrl-C, closing the SSH session) revokes it.
A new device pairs with full access: agents, terminals, workspace management and Point Guard chat. Lower it per device:
pu phone devices # names, grants, chat level
pu phone grant iPhone --workspace off --terminals view # watch agents and read Point Guard chat
pu phone grant iPhone --terminals off --workspace off # no access
pu phone grant iPhone --terminals control --workspace on # full control again, including chat
pu phone rename iPhone "Work phone"
pu phone revoke "Work phone" # closes its connections now
Point Guard chat can run pu on this server, so sending chat needs the broadest grant (terminals Control plus workspace management). Machines this server reaches with pu machine appear on the phone under the same grants.
How it stays private:
- The phone listener binds only this host's Tailscale address, confirmed by both
tailscale ip -4and the network interfaces. It never binds a public or LAN address. Without Tailscale it doesn't listen at all, and it starts by itself once Tailscale comes up. - In userspace-networking mode (containers, some VMs), it listens on loopback and needs
tailscale serve --bg --tcp 8787 tcp://127.0.0.1:8787. That is TCP passthrough, so the phone still checks the pinned certificate end to end.pu phone statusprints the exact command when one is needed. - Every connection must present the host certificate pinned in the QR code and a per-device credential. Every message is checked against that device's current grant.
- The owner channel is a Unix socket in a private
0700directory under~/.local/state/purepoint/point-guard/, and it still needs a token.purefuses to use it unless the directory, socket and token are private to you. There is no admin TCP port. - The service runs with an explicit environment (
NODE_OPTIONS,LD_PRELOADand similar inherited variables are removed),UMask=0077andNoNewPrivileges=yes.
On hosts without systemd, run pu phone run under your own supervisor instead.
Upgrading
Re-run the installer (with PU_WITH_PHONE=1 if you use phone access), then restart the daemon so it uses the new binary. Restarting suspends running agents, and they resume when the daemon comes back:
pu daemon stop && pu daemon start
pu phone restart # phone access: waits until Point Guard is idle, then moves to the new version
Client and server must speak the same protocol version. pu daemon status on the server shows the daemon's, and pu machine status on a client reports a mismatch as incompatible. Upgrade PurePoint on both sides together.
Troubleshooting
no usable pu-engine:pulooks beside itself, then in~/.pu/bin. Keep both binaries together (install.sh,cargo installand the macOS app all do). The error lists why each candidate was refused. To use another build, setPU_ENGINE_PATHto its path. The binary must be owned by you (or root) and not writable by group or others.- Agents can't find
claude/codex: the daemon takes agent environments from your login shell ($SHELL -li). Make sure yourPATHis exported from~/.profile(or~/.bash_profile), not only from~/.bashrc. systemctl --userfails with "Failed to connect to bus": you're in a session without a user manager (e.g.su). Log in over SSH directly as that user.pu phonesays the payload is for another pu version: phone access runs only with the exactpuit was released with. Re-run the installer withPU_WITH_PHONE=1, thenpu phone restart.pu phone pairsays there is no tailnet address: runtailscale up(and thetailscale servecommand frompu phone statusin userspace mode). The listener binds on its own within a minute.- Point Guard stops when you log out: enable lingering with
sudo loginctl enable-linger $USER. - Logs:
journalctl --user -u pu-pointguard. They never contain credentials, pairing codes, prompts or terminal content.