---
title: Running it
description: Set up the server and web client against your recorder.
---

You need [mise](https://mise.jdx.dev), which installs Bun, go2rtc and the
other tools at the versions the repository pins.

```sh
mise install
mise run build
```

## Your sources

The server's sources, such as a recorder, and the names you give cameras are
kept in `sources.json` in its config folder: `~/.config/kestrel` (or
`$XDG_CONFIG_HOME/kestrel`) on Linux and macOS, `%APPDATA%\kestrel` on
Windows, or the folder `KESTREL_CONFIG` names. The server writes it when you
save from the Settings page, readable only by you, and you can edit it by
hand too; restart the server after a hand edit.

The Settings page, in the app bar, lists each source as a panel that opens
to the settings its kind takes; opening one closes the one open before. Add
source picks a kind, Remove takes one out, and a `stream` source lists its
cameras with their addresses. Under each source, name its cameras: the name
shows across the app in place of the source's own name for it, such as
"Garden", or "Ch 3" for a recorder's numbered channel. Camera order, below
the sources, sets the order cameras show in across the app: drag one by its
handle, or focus the handle and move it with the arrow keys, Home and End.
Cameras left out of the order, such as a new source's, follow in the order
their sources give them. Save restarts only
the sources whose settings changed. If the settings changed since the page
loaded them, such as from another tab, the save is refused: reload the page
and make the change again.

```json
{
  "sources": [
    {
      "id": "swann",
      "kind": "raysharp",
      "name": "Swann recorder",
      "settings": { "host": "192.0.2.10", "username": "admin", "password": "…" }
    },
    {
      "id": "garden",
      "kind": "stream",
      "name": "Garden cameras",
      "settings": {},
      "cameras": [
        {
          "id": "porch",
          "name": "Porch",
          "main": "rtsp://user:…@192.0.2.20:554/stream1",
          "sub": "rtsp://user:…@192.0.2.20:554/stream2"
        },
        {
          "id": "shed",
          "name": "Shed",
          "still": "http://192.0.2.21/snapshot.jpg"
        }
      ]
    }
  ],
  "cameras": [{ "camera": "swann/1", "label": "Front door" }],
  "order": ["garden/porch", "swann/1"]
}
```

A `raysharp` source is a Raysharp-made recorder, such as a Swann DVR, with
live view, recordings, events and its disks; each channel is a camera. A
`stream` source is cameras you list yourself, each with an `id`, a `name`
and a `main` stream address, any that go2rtc plays (RTSP, RTMP, HLS, MJPEG
and more), with an optional `sub` stream for small tiles. A camera with only
a `still` address shows that picture on the Live page, a new one every few
seconds. Streams have live view only: no recordings or events.

A `reolink` source is a Reolink camera, or a Reolink Home Hub or recorder
with a camera on each channel, from its `host`, `username` and `password`.
Start the host with `https://` where its HTTP port is off. It has live view
from each camera's RTSP main and sub streams, so turn RTSP on in the
camera's network settings, and stills, motion and person, vehicle and animal
detections, read every couple of seconds, and its disks or SD card. A hub
or recorder's recordings are copied into the recordings folder like a
recorder's: each file is replayed over port 9000, Reolink's own protocol,
and converted from H.265 to H.264 with ffmpeg. A camera's own SD card
recordings are listed beside the server's, marked as on the source only and
faded on the timeline, but not copied or played: it cuts a download short,
and older firmware won't replay a file.
Record the camera on the server for footage you can play.

A `bambu` source is a Bambu Lab P1 or A1 printer's camera, from its `host`
and the LAN `accessCode` on its network settings screen. These printers
have no video stream on the network, only a picture every second or two on
port 6000, so the camera shows on the Live page as a still, a new one every
few seconds. Turn on its LAN mode, or LAN-only liveview, for the feed to
answer.

The printer's recordings come from its storage over FTPS on port 990, with
the same access code: what its camera records during prints, as the
camera's continuous footage, and the timelapse of each print, as a second
camera, such as `bambu/timelapse`. They're MJPEG, so each is converted to
H.264 with ffmpeg as it's copied or played, one at a time. Set
`KESTREL_FFMPEG` for an ffmpeg that isn't on the path; it needs `libx264`.

A source's `id` names its cameras, such as `swann/3`, so links and labels
keep working as long as it stays the same. It holds logins, so keep it to
yourself. The API never sends a password back: the page shows which are set,
and a save keeps each one you didn't change.

## The recorder's details from the environment

Before there's a sources file, the server runs one Raysharp recorder, `swann`,
from the environment. A Raysharp source whose settings leave its address or
login out falls back to the same variables, so the login can stay out of the
file. Copy `.env.example` to `.env.local`, which git ignores, and fill it in:

| Variable                         | What it is                                          |
| -------------------------------- | --------------------------------------------------- |
| `KESTREL_RAYSHARP_HOST`          | The recorder's address on your network              |
| `KESTREL_RAYSHARP_PORT`          | The control port, 9000 unless it was changed        |
| `KESTREL_RAYSHARP_RTSP_PORT`     | The RTSP port, from the recorder's network settings |
| `KESTREL_RAYSHARP_USERNAME`      | A user on the recorder                              |
| `KESTREL_RAYSHARP_PASSWORD`      | That user's password                                |
| `KESTREL_RAYSHARP_RTSP_USERNAME` | The RTSP user, when it isn't the one above          |
| `KESTREL_RAYSHARP_RTSP_PASSWORD` | The RTSP password, when it isn't the one above      |

Live video logs in to the RTSP port with the RTSP user and password when
they're set. Some recorders keep a separate RTSP password: if live players
show `wrong user/pass`, set it there.

`mise run serve:server` reads the file through 1Password's `op run`, so the
login can be `op://vault/item/field` references rather than the values
themselves. A field whose name has spaces can be referenced by its ID.

So 1Password doesn't ask each time the server starts, run `mise run auth:save`
once. It fills in the references and saves the result to
`~/.config/kestrel/recorder.env`, which only you can read, and the server
uses that file while it's there. After changing the login, run it again;
`mise run auth:forget` removes the file.

## Starting the server

```sh
mise run serve:server
```

It runs in the background through Pitchfork, at
<https://server.kestrel.localhost>, and serves the web client and its API.
It starts each source as it starts, and a recorder logs in again, waiting
longer each time, whenever the connection drops. A source that can't start,
such as one with no address, shows as not connected with the reason. `mise
run serve:server:logs` follows its log.

The server listens on `127.0.0.1:7380`. go2rtc only listens on
`127.0.0.1:1984`, and the server passes it through under `/go2rtc/`; it
starts go2rtc again whenever the cameras' streams change. go2rtc's RTSP
server stays on `127.0.0.1:1985`, since its ffmpeg sources hand their output
back through it. Set `KESTREL_HOST`
to listen on another address, such as your machine's address on your network
to use it from a phone, and `KESTREL_PORT`, `KESTREL_GO2RTC_PORT` and
`KESTREL_GO2RTC_RTSP_PORT` for other ports. The server doesn't ask for a
login, so only listen where everyone
who can reach it should see the cameras and change its sources.

## Desktop app

The desktop app runs the server itself and opens the web client in a
window, so Kestrel keeps running without Pitchfork or a terminal. It
listens on `127.0.0.1:7380` like `serve:server`, with go2rtc built in, and
takes its sources from `sources.json`. For a recorder that leaves its
address and login out there, it reads the file `mise run auth:save` keeps;
it doesn't read `.env.local`, since that needs 1Password.

Each [release](https://github.com/timmo001/kestrel/releases) has it for
Linux and Windows (x64): unpack the Setup file and run the installer in it,
or install it with mise and start it with `kestrel`:

```sh
mise use -g github:timmo001/kestrel
```

Neither is signed, so Windows asks before running it, but each release's
files have a GitHub attestation, which mise checks, or check one yourself
with `gh attestation verify <file> -R timmo001/kestrel`. Recording needs
`ffmpeg` on the path, or `KESTREL_FFMPEG`; the app doesn't bring its own.
The Windows build hasn't been tried on a real machine yet, and Start at
login is Linux only. With mise, Start at login points at that version, so
tick it again after upgrading.

Closing the window leaves it running in the tray. The tray menu opens the
window again, shows whether the server is running, how many sources are
connected and how many cameras it records, opens these docs and quits. The
docs are built into the app, so they open in your browser without a
connection, from the tray or the page's More menu. If the server stops, the
app starts it again, waiting longer each time up to 30 seconds.
Opening the app again while it runs brings its window to the front.

Tick Start at login in the tray to start it in the tray when you log in. It
adds `~/.config/autostart/dev.timmo.kestrel.desktop`; untick it to remove
that again.

The app is the always-on server, so stop the background one with `mise run
serve:server:stop` while the app runs: both use ports 7380 and 1984 and the
same recordings folder.

| Task                       | What it does                                                                      |
| -------------------------- | --------------------------------------------------------------------------------- |
| `mise run desktop:start`   | Builds the app for development and opens it                                       |
| `mise run desktop:build`   | Builds the release, in `packages/desktop/artifacts`                               |
| `mise run desktop:install` | Builds the release and installs it for you, in `~/.local/share/dev.timmo.kestrel` |

## Recordings

Recordings are local first: the web client and CLI play from the
recordings folder on the server wherever it has a copy. The server writes
its own recordings there, and copies each source's in beside them, with
why each was made, such as motion, in the file's name:
`<day>/<time>.motion.mp4`. What a source has that isn't copied yet, such
as footage older than the hours kept, is listed too, marked as on the
source, and plays from it. A source that can't hand its recordings over,
such as a Reolink camera's SD card, has them listed as on the source only,
faded on the timeline, with no Play or Download.

### Copying from sources

Every 10 minutes the server looks at what each source with recordings of
its own, such as a recorder, has from within the hours kept, and copies
what the folder doesn't have yet. It copies one file at a time, at full
quality: clips first, then continuous footage, newest first. A file is
written beside its final name and moved into place once whole. Past the
disk share, a file is only copied when an older one would be cleared out
to make room for it.

### Recording cameras

The server can record cameras itself, for those whose source keeps no
recordings of its own, such as a Reolink camera or a stream. It needs
`ffmpeg` installed. Each picked camera's main stream is read from go2rtc,
so the camera serves one connection however many watch or record it, and
saved unchanged, video only, as 5-minute MP4 files in
`recordings/<source>/<camera>/<day>/` in the state folder, or a folder you
choose, such as a share on a NAS.

### Keeping recordings

Each hour the server clears out old recordings, its own and those copied
from sources alike:

| Setting           | Default       | What it keeps                                                       |
| ----------------- | ------------- | ------------------------------------------------------------------- |
| `continuousHours` | 168 (7 days)  | Footage recorded all the time, in files with no reason or `normal`  |
| `clipHours`       | 720 (30 days) | Clips recorded for a reason, such as motion or PIR                  |
| `diskShare`       | 10            | The most of its disk the recordings folder may use, as a percentage |

Past the share, whatever is furthest through its own hours goes first: a
clip a week into its 30 days goes before continuous footage an hour into
its 7, and footage 4 days into its 7 goes before that clip. 5% of the disk
is always left free. Copying from sources follows the same order, so it
never copies an old clip only to clear out newer footage for it. A file
written to in the last two minutes is left alone. Only folders with the
server's marker are touched, so a shared folder is safe.

Pick the cameras, folder and how long they're kept in the Recording panel
on the Settings page. They're saved in `sources.json` under `recording`,
beside the rest of the settings, so every client sees the same ones, and
the server picks up a change without restarting.

After changing the server, restart it with `mise run serve:server:restart`.
If it doesn't come up without a saved login, `op run` may be waiting for
1Password to be unlocked.

## Settings

| Variable                   | Default                  | What it sets                                       |
| -------------------------- | ------------------------ | -------------------------------------------------- |
| `KESTREL_HOST`             | `127.0.0.1`              | The address the server listens on                  |
| `KESTREL_PORT`             | `7380`                   | The server's port (`PORT` too, for Pitchfork)      |
| `KESTREL_GO2RTC_PORT`      | `1984`                   | go2rtc's port                                      |
| `KESTREL_GO2RTC_RTSP_PORT` | `1985`                   | go2rtc's RTSP port, on 127.0.0.1 only              |
| `KESTREL_GO2RTC`           | `go2rtc`                 | The go2rtc binary; the desktop app uses its own    |
| `KESTREL_FFMPEG`           | `ffmpeg`                 | The ffmpeg binary, for recording cameras           |
| `KESTREL_WEB`              | `packages/web/dist`      | The built web client; the desktop app uses its own |
| `KESTREL_CONFIG`           | `~/.config/kestrel`      | Where `sources.json` is kept                       |
| `KESTREL_STATE`            | `~/.local/state/kestrel` | Where push subscriptions and recordings go         |
