# Server setup

`emma serve` runs the whole server (web app, API, downloads for other machines) as one process with a
SQLite database. This page covers the address people open, HTTPS, the settings, running it as a service,
backups and updates.

## Address and HTTPS

`--url` is the address people open; the install, invite and sign-in links use it. Give it once; it is saved
in `~/.emma/config.toml`.

- **An address alone** with `--port`, like `--url 203.0.113.5 --port 8000`: emma opens it itself over plain HTTP
  on that port, and the links use `http://203.0.113.5:8000`. This is the usual case (`--port` defaults to
  `10030`). The port goes in `--port`, not in the address, as with Grafana's `domain` and `http_port`.
- **An address starting with `http://` or `https://`** is used exactly as given, for when something in front (a
  proxy, a load balancer) takes it and passes requests on to emma's port, `--port` (default `10030`). `http://host` with no port means port 80, as in any address: if nothing answers
there, `emma serve` warns once it is running and says which `--url` to use.

| People open | Command |
|---|---|
| this machine, directly | `emma serve --url server --port 10030` |
| your HTTPS proxy, which sends to emma's 10030 | `emma serve --url https://emma.example.com` |
| a proxy on another port | `emma serve --url https://emma.example.com:8443` |
| emma itself over HTTPS | `emma serve --url https://emma.example.com --letsencrypt` (port 443) |
| this machine, on another port | `emma serve --url server --port 8080` |

More on each setup:

| Setup | Command | Needs |
|---|---|---|
| This machine only | `emma serve` | nothing |
| Public IP, plain HTTP | `emma serve --url 203.0.113.5 --port 10030` | TCP 10030 open (on AWS: the security group) |
| Your domain, emma does HTTPS | `emma serve --url https://emma.example.com --letsencrypt` | DNS pointing here; port 443 open and usable by this user |
| Only an IP, emma does HTTPS | `emma serve --url https://203-0-113-5.sslip.io --letsencrypt` | port 443 open and usable (sslip.io turns the name back into the IP) |
| Behind your HTTPS proxy (nginx, Caddy, a load balancer) | `emma serve --url https://emma.example.com` | the proxy forwards to port 10030 (below) |
| Your own computer, through a tunnel (Tailscale, ngrok, Cloudflare Tunnel) | `emma serve --host 127.0.0.1 --url https://<tunnel address>` | the tunnel pointing at port 10030 (see [Quick start](quick-start.md)) |

`203.0.113.5` and `emma.example.com` stand for your own IP address and domain.

**Without HTTPS** emma works (sign-in, messages, files, agents), but browsers allow voice input, passkeys,
the home-screen app and notifications only over HTTPS. HTTPS is also recommended for security.

**With `--letsencrypt`** emma gets and renews the certificate itself on the first HTTPS request (it takes a
few seconds). Only port 443 is used for the certificate. The plain port (10030) then answers this machine
only and sends everyone else to HTTPS. To let a non-root user use port 443:

- systemd service: `AmbientCapabilities=CAP_NET_BIND_SERVICE` (see below)
- otherwise: `sudo setcap cap_net_bind_service=+ep "$(readlink -f ~/.local/bin/emma)"` (again after each update)

**Behind a proxy**, pass the original scheme and client address, and do not buffer (the web app keeps a live
event stream open; agents hold a request for up to 55 seconds):

```nginx
location / {
    proxy_pass http://127.0.0.1:10030;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_buffering off;
    proxy_read_timeout 120s;
    client_max_body_size 120m;
}
```

Caddy needs only `reverse_proxy 127.0.0.1:10030`. Then set `trusted_proxies` (below) to the proxy's address
so the sign-in limit counts each browser, not the proxy.

## Settings

Each setting has the same name in three places, and the first one found wins:

1. an option: `emma serve --max-file-mb 200`
2. an environment variable: `EMMA_MAX_FILE_MB=200`
3. `~/.emma/config.toml` (`EMMA_HOME` moves `~/.emma`): `max_file_mb = 200`

Restart `emma serve` after changing the file.

| Setting | Default | What it does |
|---|---|---|
| `url` | the address the admin first used | where people open emma; install, invite and sign-in links use it |
| `letsencrypt` | `false` | emma gets a Let's Encrypt certificate for `url`'s name and serves HTTPS itself |
| `port` | `10030` | port the server listens on |
| `host` | `0.0.0.0` | address the server binds (`127.0.0.1`: this machine only) |
| `data_dir` | `~/.emma/data` | database and attachments |
| `max_file_mb` | `100` | largest attachment, MB |
| `files_max_gb` | `50` | all attachments together, GB |
| `trusted_proxies` | none | proxies (IPs or CIDRs, comma-separated) whose `X-Forwarded-For` names the browser |
| `language` | `en` | `ko` for Korean CLI output and server notices (the web app has its own setting) |
| `auto_update` | `true` | follow new versions |
| `stt.*` | none | voice input, see [Voice input](voice-input.md) |

Secrets (`stt.api_key`) are read only from the file or the environment, never from options, since options
show up in the process list.

Example `~/.emma/config.toml`:

```toml
url = "https://emma.example.com"
letsencrypt = true
max_file_mb = 200
```

## Keep it running

emma does not detach. Run it in tmux, or as a systemd service so it starts with the machine:

```ini
# /etc/systemd/system/emma.service
[Unit]
Description=emma
After=network-online.target
Wants=network-online.target

[Service]
User=alice
ExecStart=/home/alice/.local/bin/emma serve
Restart=always
# only with --letsencrypt (port 443):
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target
```

```sh
sudo systemctl daemon-reload
sudo systemctl enable --now emma
journalctl -u emma -f
```

Replace `alice` with your user. Without root, a user service works the same way
(`~/.config/systemd/user/emma.service` without `User=` and `AmbientCapabilities=`, `WantedBy=default.target`,
`systemctl --user enable --now emma`, and `loginctl enable-linger` so it runs while you are logged out).

## Updates

The server checks https://get.emma.now for a new version a minute after it starts and then every hour. Each
release says when servers switch to it (usually a day after it is published; a security fix at once), and
the server does so by itself: the new build is verified (an ed25519 signature checked against keys built into
emma, and the file's sha256), installed next to the current one, and the server finishes the requests in
flight and restarts into it. It never moves to an older version. The CLI on every machine follows its
server's version on its next command. Set `auto_update = false` to stay put.

## Backups

While the server runs:

```sh
emma admin backup --local --out ~/emma-backup-2026-10-26
```

This copies the database and attachments into a new folder. To restore, stop the server and point
`data_dir` at that folder (or copy it to `~/.emma/data`).

## Managing members from the server machine

On the server machine, `emma admin <command> --local` works on the database directly, without a token:
`requests`, `approve --name`, `members`, `add-person --name --role`, `login-link --name` (a one-time sign-in
link when someone lost theirs), `revoke-tokens --name`, and more. `emma help` lists them all.

## Security notes

- Coding agents on one machine usually run as the same OS user, so each can read the others' emma tokens
  (`~/.emma/agents/`) and files, and act as them. On machines that matter, give each agent its own container
  (e.g. Docker) or user account.
- Personal tokens sign people in: keep them in a password manager, not on the machines where agents run.
- `emma admin revoke-tokens --name <member>` (or Settings → Members) signs a member out everywhere at once.

## Uninstall

1. Stop the server: Ctrl-C where it runs, or for a service `sudo systemctl disable --now emma` and
   `sudo rm /etc/systemd/system/emma.service` (a user service: `systemctl --user disable --now emma` and
   remove `~/.config/systemd/user/emma.service`).
2. On the server and on every machine you added:

```sh
emma uninstall           # lists what it would remove
emma uninstall --yes     # removes the program, skill and agent permissions
emma uninstall --purge --yes   # also ~/.emma: settings, tokens and server data
```

`--purge` on the server deletes every message and file for good: take a [backup](#backups) first if you may
want them. Coding agent sessions that were waiting on emma stop getting messages; end them or tell them emma
is gone.
