From 5872fcf8dbb689b4fcec836db6719bf48134489b Mon Sep 17 00:00:00 2001 From: rzuasti Date: Tue, 2 Jun 2026 17:37:10 -0400 Subject: [PATCH] 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 --- README.md | 202 +++++++++++++++++++++++++++++------------------------- 1 file changed, 107 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 58d26e5..9041c6c 100644 --- a/README.md +++ b/README.md @@ -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.16–31.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.16–31.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` | ~350–700 MB | | Small office | `5m` | `90d` | ~175–350 MB | | Medium office | `15m` | `90d` | ~175–450 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.