Setup, teardown and doctor
Every step that needs root lives in one command, hallway setup, so the daemon never needs privileges. You only need
it for fronted mode: direct mode, the default, needs no setup at all.
hallway setup
Section titled “hallway setup”v1 Run it once, when you want fronted mode:
hallway setup --print # show the full plan; run nothinghallway setup # fronted mode on *.localhosthallway setup --domain myname.internal # under your own domain insteadThe plan is a numbered list, and each step is tagged with who does it:
[Hallway]: Hallway’s own files and user-level jobs, as you.[you]: on macOS,brew install caddy(anddnsmasqfor a custom domain) if they’re missing, as you.[sudo]: a privileged change. The exact command is shown; a file to be installed as root is first written to~/.hallway/generated/setup/as you, shown in full, then copied into place withsudo install.
For fronted mode on localhost, it:
- installs Caddy if it is missing (macOS, with Homebrew; on Linux it stops and says how to get a release binary);
- writes Caddy’s configuration (
generated/caddy.json) and the fallback page, and checks them withcaddy validate; - starts Caddy as you, as the user-level job
hallway.caddy, on127.0.0.1:4443and:4480, with its admin API on a private socket; - [sudo] trusts Hallway’s local certificate authority;
- [sudo] redirects loopback ports 443 and 80 to Caddy’s 4443 and 4480;
- sets
mode: frontedinconfig.yaml; - checks that
https://hallway.localhostanswers through the redirect, with Hallway’s certificate.
With --domain, it also writes and starts Hallway’s own dnsmasq (as you) and adds one more [sudo] step, the
resolver entry for that domain. See Custom domains.
The rules it follows:
- Each privileged command is printed with a one-line reason, then run with
sudo, so you see exactly what happens;sudoasks for your password as usual. - It is idempotent. Re-running it checks the real state (the root in the trust store, the installed files’ exact contents, a TLS handshake on port 443) and only does what is missing; already-done steps say so. If a command fails, the run stops there, and running setup again carries on.
- It refuses before running anything when the domain is
.local, a TLD on browsers’ HSTS preload list (.dev,.app, …),.exampleor.invalid; when anything else holds port 443 or 80, or Caddy’s or dnsmasq’s ports (it names the process, its PID and command line); when a helper it needs is missing and can’t be installed; and on Linux, when there is no systemd, nonftor no system CA store it recognises. It warns about a real or made-up TLD, a Caddy older than 2.7, and another Caddy’s admin API on port 2019.
The security model
Section titled “The security model”Serving https://slides.localhost means something must answer on 127.0.0.1:443, which only root may bind. The
obvious answer, a Caddy running as root, has a problem: anything that can reach Caddy’s admin API can reconfigure it,
for instance to serve files only root can read. A root Caddy would turn any malicious install script on your machine
into a privilege escalation. So Hallway doesn’t run one.
-
Caddy never runs as root. It runs as you, as a LaunchAgent or a systemd user unit, on loopback ports 4443 and 4480, which need no privileges. It can do nothing you couldn’t already do.
-
Its admin API is a Unix socket in your data home (
state/caddy/admin.sock) with mode 0600, never a TCP port. Only your own processes can reach it, and since Caddy holds no privilege, reaching it gains them nothing. -
The only privileged changes are fixed, and read nothing you could rewrite later:
- The redirect of loopback ports 443 → 4443 and 80 → 4480. On macOS, a pf anchor (
com.apple/hallway, in/etc/pf.anchors/hallway), loaded at boot by thehallway.pfLaunchDaemon; macOS’s own/etc/pf.confalready evaluatescom.apple/*anchors, so it is never edited. On Linux, an nftables table of Hallway’s own (inet hallway, in/etc/hallway/redirect.nft), applied at boot byhallway-redirect.service. - Trusting Hallway’s local certificate authority: in the System keychain on macOS, in the system CA store on Linux (plus your own NSS database, for Chrome, without sudo).
- For a custom domain only, the resolver entry:
/etc/resolver/<base>on macOS, asystemd-resolveddrop-in on Linux, pointing that one domain at Hallway’s dnsmasq, which also runs as you.
Each is printed and run through
sudo. After that, adding, changing or removing an app never needs sudo again: the daemon reconfigures Caddy through its socket, as you. - The redirect of loopback ports 443 → 4443 and 80 → 4480. On macOS, a pf anchor (
Rejected along the way: a root Caddy with its admin API on TCP or on a user-only socket (your own processes could
still escalate), a root Caddy with no admin API (sudo for every new app), Caddy as you on 0.0.0.0:443 (every app
exposed to your network), and on Linux setcap on the Caddy binary (whoever reaches its API could bind any privileged
port). See No root in the daemon.
Without any redirect
Section titled “Without any redirect”If you set proxy.publicPort to the same value as proxy.httpsPort (4443) in config.yaml, setup installs no
redirect: links then carry the port (https://slides.localhost:4443). Trusting the certificate still needs sudo once.
See Fronted mode without the redirect.
What it changes outside the data home
Section titled “What it changes outside the data home”| macOS | Linux | |
|---|---|---|
| Caddy, as you | ~/Library/LaunchAgents/hallway.caddy.plist | ~/.config/systemd/user/hallway-caddy.service |
| dnsmasq, as you (custom domain) | ~/Library/LaunchAgents/hallway.dnsmasq.plist | ~/.config/systemd/user/hallway-dnsmasq.service |
| The redirect (sudo) | /etc/pf.anchors/hallway, /Library/LaunchDaemons/hallway.pf.plist | /etc/hallway/redirect.nft, /etc/systemd/system/hallway-redirect.service |
| The trusted root (sudo) | the System keychain | the system CA store (e.g. /usr/local/share/ca-certificates/hallway-local-ca.crt); your NSS database, as you |
| The resolver (sudo, custom domain) | /etc/resolver/<base> | /etc/systemd/resolved.conf.d/hallway.conf |
Caddy’s configuration, certificates and state, dnsmasq’s configuration, and the files staged for sudo all stay in
the data home.
hallway teardown
Section titled “hallway teardown”v1 Undo it any time:
hallway teardown --print # show what it would remove; run nothinghallway teardownhallway teardown --uninstall-packages # also uninstall Caddy and dnsmasq from HomebrewIt removes only what it finds: the resolver entry, the dnsmasq job, the redirect, the trusted root and the Caddy job,
with sudo for the privileged ones, then sets mode: direct. The dashboard keeps working at
http://localhost:4255. Homebrew packages are left alone unless you ask; on Linux, --uninstall-packages only
reminds you to use your package manager. The certificate authority stays in the data home, unused, so a later setup
can trust the same root again.
hallway doctor
Section titled “hallway doctor”hallway doctorhallway doctor --jsonRead-only diagnostics. Each check prints pass, warn or fail with a suggested fix.
v0 The daemon, your data and your services:
- the daemon is running, its port is free or its own, and its service (launchd job or systemd user unit) is loaded;
- on Linux, whether lingering is on, so services keep running without a login session;
- the data home is readable, and lists any YAML parse or validation errors;
- two declared apps on one port;
- apps listening on
0.0.0.0, which exposes them to your network; - managed-service jobs that are missing or orphaned, whose
PATHcontains per-shell version-manager directories or entries that no longer exist, or whose Node version has been uninstalled (see the PATH trap).
v1 Fronted mode, when it is on:
- Caddy: its version, its job, its admin socket answering and private, and the configuration it is running matching
generated/caddy.json; - who holds Caddy’s ports and ports 80 and 443 (process, PID and command line), if it isn’t Caddy;
- a TLS handshake through the redirect that verifies against Hallway’s root, and the HTTP-to-HTTPS redirect;
- the root trusted, with a note for Firefox;
*.localhostresolving (a warning only: browsers resolve it themselves);- for a custom domain: its TLD (delegated or made up, checked against IANA’s list), dnsmasq running and bound to
loopback only, the resolver entry in place, and both
<base>and a name under it resolving to your machine.