Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 50 additions & 57 deletions docs/content/docs/guides/networking/multiple-daemons.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,41 @@ description: How to run multiple Scanopy daemon instances on a single machine, e

This guide walks through running multiple daemon instances on a single host, each scanning different interfaces or network segments. For help deciding whether this is the right approach for your network, see [Planning Daemon Deployment](/setting-up-daemons/planning-daemon-deployment/).

## How config namespacing works
## How multiple daemons coexist

Every daemon instance has a name. The default is `scanopy-daemon`. When you pass `--name`, the daemon stores its configuration in a subdirectory named after that value:
Each `scanopy-daemon install` on a host takes its own **slot** — an independent install with its own config file, service, and log file. The first install takes the default slot (`scanopy-daemon`); each additional install takes the next number (`scanopy-daemon-2`, `scanopy-daemon-3`, …). The installer allocates the slot for you — you don't name it.

| Name | Config path (Linux) |
| --- | --- |
| _(default)_ | `~/.config/scanopy/daemon/config.json` |
| `--name eth0` | `~/.config/scanopy/daemon/eth0/config.json` |
| `--name iot` | `~/.config/scanopy/daemon/iot/config.json` |
| Slot | Service | Config (Linux) |
| --- | --- | --- |
| `scanopy-daemon` | `scanopy-daemon` | `/etc/scanopy/daemon/scanopy-daemon/config.json` |
| `scanopy-daemon-2` | `scanopy-daemon-2` | `/etc/scanopy/daemon/scanopy-daemon-2/config.json` |
| `scanopy-daemon-3` | `scanopy-daemon-3` | `/etc/scanopy/daemon/scanopy-daemon-3/config.json` |

Each slot's config holds its own daemon ID, API key, server URL, and runtime state. Slots are fully independent — starting, stopping, or reconfiguring one does not affect the others. A slot is not the daemon's *name*: the name is assigned by the server and shown in the app, and can be changed there without moving the slot.

Each named daemon gets its own config file with its own daemon ID, API key, server URL, and runtime state. They are fully independent — starting, stopping, or reconfiguring one does not affect the others.
<Callout type="info">
A daemon's display name comes from the server, so you never pass it at install time. Run `scanopy-daemon list` on the host to see each installed slot, its service, and its config path.
</Callout>

**Platform paths:**
**System config locations by platform** (service installs):

| Platform | Base directory |
| --- | --- |
| Linux | `~/.config/scanopy/daemon/` |
| macOS | `~/Library/Application Support/com.scanopy.daemon/` |
| Windows | `%APPDATA%\scanopy\daemon\` |
| Linux | `/etc/scanopy/daemon/` |
| macOS | `/Library/Application Support/Scanopy/daemon/` |
| Windows | `%ProgramData%\Scanopy\daemon\` |
| FreeBSD | `/usr/local/etc/scanopy/daemon/` |

## Restricting daemons to specific interfaces

Use `--interfaces` to limit which network interfaces a daemon scans. Without it, the daemon scans all interfaces.

```bash
# Scan only eth0
sudo scanopy-daemon install --name eth0 --interfaces eth0
sudo scanopy-daemon install --interfaces eth0

# Scan two interfaces
sudo scanopy-daemon install --name production --interfaces eth0,eth1
sudo scanopy-daemon install --interfaces eth0,eth1
```

The same setting works as an environment variable or in the config file:
Expand All @@ -49,20 +54,16 @@ SCANOPY_INTERFACES=eth0,eth1

When `--interfaces` is set, the daemon only reports and scans subnets attached to those interfaces. This prevents overlap between daemon instances sharing the same host.

## Configuring ports and bind addresses
## Ports and bind addresses

Every daemon instance runs an HTTP server that binds to `0.0.0.0:60073` by default. Because `0.0.0.0` binds all interfaces, the first daemon claims port 60073 on every address — subsequent instances on the same host will fail with a port conflict.
Every daemon instance runs an HTTP server, bound to `0.0.0.0:60073` by default. Because `0.0.0.0` binds all interfaces, the first daemon claims port 60073 on every address — a second daemon can't reuse it.

To fix this, give each instance a unique `--bind-address` (the IP of its specific interface) and a unique `--daemon-port`:
The installer handles this for you: when you install another daemon and don't ask for a specific port, it picks the next free one (60074, 60075, …) so the new instance doesn't collide with one already on the host. You only need to set `--daemon-port` when you want a *specific* port — most importantly for a **ServerPoll** daemon, where the port is the one the server dials, so the install command already carries it and the installer leaves it alone.

```bash
# Daemon on eth0 (192.168.1.10) — uses default port
sudo scanopy-daemon install --name eth0 --interfaces eth0 \
--bind-address 192.168.1.10 --daemon-port 60073
To pin a daemon's listener to one interface, give it that interface's IP with `--bind-address`:

# Daemon on eth1 (10.0.0.10) — different port
sudo scanopy-daemon install --name eth1 --interfaces eth1 \
--bind-address 10.0.0.10 --daemon-port 60074
```bash
sudo scanopy-daemon install --interfaces eth0 --bind-address 192.168.1.10
```

The same settings work as environment variables or in the config file:
Expand All @@ -76,31 +77,30 @@ SCANOPY_DAEMON_PORT=60073
{ "bind_address": "192.168.1.10", "daemon_port": 60073 }
```

<Callout type="warn">
Both `--bind-address` and `--daemon-port` are required when running multiple daemons on one host. Even if each daemon uses a different bind address, using the same port on `0.0.0.0` will cause a conflict.
</Callout>

## Setting up multiple daemons

Create a separate daemon entry in **Discover > Scan > Daemons** for each instance. Each daemon gets its own API key and can target the same or different networks. During setup, use the **Advanced** tab to configure interface restrictions under Network Discovery. The app provides platform-specific install and run commands with your settings included.
Create a separate daemon entry in **Discover > Scan > Daemons** for each instance. Each is provisioned with its own API key and can target the same or different networks. During setup, use the **Advanced** tab to set that instance's interface restrictions, bind address, and port under Network Discovery — they're baked into the install artifact the wizard gives you, so you don't have to add them by hand.

Run `scanopy-daemon install --name <name>` for each instance. Each one registers its own background service — named `scanopy-daemon-<name>` — and writes its namespaced config, so it starts immediately and on every reboot:
Run each daemon's own install artifact on the host. Each run takes a fresh slot, so you can install several without them overwriting each other — no per-instance flags to remember:

```bash
sudo scanopy-daemon install --name eth0 --interfaces eth0 \
--bind-address 192.168.1.10 --daemon-port 60073 \
--server-url https://scanopy.example.com --daemon-api-key <key-for-eth0>
sudo scanopy-daemon install --name iot --interfaces eth1 \
--bind-address 10.0.0.10 --daemon-port 60074 \
--server-url https://scanopy.example.com --daemon-api-key <key-for-iot>
# First daemon — takes the scanopy-daemon slot
sudo scanopy-daemon install --interfaces eth0

# Second daemon — takes the scanopy-daemon-2 slot automatically
sudo scanopy-daemon install --interfaces eth1
```

Manage each instance by its service name — for example `sudo systemctl restart scanopy-daemon-eth0` (Linux), `sudo launchctl kickstart -k system/com.scanopy.daemon.eth0` (macOS), or `sc.exe stop scanopy-daemon-iot` (Windows). The bind address and port are persisted in each daemon's config file, so re-running `install` or restarting the service doesn't need them again.
Each `install` registers a per-slot background service that starts immediately and on every reboot, and writes that slot's config. Manage each by its service — for example `sudo systemctl restart scanopy-daemon-2` (Linux) or `sc.exe stop scanopy-daemon-2` (Windows), where the service id matches the slot. On macOS the launchd label is `com.scanopy.daemon.<slot>` (for example `sudo launchctl kickstart -k system/com.scanopy.daemon.scanopy-daemon-2`). Run `scanopy-daemon list` to see each slot's exact service id. Interface, bind address, and port are persisted in each slot's config, so restarting or re-running `install` doesn't need them again.

<Callout type="info">
To re-run against an *existing* slot — reconfigure it, or re-key it after rebuilding the host — pass `--instance <selector>`, where the selector is the daemon's name, its slot, its service id, or its daemon id (all shown by `scanopy-daemon list`). Without a selector on a multi-daemon host, a non-interactive install allocates a new slot rather than guessing which one you meant, and an interactive one asks. The reconfigure command the app generates already includes the right `--instance`.
</Callout>

### systemd template (Linux, manual)

<Callout type="info">
Prefer `scanopy-daemon install --name <name>` above — it registers a per-instance service for you. The systemd template below is the manual alternative, kept for older daemons or hand-managed setups.
Prefer `scanopy-daemon install` above — it registers a per-slot service for you. The systemd template below is the manual alternative, kept for older daemons or hand-managed setups.
</Callout>

Scanopy provides a [systemd template unit](https://github.com/scanopy/scanopy/blob/main/scanopy-daemon%40.service) for running multiple instances. Download and install it:
Expand All @@ -111,10 +111,10 @@ sudo curl -o /etc/systemd/system/scanopy-daemon@.service \
sudo systemctl daemon-reload
```

The template uses the instance identifier as both `--name` and `--interfaces`. Each instance needs its own bind address and port — set these via environment overrides:
The template uses the instance identifier as `--interfaces`. Each instance needs its own bind address and port — set these via environment overrides:

```ini
ExecStart=/usr/local/bin/scanopy-daemon --name=%i --interfaces=%i
ExecStart=/usr/local/bin/scanopy-daemon --interfaces=%i
SyslogIdentifier=scanopy-daemon-%i
```

Expand Down Expand Up @@ -155,41 +155,34 @@ journalctl -u scanopy-daemon@eth1
```

<Callout type="info">
The template ties the instance name to a single interface. If a daemon needs multiple interfaces, create a custom service file that passes `--interfaces eth0,eth1` and a descriptive `--name`.
The template ties the instance name to a single interface. If a daemon needs multiple interfaces, create a custom service file that passes `--interfaces eth0,eth1`.
</Callout>

## Listing existing daemon configs
## Listing installed daemons

To see which named daemons are configured on a host, list the config directory:
To see which daemons are installed on a host — their slots, services, config paths, and IDs — run:

```bash
# Linux
ls ~/.config/scanopy/daemon/

# macOS
ls ~/Library/Application\ Support/com.scanopy.daemon/

# Windows
dir %APPDATA%\scanopy\daemon\
scanopy-daemon list
```

Each subdirectory corresponds to a named daemon instance. A `config.json` at the top level is the default (unnamed) daemon.
Each line is one installed slot. Use any of the values it prints as the `--instance` selector for `install` or the `--name` selector for `uninstall`.

## Upgrading and restarting

All daemon instances share the same binary. Download the latest binary once (the app's **Update** button provides the command), then restart each instance's service:
All daemon slots share the same binary. Download the latest binary once (the app's **Update** button provides the command), then restart each slot's service:

```bash
# Instances installed with `scanopy-daemon install` (services named scanopy-daemon-<name>)
sudo systemctl restart scanopy-daemon-eth0
sudo systemctl restart scanopy-daemon-iot
# Slots installed with `scanopy-daemon install`
sudo systemctl restart scanopy-daemon
sudo systemctl restart scanopy-daemon-2

# Legacy systemd-template instances (scanopy-daemon@<name>): stop, replace the binary, start
sudo systemctl stop scanopy-daemon@eth0 scanopy-daemon@eth1
sudo systemctl start scanopy-daemon@eth0 scanopy-daemon@eth1
```

Configuration is preserved across upgrades — each daemon reads its settings from its own config file on startup.
Configuration is preserved across upgrades — each slot reads its settings from its own config file on startup.

## Docker

Expand Down
69 changes: 11 additions & 58 deletions docs/content/docs/reference/daemon-configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,16 @@ environment:

**Configuration file**:

The daemon automatically creates a config file at:
A daemon you run yourself creates its config file under your user profile:

- **Linux**: `~/.config/scanopy/daemon/config.json`
- **macOS**: `~/Library/Application Support/com.scanopy.daemon/config.json`
- **Windows**: `%APPDATA%\scanopy\daemon\config.json`

A daemon installed as a service uses a system directory instead. A background service runs under a different profile than whoever installed it, so `install` writes the config to a system location and points the service at it explicitly. The install command prints the config and log paths it settled on — read them off the output rather than guessing.

Use `--config-dir` / `SCANOPY_CONFIG_DIR` to override the directory holding `config.json`.

The config file stores runtime state (daemon ID, host ID) alongside your settings. Command-line and environment variables take priority over the file.

## Parameter Reference
Expand Down Expand Up @@ -86,68 +90,17 @@ Seeded credentials are applied **only at first registration**. If the daemon is
There is no flag for scanning the local Docker or Podman socket. Local sockets are ordinary credentials — create a [Docker Socket](/guides/integrations/docker/) or [Podman Socket](/guides/integrations/podman/) credential and, if seeding it here, reference it with the loopback form above.
</Callout>

## Deprecated in v0.15.0

<Callout type="warn">
The following parameters are deprecated and will be removed in v0.16.0. Use [Docker Proxy credentials](/guides/integrations/docker/) instead. See the [migration guide](/guides/operations/unified-discovery-migration/#1-recreate-docker-proxy-configuration-as-a-credential) for steps.
</Callout>

- `--docker-proxy` / `SCANOPY_DOCKER_PROXY`
- `--docker-proxy-ssl-cert` / `SCANOPY_DOCKER_PROXY_SSL_CERT`
- `--docker-proxy-ssl-key` / `SCANOPY_DOCKER_PROXY_SSL_KEY`
- `--docker-proxy-ssl-chain` / `SCANOPY_DOCKER_PROXY_SSL_CHAIN`

Scan speed settings (`--scan-rate-pps`, `--arp-rate-pps`, etc.) configured at the daemon level are no longer used. Scan speed is now configured per-discovery in the **Speed** tab. See the [migration guide](/guides/operations/unified-discovery-migration/#2-re-apply-scan-speed-overrides).

## Concurrent Scans

Controls how many hosts the daemon scans simultaneously during network discovery.

**Default behavior**: Auto-detected based on system resources

- Calculates based on available memory
- Typical range: 10-20 for most systems
- Adjusts to prevent memory exhaustion

**When to set manually**:
## Deprecated Parameters

- System crashes during scans
- Memory errors in logs
- Very large networks (100+ hosts)
- Resource-constrained devices (Raspberry Pi)

**Recommended values**:

- **Raspberry Pi 4 (4GB)**: 5-10
- **Standard desktop**: 15-20
- **Server**: 20-30+
- **Low memory**: Start with 5, increase gradually

**Setting**:

```bash
# CLI
scanopy-daemon --concurrent-scans 10

# Environment
export SCANOPY_CONCURRENT_SCANS=10

# Docker
environment:
- SCANOPY_CONCURRENT_SCANS=10
```
The Docker proxy parameters (`--docker-proxy`, `--docker-proxy-ssl-cert`, `--docker-proxy-ssl-key`, `--docker-proxy-ssl-chain`, and their `SCANOPY_*` equivalents) are superseded by [Docker Proxy credentials](/guides/integrations/docker/). See the [migration guide](/guides/operations/unified-discovery-migration/#1-recreate-docker-proxy-configuration-as-a-credential) for steps.

**Symptoms of too high**:
Scan speed settings (`--scan-rate-pps`, `--arp-rate-pps`, and similar) configured at the daemon level are no longer used. Scan speed is configured per-discovery in the **Speed** tab. See the [migration guide](/guides/operations/unified-discovery-migration/#2-re-apply-scan-speed-overrides).

- Daemon crashes during scans
- "CONCURRENT_SCANS too high for this system" error
- Out of memory errors
- System becomes unresponsive
## Scan Concurrency

**Impact**:
The daemon decides how many hosts to scan simultaneously on its own, based on available memory and its file descriptor limit. There is no setting to configure it.

- Lower value = slower scans, more stable
- Higher value = faster scans, more memory usage
If scans are slower than expected because concurrency has been clamped low, the usual cause is a restrictive file descriptor limit — see ["Too Many Open Files"](/setting-up-daemons/troubleshooting-scans/scan-performance/#too-many-open-files-error).

## Logging

Expand Down
Loading
Loading