Linux
Same CLI. No window.
Linux runs the headless supervisor in cli/. Do not install the SwiftUI app there. A portly command starts a loopback daemon on 127.0.0.1:7737 if needed, then talks to it. Agents get the same commands as macOS.
Install
Needs Go 1.22+. The binary is the supervisor. Put it on PATH, then any later portly call can start the daemon.
$ git clone https://github.com/Melvynx/portly.git $ cd portly/cli $ go test ./... $ go build -o portly . $ sudo install -m 755 portly /usr/local/bin/portly ✓ portly 0.1.12 ✓ API on 127.0.0.1:7737
Cross-compile from another host with GOOS=linux GOARCH=amd64 go build -o portly .
Stay up across logins
forever writes a systemd user unit and enables it. No LaunchAgent. If systemctl --user is missing, the command fails instead of silently installing the wrong thing.
$ portly forever enable --json $ portly forever status --json $ loginctl enable-linger "$USER"
Linger matters on a headless VPS: without it, the user systemd instance dies when the SSH session ends. Also export XDG_RUNTIME_DIR=/run/user/$(id -u) in non-login agent environments so systemctl --user can talk to the bus.
What agents should run
Inspect first. Reuse a healthy managed server. One-off work is a temporary job, not nohup and not &.
$ portly status
$ job_id="$(portly temp 'pnpm test' --path /app --timeout 30m)"
$ portly wait "$job_id"
$ portly add-project --name app --path /app --json
$ portly add-server --project app --name web \
--command 'pnpm dev' --port 5173 --start --jsonstatusActive servers and problems. --details / --json when you need more.temp / waitOne-off job. temp returns an ID; wait prints logs and the real exit code (124 on timeout).add-project / add-serverLong-lived work. --start launches it under the daemon.start / stop / restartOne server, a project, or --all.logsCaptured output. --tail N.take-overStop the stray listener on the configured port, then start Portly's command.port / kill-portWho holds a port. SIGTERM only when you ask.foreversystemd --user unit. enable | status | disable.openSucceeds with a no-UI message. There is no window.quitStops every managed server and the daemon.
temp returns immediately. wait exits with the process code. A timeout kills the process group and exits 124. Completed temp metadata stays about an hour and is never written to config.json.
What changes on Linux
- No app, no menu bar, no Sparkle.
openis a documented no-op. foreveris systemd user, not launchd.- Shell is
$SHELL -lcwhen it is an absolute executable, otherwise/bin/bash -lc. - PTY via creack/pty, with a pipe fallback if PTY setup fails.
- Do not run the macOS app and this daemon on the same host. Both claim
127.0.0.1:7737.
Paths and the API
- Config:
~/.config/portly/config.json - Logs:
~/.config/portly/logs/ - Override the home with
PORTLY_HOME - API: loopback only, default port
7737. It can spawn processes, so it never binds to the network.
Put the same Development servers rule you use on macOS in the repo AGENTS.md. On a VPS, point agents at /usr/local/bin/portly, not Portly.app.