# Deploying on OpenBSD

## Prerequisites

- OpenBSD 7.4 or later
- OpenSMTPD running (default on OpenBSD)
- `http-smtp-rele` binary built for OpenBSD

---

## System User

Create a dedicated unprivileged user:

```sh
useradd -u 700 -d /var/empty -s /sbin/nologin \
        -c "http-smtp-rele daemon" _http_smtp_rele
```

Choose a UID that does not conflict with existing system users (`id 700` is usually free;
verify with `getent passwd 700`).

---

## File Placement and Permissions

```sh
# Binary
cp http-smtp-rele /usr/local/bin/http-smtp-rele
chmod 555 /usr/local/bin/http-smtp-rele
chown root:bin /usr/local/bin/http-smtp-rele

# Config (contains secrets — group-readable only by the daemon user)
install -o root -g _http_smtp_rele -m 640 \
    /dev/null /etc/http-smtp-rele.toml
vi /etc/http-smtp-rele.toml

# rc.d script
cp examples/openbsd-rc.d-http_smtp_rele /etc/rc.d/http_smtp_rele
chmod 555 /etc/rc.d/http_smtp_rele
chown root:wheel /etc/rc.d/http_smtp_rele
```

| Path | Owner | Group | Mode | Purpose |
|------|-------|-------|------|---------|
| `/usr/local/bin/http-smtp-rele` | root | bin | 555 | Binary |
| `/etc/http-smtp-rele.toml` | root | `_http_smtp_rele` | 640 | Config (contains secrets) |
| `/etc/rc.d/http_smtp_rele` | root | wheel | 555 | rc.d script |

---

## rc.d Service Management

```sh
# Enable and start
rcctl enable http_smtp_rele
rcctl start  http_smtp_rele

# Verify
rcctl check  http_smtp_rele

# Stop
rcctl stop   http_smtp_rele

# Restart after config change
rcctl restart http_smtp_rele

# View flags
rcctl get    http_smtp_rele
```

The rc.d script (`examples/openbsd-rc.d-http_smtp_rele`) runs the daemon as
`_http_smtp_rele` and fails pre-flight if the config file is missing.

---

## OpenSMTPD Integration

The relay submits to `127.0.0.1:25` by default. This works with the default OpenSMTPD
configuration, which listens on loopback.

Verify OpenSMTPD is running and accepting local connections:

```sh
rcctl check smtpd
echo "QUIT" | nc -q1 127.0.0.1 25
# Expected: 220 hostname.example.com ESMTP OpenSMTPD
```

If SMTP is reachable, `/readyz` returns `{"status":"ready"}`:

```sh
curl http://127.0.0.1:8080/readyz
```

### Minimum smtpd.conf

The default `/etc/mail/smtpd.conf` on OpenBSD includes:
```
listen on lo0
```

No further changes are required for localhost relay. OpenSMTPD accepts unauthenticated
connections from localhost by default.

---

## pledge and unveil

After loading the config, `http-smtp-rele` applies OpenBSD sandboxing:

1. **`unveil(NULL, NULL)`** — locks the filesystem view. No file access is possible
   after this point (config is already loaded).

2. **`pledge("stdio inet")`** — restricts syscalls to:
   - `stdio`: read/write on existing descriptors (stderr for logs)
   - `inet`: TCP connections (incoming HTTP, outgoing SMTP)

If a pledge or unveil violation occurs, OpenBSD sends `SIGABRT` and the process terminates.
The kernel logs the violation to `/var/log/messages`.

**Why this pledge set works:** The config is read before sandboxing, so `rpath` is not
needed at runtime. SMTP uses a direct TCP connection (no `dns` needed when `smtp.host` is
an IP address).

> **Important:** Set `smtp.host = "127.0.0.1"` (not `"localhost"`). The `dns` promise is
> not included; hostname resolution is not available after pledge is applied.

---

## Verifying the Deployment

```sh
# Health check
curl http://127.0.0.1:8080/healthz

# SMTP readiness
curl http://127.0.0.1:8080/readyz

# Send a test mail (replace with your key and recipient)
API_KEY=your-secret TO=you@example.com sh examples/curl-send.sh
```

Check the log:
```sh
grep http_smtp_rele /var/log/daemon
```

---

## Upgrading

1. Build or download the new binary.
2. Copy to a temporary path: `cp http-smtp-rele /usr/local/bin/http-smtp-rele.new`
3. `rcctl stop http_smtp_rele`
4. `mv /usr/local/bin/http-smtp-rele.new /usr/local/bin/http-smtp-rele`
5. `rcctl start http_smtp_rele`
6. `rcctl check http_smtp_rele`

The stop/start sequence ensures the pledge/unveil state is re-applied from the new binary.

---

## SQLite status store on OpenBSD

The SQLite status store (`store = "sqlite"`) requires additional
`unveil` and `pledge` permissions. This is handled automatically
when `db_path` is set in the configuration.

**Additional promises added to pledge:**

```
rpath wpath cpath
```

**Additional unveil path:**

```
unveil(db_path, "rwc")
```

The unveil is applied before the main `pledge` call, during the startup
restrictions phase. The parent directory of `db_path` must exist at startup:

```sh
# Create the database directory owned by the service user
install -d -o _http_smtp_rele -g _http_smtp_rele -m 750 \
    /var/db/http-smtp-rele
```

For the strictest pledge profile, use the default `store = "memory"` and
accept non-durable (restart-clears) status records.

## SIGHUP config reload

`http-smtp-rele` keeps `rpath` in the runtime pledge so that the SIGHUP
handler can re-read the configuration file. The effective read surface is
limited by `unveil`: only the config file path is unveiled with `"r"` access.

This means `rpath` does not meaningfully expand the attack surface beyond
what was already readable. This is the standard OpenBSD pattern for daemons
that support configuration reload.

To send a SIGHUP:

```sh
kill -HUP $(cat /var/run/http-smtp-rele.pid)
# or, if using rc.d:
rcctl reload http_smtp_rele
```

Reloadable settings: `ttl_seconds`, `max_records`, `cleanup_interval_seconds`,
`rate_limit.*`, `logging.*`, `mail.*` (except fields that require rebuild).

Non-reloadable (require restart): `[server]`, `[smtp]`, `[status].enabled`,
`[status].store`, `[status].db_path`, `[status].redis_url`.
