# reeve

```
 _ __ ___  _____   _____
| '__/ _ \/ _ \ \ / / _ \
| | |  __/  __/\ V /  __/
|_|  \___|\___| \_/ \___|
       RunCloud, scaled down — for localhost.
```

A local web development stack manager for macOS. Think RunCloud, scaled down,
localhost-only, open source: provision and manage web servers, **per-vhost PHP
versions**, SSL, and DNS — from one CLI and TUI.

Written in Rust. It does not bundle servers or PHP; it orchestrates
Homebrew-installed ones and manages them as your-user launchd services (no sudo).

![reeve](screenshot.png)

## Why

The classic macOS + Homebrew + Apache + `mod_php` setup links exactly **one**
PHP version at a time. reeve drops `mod_php` for **PHP-FPM, one master per
version**, so two vhosts can run two different PHP versions *simultaneously* —
the thing the old switcher-script approach can't do.

## Highlights

- **Per-vhost PHP version** — each vhost picks its PHP; versions run side by side.
- **Per-version PHP tuning** — `memory_limit`, upload sizes, OPcache, FPM pool,
  timezone, and a one-click **Xdebug** toggle (off/debug/profile), all without
  hand-editing `php.ini`.
- **Multiple backends** behind one abstraction: **Caddy**, **Apache**, **nginx**
  (OpenLiteSpeed is wired but unsupported on macOS — see below).
- **Run several servers at once**, on different ports, managed independently.
- **Background services** — install/start/stop **MySQL, MariaDB, PostgreSQL,
  Redis, memcached, Mailpit** via Homebrew + launchd, alongside the web stack.
- **Framework presets** — `laravel`, `wordpress`, `symfony`, `grav`, `drupal`
  set the right docroot + rewrites automatically; or point a vhost at an
  upstream dev server as a **reverse proxy** (Vite, Node, …).
- **Directory parking** (Valet-style) — park `~/Sites` and every subfolder
  auto-serves as `<folder>.test`, framework auto-detected, no per-project setup.
  Odd folder names are slugified to valid hostnames automatically.
- **Trusted local SSL** via a shared mkcert CA — `https://app.test` with no warnings.
- **Wildcard DNS** for one or more TLDs (`.test`, `.localhost`, `.lan`, …) via a
  user-run dnsmasq (no root daemon).
- **Default site** — optionally serve a catch-all from your sites root (with an
  optional framework preset), so `http(s)://localhost` works without a vhost.
  Each server can override its default root, or share the global sites root.
- **`logs` + `doctor`** — tail any service's log, and a one-shot health check of
  the whole stack (Homebrew, servers, FPM, services, DNS, certs, ports).
- **Honest, port-aware status** — `running` only when the port is actually bound;
  otherwise `loaded, not bound`, `:<port> held by <process>`, or `crashed`.
  `start`/`restart` preflight the ports and name any foreign process holding them.
- **TUI dashboard** with live status — mouse-clickable, scrollable panels (a
  dedicated **Parked** panel for parked sites) — plus a scriptable CLI on the
  same engine.
- **No sudo** for day-to-day use; servers bind 80/443 as your user via launchd.

## Requirements

- macOS (built and tested on macOS 26 "Tahoe", Apple Silicon)
- [Homebrew](https://brew.sh) — reeve will offer to install it if missing

## Installation

### Homebrew (recommended)

```bash
brew install yetidevworks/reeve/reeve
```

### From crates.io

```bash
cargo install reeve-cli
```

(The crate is published as `reeve-cli` because `reeve` is already taken on
crates.io; the installed binary is still `reeve`.)

### From source

```bash
git clone https://github.com/yetidevworks/reeve
cd reeve
cargo install --path crates/reeve
```

### Pre-built binaries

Download from [GitHub Releases](https://github.com/yetidevworks/reeve/releases).

### Updating

```bash
reeve update           # update to the latest release
reeve update --check   # just check whether a newer version exists
```

`update` detects how reeve was installed. Homebrew and cargo installs print the
matching upgrade command (`brew upgrade reeve` / `cargo install --force
reeve-cli`); a plain binary (e.g. under `~/.local/bin`) is downloaded and
replaced in place. Quit and relaunch reeve afterwards to pick up the new binary.

## Quick start

```bash
reeve init                 # detect Homebrew, scaffold config/state
reeve php install 8.3      # install a PHP version + its FPM master
reeve php install 8.4      # ...and another, running at the same time
reeve server add caddy     # add a web server (default ports 80/443)
reeve vhost add app.test --root ~/Sites/app --php 8.3 --server caddy --ssl
reeve server start caddy   # render config, mint cert, start
```

Add a database and a mail catcher, tune PHP, or use a framework preset:

```bash
reeve service add mysql && reeve service start mysql   # MySQL on :3306
reeve service add mailpit && reeve service start mailpit  # SMTP :1025, UI :8025
reeve php set 8.3 memory_limit 512M                    # tune php.ini
reeve php xdebug 8.3 debug                              # one-click Xdebug
reeve vhost add shop.test --root ~/Sites/shop --php 8.3 --server caddy --ssl --preset laravel
reeve vhost add ui.test --proxy http://localhost:5173 --server caddy --ssl   # reverse proxy
```

Or park a directory so every project in it just works, no per-site setup:

```bash
reeve park add ~/Sites --server caddy --php 8.3 --ssl   # ~/Sites/blog → blog.test, etc.
reeve apply                                             # picks up new folders anytime
```

One-time DNS + trust setup so `*.test` resolves system-wide and certs are trusted:

```bash
reeve dns setup            # installs dnsmasq + wires /etc/resolver (one admin prompt)
reeve ssl trust            # install the mkcert CA into your trust store (once)
```

`dns setup` writes the root-owned `/etc/resolver/<tld>` files itself via the
native macOS admin dialog — no manual `sudo` needed. In the TUI, press `D`.

Then `https://app.test` just works in the browser.

## TUI

Run with no arguments to open the dashboard:

```
reeve
```

```
 reeve   [.test ✓ resolving]   ● health (L logs)
┌ Servers ──────────────────────────────────────────────┐
│ › caddy    caddy   :80/:443     ● running             │
│   apache   apache  :8080/:8543  ● running             │
│   nginx    nginx   :9090/:9443  ○ stopped             │
├ Vhosts ───────────────────────────────────────────────┤
│   https://app.test   caddy   8.3   ~/Sites/app        │
├ Parked (37)  [1–4 of 37] ──────────────────────────────┤
│ › https://blog.test  caddy   8.3   ~/Sites/blog        │
│   https://shop.test  caddy   8.3   ~/Sites/shop        │
├ PHP ──────────────────────────────────────────────────┤
│ 8.3★ ● running 🐛debug    8.4 ● running               │
├ Services ─────────────────────────────────────────────┤
│ › mysql      :3306   ● running                        │
│   mailpit    :8025   ● running                        │
└───────────────────────────────────────────────────────┘
 enter start · x stop · r restart · n new · s settings · L log · q quit
```

Navigation is shared across panels; the key bar is context-aware (it shows the
keys for the focused panel).

| Key | Action |
|-----|--------|
| `↑` `↓` `←` `→` / `hjkl` | Move selection within the focused panel |
| `PgUp` `PgDn` / `Home` `End` | Page / jump through a long list (e.g. Parked) |
| mouse | Click a row to focus + select it; wheel scrolls the panel under the cursor |
| `Tab` / `Shift-Tab` | Switch panel (Servers → Vhosts → Parked → PHP → Services) |
| `n` | New — server / vhost / PHP install / service (per focused panel) |
| `e` | Edit — server (ports, backend, default site) / vhost / PHP extensions |
| `Enter` | Start server or service / restart FPM master |
| `x` · `r` | Stop · restart (Servers, Services) |
| `s` | Per-backend settings (Servers) / per-version PHP settings (PHP) |
| `X` | Cycle Xdebug off→debug→profile (PHP panel) |
| `d` | Set default PHP version (PHP panel) |
| `p` | Park a directory / manage parks (Vhosts or Parked panel) |
| `L` | View the focused item's log |
| `?` | Full `doctor` health report |
| `T` | Install the mkcert CA into your trust store (`ssl trust`) |
| `Del` / `Backspace` | Remove the focused item (with confirm) |
| `a` · `v` | Apply · validate all configs |
| `c` | Preferences (TLDs, sites root, default backend) |
| `D` | Set up wildcard DNS (admin prompt) |
| `q` / `Esc` | Quit |

The new-vhost wizard (`n` on Vhosts) covers framework **presets** and a
**reverse-proxy** target, so everything the CLI does is reachable from the TUI.

## Commands

| Command | Description |
| --- | --- |
| `init` | Detect Homebrew, scaffold config/state |
| `php install <ver>` | Install (or adopt) a PHP version + FPM master |
| `php list` / `php use <ver>` | List versions / set the default for new vhosts |
| `php cli [ver]` | Switch the terminal `php` (via the `~/.reeve/bin` shim); omit to show current |
| `php ext add\|remove\|list <ver> [name]` | Manage extensions per version (pecl) |
| `php settings <ver>` / `php set <ver> <key> <value>` | Show / tune php.ini, OPcache, FPM pool |
| `php xdebug <ver> off\|debug\|profile` | Toggle Xdebug for a version |
| `server add <backend> [--http N --https N] [--default-site] [--root <dir>] [--preset <fw>]` | Add caddy\|apache\|nginx (optionally a catch-all default site; `--root` overrides its docroot, else the global sites root) |
| `server start\|stop\|restart\|list\|remove <name>` | Manage a server (independent) |
| `vhost add <host> --root <dir> --php <ver> --server <name> [--ssl] [--preset <fw>] [--proxy <url>]` | Add a vhost |
| `vhost list\|remove <host>` | Manage vhosts |
| `service add\|start\|stop\|restart\|remove\|list <kind>` | Manage databases / redis / memcached / mailpit |
| `park add <dir> --server <name> --php <ver> [--tld T] [--ssl]` | Auto-serve every subfolder as `<folder>.<tld>` |
| `park list\|remove <dir>` | Manage parked directories |
| `apply` | Render generated configs + reconcile running services |
| `validate` | Run every backend's native config test |
| `logs [<target>] [-n N] [--follow]` | View or tail a service's log |
| `doctor` | Diagnose the whole stack (brew, servers, FPM, services, DNS, certs, ports) |
| `update [--check]` | Self-update to the latest GitHub release (`--check` only reports) |
| `ssl mint <host>` / `ssl trust` / `ssl untrust` / `ssl status` / `ssl ca` | Local certificates: mint one, install/remove the mkcert CA in the trust store, or show its state |
| `dns setup` / `dns status` | Wildcard `*.test` DNS |

## CLI PHP version

`php cli <ver>` switches the `php` on your terminal — the modern replacement for
the old `sphp` script. Instead of running `brew link` (which mutates Homebrew's
global state and fights keg-only versions), reeve keeps a shim directory of
symlinks at `~/.reeve/bin` (`php`, `pecl`, `phpize`, `php-config`, `phar`) and
just repoints them. Switching is instant and Homebrew is never touched.

The shim needs to be on the front of your PATH once. `reeve init` and
`reeve php cli` offer to add it to your shell profile automatically — or add it
by hand:

```bash
# ~/.zshrc (or ~/.bash_profile)
export PATH="$HOME/.reeve/bin:$PATH"
```

```bash
reeve php cli 8.4    # point the CLI at PHP 8.4
reeve php cli        # show the current CLI version
php -v               # PHP 8.4.x …
```

`doctor` warns if `~/.reeve/bin` isn't ahead of Homebrew on your PATH. To revert
entirely, remove that line from your profile — nothing else changes.

## How it works

`state.toml` is the single source of truth (servers, PHP versions, vhosts).
reeve **renders** native configs (Caddyfile, httpd vhosts, nginx server
blocks, FPM pools) into `generated/`, then reconciles launchd services to match.
You never hand-edit generated files — you edit state via the CLI/TUI and `apply`.

```
~/Library/Application Support/reeve/
  config.toml · state.toml · generated/ · certs/ · logs/
~/.reeve/run/            # sockets (kept space-free, short)
```

## OpenLiteSpeed

OLS has no official macOS build, and the only community Homebrew tap fails to
compile its (2022-deprecated) `admin_php` dependency against the current macOS
SDK. The backend is wired in and will activate if a working OLS install appears,
but it is **not usable on macOS today**. Use Caddy, Apache, or nginx.

## Development

```bash
cargo build              # debug build
cargo test               # unit tests
cargo clippy --all-targets -- -D warnings
cargo fmt --all
cargo install --path crates/reeve   # install the local binary
```

The repo is a Cargo workspace; the crate lives in `crates/reeve`. CI runs
fmt + clippy + tests on macOS and Linux; tagged `v*` pushes build release
binaries, publish `reeve-cli` to crates.io, and update the Homebrew tap.

## License

[MIT](LICENSE) © 2026 Andy Miller
