Restructure README into a getting-started-first layout

Reorganize into What is OOTT?, Getting started (Docker + mobile apps),
Deeper dive (Nix flake, config options, HTTPS), and Things to keep in
mind (storage, privileges/ports). Inline a minimal oott.toml and the
compose file in the Docker quickstart, and document the missing
web_server.* options.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
rzuasti
2026-06-02 17:37:10 -04:00
co-authored by Claude Opus 4.8
parent 8df21e41b1
commit 5872fcf8db
+107 -95
View File
@@ -3,70 +3,97 @@ Easy to setup and use network device discovery and alert system
Licensed under AGPL v3. See [LICENSE](LICENSE).
* [What is it?](#what-is-it)
* [Installation & configuration](#installation-configuration)
* [Install OOTT using Docker](#install-oott-using-docker)
* [Install OOTT using NixOS flakes](#install-oott-using-nixos-flakes)
* [Configuration options](#configuration-options)
* [Network ports](#network-ports)
* [Connecting the app to the backend](#connecting-the-app-to-the-backend)
* [Storage considerations](#storage-considerations)
* [What is OOTT?](#what-is-oott)
* [Getting started](#getting-started)
* [Simple installation with Docker](#simple-installation-with-docker)
* [Using the mobile apps](#using-the-mobile-apps)
* [Deeper dive](#deeper-dive)
* [Installing OOTT as a Nix flake](#installing-oott-as-a-nix-flake)
* [Full list of configuration options](#full-list-of-configuration-options)
* [Using HTTPS and domain names with the mobile apps](#using-https-and-domain-names-with-the-mobile-apps)
* [Things to keep in mind](#things-to-keep-in-mind)
* [Storage considerations](#storage-considerations)
* [Privileges and network ports](#privileges-and-network-ports)
## What is it?
OOTT provides a service that runs behind the scenes and monitors your local network. It's most relevant features are:
* Scan network regularly using ARP probes
* Notify when a new device is found
* Notify when a device changed it's IP address or network interface vendor (based on its MAC address)
* Notify when a device came back online after a configurable period of being offline
## What is OOTT?
OOTT runs behind the scenes and monitors your local network, notifying you when something changes. Its most relevant features are:
* Scans the network regularly using ARP probes, with additional mDNS, SSDP, DHCP and (optionally) SNMP discovery
* Notifies you when a new device is found
* Notifies you when a device changes its IP address or network interface vendor (based on its MAC address)
* Notifies you when a device comes back online after a configurable period of being offline
OOTT can be installed as a NixOS module (using flakes) or as a docker image, see below for instructions on how to deploy and configure it.
You configure and browse the data it collects through a companion app available on the web, desktop, iOS and Android.
# Installation & configuration
## Install OOTT using Docker
OOTT is available on Docker Hub as a pre-built image [here](https://hub.docker.com/repository/docker/rzuasti/oott/general).
To use it I recommend using docker compose, you can find a sample compose file [here](https://github.com/rzuasti/oott/blob/main/examples/docker-compose.yml).
## Getting started
If you use our Docker Hub image and docker compose the steps you have to follow are:
1. Create a folder structure in your host
2. Create the configuration file
3. Create the `docker-compose.yml` file
4. Start the service
### Simple installation with Docker
This is the quickest way to get OOTT running. OOTT is published on Docker Hub as a pre-built image, [`rzuasti/oott`](https://hub.docker.com/repository/docker/rzuasti/oott/general), and the steps below get you to a running service with the smallest possible configuration.
#### 1. Create a folder structure in your host
You need a place to store the docker-compose.yml file, the OOTT configuration file and the database that will store the application state:
You need somewhere on the host to store the configuration file and the database (OOTT uses SQLite, so the database folder should be on local — not remote — storage):
```bash
mkdir -p /docker/oott/config
mkdir -p /docker/oott/db
```
The user that runs Docker needs read access to the config folder and read/write access to the db folder. You can use any paths you like as long as you keep the volume mappings below in sync.
You can choose whatever structure or locations you choose. Note that:
* The user that runs the docker process must have read access to the config folder and read/write access to the db folder
* OOTT uses SQLite and it doesn't really like remote access so the `db/` folder should be local to your docker host
Create a minimal `oott.toml` at `/docker/oott/config/oott.toml`:
```toml
[database]
path = "/db/oott.db" # Must be "/db/oott.db" for the Docker image (the mounted volume)
#### 2. Create the configuration file
Create the `oott.toml` file (for example at `/docker/oott/config/oott.toml` in your docker host) and populate it with your preferences. You can use the [provided example](https://github.com/rzuasti/oott/blob/main/examples/sample_oott.toml) as baseline.
[web_server]
api_key = "CHANGE_ME" # API key the app uses to talk to the backend — change this!
[notifications]
method = "pushover" # Use "none" to only log notifications instead of sending them
[notifications.pushover]
token = "" # Your Pushover application token
user_key = "" # Your Pushover user key
```
This is enough to start the service; every other option falls back to a sensible default. See [Full list of configuration options](#full-list-of-configuration-options) for everything you can tune, and the [full sample config](https://github.com/rzuasti/oott/blob/main/examples/sample_oott.toml) for a commented, all-options file.
Create a `docker-compose.yml` at `/docker/oott/docker-compose.yml`:
```yaml
name: oott
services:
oott:
image: rzuasti/oott:latest
volumes:
- /docker/oott/config:/config
- /docker/oott/db:/db
network_mode: host
```
Host networking is required so OOTT can see the multicast and broadcast traffic its scanners rely on (see [Privileges and network ports](#privileges-and-network-ports)).
Start the service from where you placed the compose file:
```bash
docker compose up -d
```
That's it. Check that everything is running with `docker ps`, and follow the logs with `docker logs CONTAINER_ID -f`.
### Using the mobile apps
The OOTT app (web, desktop, iOS and Android) talks to the backend exclusively over its REST API on port `3000`. Open the app's settings and point it at your backend's address on the local network over HTTP, for example `http://192.168.1.50:3000`, using the API key you set in `web_server.api_key`.
This is the simplest setup and needs no extra infrastructure, but it only works while your phone is on the same local network as the backend.
> [!IMPORTANT]
> The `database.path` option must always be set to `"/db/oott.db"` when using our docker image.
> On iOS, the direct HTTP connection only works when you use the backend's **private-range IP address** (`192.168.x.x`, `10.x.x.x`, `172.1631.x.x`) or a `*.local` (mDNS/Bonjour) name. A custom internal domain (e.g. `oott.mylan.com`) served over plain HTTP is blocked by iOS App Transport Security, even when it resolves to a private IP. Use the IP address directly, or set up HTTPS.
#### 3. Create the `docker-compose.yml` file
Create a docker compose file to run the container (for example at `/docker/oott/docker-compose.yml`), you can use the [provided example](https://github.com/rzuasti/oott/blob/main/examples/docker-compose.yml) as is or adjust it to match your environment.
> [!NOTE]
> The first time the iOS app reaches your backend on the local network, iOS shows a one-time "find devices on your local network" prompt. This is expected — tap Allow to continue.
#### 4. Start the service
Run `docker compose up -d` from where you placed your `docker-compose.yml` file and verify everything is running smoothly.
To use a domain name, or to reach the backend from outside the local network, set up TLS as described in [Using HTTPS and domain names with the mobile apps](#using-https-and-domain-names-with-the-mobile-apps).
You can check the applications log using `docker logs CONTAINER_ID -f`, to see the active containers you can use `docker ps`.
## Deeper dive
## Install OOTT using NixOS flakes
OOTT comes with a pre-built NixOS flake that you can integrate in your configuration. If you are not using flakes, well you should. If you still won't I guess you can use the [flake code](https://github.com/rzuasti/oott/tree/main/nix) as a baseline and write your own derivation.
### Installing OOTT as a Nix flake
OOTT comes with a pre-built NixOS flake that you can integrate into your configuration. If you are not using flakes you can use the [flake code](https://github.com/rzuasti/oott/tree/main/nix) as a baseline and write your own derivation.
To integrate the OOTT flake into your config in most cases you should do the following:
1. Add OOTT to your inputs
2. Add the OOTT module and overlay
3. Enable and setup OOTT in your system configuration
To integrate the OOTT flake into your config you generally do three things: add OOTT to your inputs, add its module and overlay, then enable and configure the service.
#### 1. Add OOTT to your inputs
In your `flake.nix` inputs section add OOTT:
**1. Add OOTT to your inputs** — in your `flake.nix` inputs section:
```nix
inputs = {
...
@@ -78,8 +105,7 @@ inputs = {
};
```
#### 2. Add the OOTT module and overlay
In your `flake.nix` modules section add the OOTT module and overlay:
**2. Add the OOTT module and overlay** — in your `flake.nix` modules section:
```nix
...
modules = [
@@ -94,8 +120,8 @@ modules = [
];
...
```
#### 3. Enable and setup OOTT in your system configuration
Finally, in your `configuration.nix` (or in an import file) enable and configure OOTT (note that you should embed the following sections in your file appropriately):
**3. Enable and configure OOTT** in your `configuration.nix` (or an imported file). Set all options through the service definition; the keys mirror the config file options listed [below](#full-list-of-configuration-options):
```nix
{
pkgs,
@@ -124,19 +150,17 @@ Finally, in your `configuration.nix` (or in an import file) enable and configure
}
```
## Configuration options
The system configuration is centralized in a single config file, you can use TOML, JSON or YAML to write it.
### Full list of configuration options
The system configuration lives in a single config file, which you can write in TOML, JSON or YAML. With Docker, TOML is recommended — the [full sample config](https://github.com/rzuasti/oott/blob/main/examples/sample_oott.toml) lists every supported option with comments. With the Nix flake, set the same options through the service definition (see above).
If you are using the provided NixOS flake you should set all the options via nix in the service definition (see above).
If you are using Docker I recommend writing the config using TOML, [here](https://github.com/rzuasti/oott/blob/main/examples/sample_oott.toml) is a sample with all the supported options.
### Options list
|Option|Sample value|Description|
|------|-------------|-----------|
|`database.path`|`/var/lib/oott.db`|Location of the system database|
|`database.path`|`/var/lib/oott.db`|Location of the system database. Must be `/db/oott.db` when using the Docker image.|
|`networking.interface`|`eno1`|Network interface to use for scans. Optional — if not set, the first non-loopback connected interface is used automatically.|
|`log.level`|`info`|Log level to use (trace, debug, info, warn, error)|
|`web_server.ip_address`|`0.0.0.0`|Address the API and web UI bind to. Use `0.0.0.0` to bind all interfaces.|
|`web_server.port`|`3000`|Port the API and web UI listen on.|
|`web_server.api_key`|`CHANGE_ME`|API key the app must present to use the backend. Always change this from the default.|
|`arp_scanner.enabled`|`true`|Whether to run the ARP scanner. The whole `[arp_scanner]` section is optional; omit it to use the defaults below. Defaults to enabled; set to `false` to turn it off.|
|`arp_scanner.wait_between_scans`|`30m`|Time to wait between each network scan (you can express it in seconds, minutes, hours, etc. as a suffix - for example: 30s, 10m, 1h)|
|`arp_scanner.sender_timeout`|`1m`|If the ARP sender process takes longer than this it will be stopped (for a class C network - 254 IPs - it should take less than a minute)|
@@ -158,47 +182,17 @@ If you are using Docker I recommend writing the config using TOML, [here](https:
|`retention.window`|`365d`|How long to retain device events and notifications. Records older than this are purged daily. Accepts duration strings (e.g. `90d`, `1y`, `6m`). Defaults to one year.|
|`device_events.deduplication_window`|`1m`|Suppress duplicate device events: if the same scanner sees the same device (same MAC and IPv4) again within this window, only one event is recorded. Accepts duration strings (e.g. `30s`, `1m`, `5m`). Defaults to one minute.|
## Network ports
OOTT binds to the following ports on the host where it runs:
### Using HTTPS and domain names with the mobile apps
To reach the backend through a domain name, or from outside the local network, put it behind a reverse proxy (nginx, Caddy, Traefik, …) that terminates TLS with a **valid certificate from a trusted CA** (for example [Let's Encrypt](https://letsencrypt.org/)), then point the app at the HTTPS URL (e.g. `https://oott.example.com`). This works on every platform — including iOS — with no further configuration.
|Port|Protocol|Configurable|Used by|
|----|--------|------------|-------|
|`3000`|TCP|Yes (`web_server.port`)|REST API and web UI|
|`5353`|UDP (multicast)|**No — fixed**|mDNS/Bonjour scanner|
|`1900`|UDP (multicast)|**No — fixed**|SSDP/UPnP scanner|
|`67`|UDP (broadcast)|**No — fixed**|DHCP scanner|
Connect using the **domain name the certificate is issued for**, not an IP address. Self-signed certificates are not supported unless they are manually trusted on the device.
> [!IMPORTANT]
> The mDNS (UDP `5353`), SSDP (UDP `1900`) and DHCP (UDP `67`) ports are fixed by their respective protocols and **cannot be changed**. They must be available on the server where OOTT is installed.
>
> OOTT binds these sockets with address/port reuse, so it can run alongside other responders already listening on them (for example `avahi` on `5353`, `minidlna` on `1900`, or a DHCP server/relay on `67`). However, the ports must not be blocked by a host firewall, and the corresponding multicast/broadcast traffic must be allowed to reach the host — otherwise the scanners will not discover any devices.
>
> Port `67` is a privileged port, so OOTT must run with sufficient privileges to bind it (it already requires raw-socket access for the ARP scanner).
>
> When running under Docker these scanners require host networking (or an equivalent setup that exposes the host's multicast and broadcast traffic to the container); see the [sample compose file](https://github.com/rzuasti/oott/blob/main/examples/docker-compose.yml).
## Things to keep in mind
## Connecting the app to the backend
The OOTT app (web, desktop, iOS and Android) talks to the backend exclusively over its REST API (port `3000` by default). You point the app at your backend from the app's settings. There are two supported ways to expose the backend to the app:
### Direct connection over HTTP (local network only)
Point the app at the backend's address on your local network, for example `http://192.168.1.50:3000`. This is the simplest setup and needs no extra infrastructure, but only works while the device is on the same local network as the backend.
> [!IMPORTANT]
> On iOS this only works when you use the backend's **private-range IP address** (`192.168.x.x`, `10.x.x.x`, `172.1631.x.x`) or a `*.local` (mDNS/Bonjour) name. A custom internal domain (e.g. `oott.mylan.com`) served over plain HTTP will be blocked by iOS App Transport Security, even when it resolves to a private IP — use the IP address directly, or HTTPS (see below), instead.
### Behind a reverse proxy with HTTPS (recommended)
Put the backend behind a reverse proxy (nginx, Caddy, Traefik, …) that terminates TLS with a **valid certificate from a trusted CA** (for example [Let's Encrypt](https://letsencrypt.org/)), and point the app at the HTTPS URL (e.g. `https://oott.example.com`). This works on all platforms — including iOS — with no further configuration, and is required for access from outside the local network.
Connect using the **domain name the certificate is issued for** (not an IP address). Self-signed certificates are not supported unless they are manually trusted on the device.
> [!NOTE]
> The first time the iOS app reaches your backend on the local network, iOS shows a one-time "find devices on your local network" prompt. This is expected — tap Allow to continue.
# Storage considerations
### Storage considerations
OOTT stores a timestamped event in the database for every device detected on every scan. Storage therefore scales with three factors: number of active devices, scan frequency, and the retention window.
## Estimates by network size
#### Estimates by network size
The figures below assume a 1-minute scan duration (`arp_scanner.scan_duration = 1m`) with a 1-minute wait between scans (`arp_scanner.wait_between_scans = 1m`), giving 720 scans per day, and a 365-day retention window. Assume all devices are continuously online (worst case).
| Network size | Active devices | Storage / year |
@@ -216,8 +210,7 @@ storage ≈ devices × scans_per_day × 145 bytes × retention_days
where `145 bytes` covers the database row and its index entry, and `scans_per_day = 86400 / (scan_duration_seconds + wait_between_scans_seconds)`.
## Tuning scan timings and retention to control storage
#### Tuning scan timings and retention to control storage
Storage is directly proportional to scan frequency and retention window. The two main levers are:
**Scan interval**`arp_scanner.wait_between_scans` is the most effective knob. Increasing it reduces scans per day linearly:
@@ -250,3 +243,22 @@ A 15-minute wait (the default) cuts storage to about one eighth of the worst-cas
| Homelab | `5m` | `180d` | ~350700 MB |
| Small office | `5m` | `90d` | ~175350 MB |
| Medium office | `15m` | `90d` | ~175450 MB |
### Privileges and network ports
OOTT binds to the following ports on the host where it runs:
|Port|Protocol|Configurable|Used by|
|----|--------|------------|-------|
|`3000`|TCP|Yes (`web_server.port`)|REST API and web UI|
|`5353`|UDP (multicast)|**No — fixed**|mDNS/Bonjour scanner|
|`1900`|UDP (multicast)|**No — fixed**|SSDP/UPnP scanner|
|`67`|UDP (broadcast)|**No — fixed**|DHCP scanner|
The mDNS (UDP `5353`), SSDP (UDP `1900`) and DHCP (UDP `67`) ports are fixed by their respective protocols and **cannot be changed**. They must be available on the server where OOTT runs.
OOTT binds these sockets with address/port reuse, so it can run alongside other responders already listening on them (for example `avahi` on `5353`, `minidlna` on `1900`, or a DHCP server/relay on `67`). However, the ports must not be blocked by a host firewall, and the corresponding multicast/broadcast traffic must be allowed to reach the host — otherwise the scanners will not discover any devices.
> [!IMPORTANT]
> OOTT needs elevated privileges: the ARP scanner requires raw-socket access, and port `67` is a privileged port. The pre-built Docker image and NixOS module already run with what they need.
>
> Under Docker, the scanners require **host networking** (or an equivalent setup that exposes the host's multicast and broadcast traffic to the container), as shown in the [Docker installation](#simple-installation-with-docker) above.