Skip to content

Daemon Mode

scripts/netvuln_daemon.sh is a persistent scheduling daemon that replaces cron-based scan scheduling. Shipped in v3.0.0, it provides multi-config support, a built-in cron engine, retry with exponential backoff, portal heartbeats with system-info reporting, signal-driven lifecycle management, and soft deregistration handling. It is also the delivery vehicle for Command and Control commands, which ride back on heartbeat responses.

Architecture

The daemon is a long-lived process that continuously monitors one or more configuration files. Each *.conf file in the configs directory (default ~/netvuln-tool/configs/, or --configs-dir) defines a scan target, schedule, and portal credentials. On each poll the daemon evaluates cron expressions against the current time and launches scan pipelines when a schedule matches.

Config parsing applies a final overrides layer: _nvd_load_configs() re-applies <configs_dir>/.c2_overrides.conf after the *.conf files, so an allowlisted value pushed remotely wins over the file value and survives a reload. See Command and Control for the override mechanism.

Key Features

  • Multi-config support: every *.conf in the configs directory is loaded; each contributes independent targets, schedules, and credentials (the dotfile .c2_overrides.conf is deliberately excluded from the glob).
  • Built-in cron engine: parses standard cron expressions (SCAN_SCHEDULE) without the system cron daemon, evaluating minute, hour, day-of-month, month, and day-of-week fields.
  • Retry with exponential backoff: a failed scan is retried up to DAEMON_SCAN_RETRY_MAX times (default 3). Backoff is DAEMON_SCAN_RETRY_BACKOFF * 2^attempt seconds (default base 60), and the wait is interruptible so signals are still handled during backoff.
  • Portal heartbeats: periodic POST to the portal /api/health endpoint every DAEMON_HEARTBEAT_INTERVAL seconds (default 300), carrying agent id, version, status, uptime, schedule states, client id, and a system-info snapshot.
  • Signal-driven lifecycle: reload, graceful shutdown, and status dump without a restart.
  • Soft deregistration: an HTTP 403 heartbeat response triggers a clean shutdown.
  • Metadata injection: injects scheduled_by=daemon into session metadata, which the portal surfaces in its scheduled-scan dashboard.

Heartbeats and System Info

Each heartbeat cycle builds a JSON payload and POSTs it with the x-api-key header to ${api_url%/upload}/health (or DAEMON_HEARTBEAT_URL when set). The payload shape:

json
{
  "agent_id": "nvd_89f6e23d",
  "version": "4.2.4",
  "timestamp": "2026-07-18T12:00:00Z",
  "status": "running",
  "uptime_seconds": 3600,
  "schedules": [
    {
      "config": "acme_corp.conf",
      "schedule_id": "sched_ab12cd",
      "last_run": "2026-07-18T06:00:00Z",
      "last_status": "success",
      "next_run": "2026-07-19T06:00:00Z",
      "retry_count": 0
    }
  ],
  "client_id": "acme-corp",
  "system_info": { "os": {}, "hardware": {}, "packages": {}, "network": {}, "config": {} }
}

The system_info block is gathered by _nvd_gather_system_info() and cached, refreshed only on the first heartbeat and then every 10th cycle to avoid overhead:

  • os: name, version, kernel, arch, hostname (reads /etc/os-release, falls back to sw_vers on macOS).
  • hardware: CPU count, total/available RAM (MB), total/available disk (GB) for /.
  • packages: detected versions of nmap, nikto, testssl, nuclei, whatweb, sslscan, and the netvuln-tool version.
  • network: non-loopback IPv4 addresses, public IP (via ifconfig.me), interfaces, DNS resolver, and a connectivity probe.
  • config: config path, log level, scan concurrency, storage path, and enabled modules.

The agent id is derived as nvd_<first 8 hex of md5(hostname)> and can be overridden with NVD_AGENT_ID (used in tests).

Soft Deregistration (HTTP 403)

Agents auto-register on their first heartbeat (no approval step). When an administrator deregisters an agent from the portal, the next heartbeat returns HTTP 403. The daemon logs Agent has been unregistered by administrator. Shutting down., sets the shutdown flag, and exits cleanly. The database record is retained (hidden from the fleet list) and can be re-enabled. See Command and Control for the full agent lifecycle (register, deregister, purge).

Signals

SignalBehavior
SIGHUPReload all config files. New configs are picked up, removed configs dropped, changed schedules take effect, and the persisted overrides file is re-applied.
SIGTERMGraceful shutdown. A running scan is allowed to finish before exit.
SIGINTGraceful shutdown (same as SIGTERM).
SIGUSR1Dump current status to ~/netvuln-tool/logs/daemon_status.json (pid, agent id, version, uptime, loaded configs, per-schedule next/last run and retry state, last heartbeat).

CLI Usage

Daemon

netvuln_daemon.sh [--configs-dir <dir>] [--foreground] [-h]

Requires DAEMON_ENABLED="true" in at least one config file. --foreground keeps the process attached instead of daemonizing (useful for debugging and under systemd).

Daemon Control

scripts/netvuln_daemon_ctl.sh is the management interface:

netvuln_daemon_ctl.sh <command> [options]

Commands:
  start              Start the daemon in the background
  stop               Stop the running daemon gracefully
  restart            Stop and then start the daemon
  reload             Send SIGHUP to reload configuration
  status             Show daemon status and schedule summary
  install-service    Install the systemd service unit (Linux only)
  uninstall-service  Remove the systemd service unit (Linux only)

The Python CLI also wraps this: python3 -m netvuln daemon <action> forwards to netvuln_daemon_ctl.sh. See Python Orchestrator.

Configuration

Daemon behavior is controlled through config-file variables:

VariableDefaultDescription
DAEMON_ENABLEDfalseEnable daemon scheduling for this config
DAEMON_POLL_INTERVAL60Seconds between schedule checks
DAEMON_HEARTBEAT_INTERVAL300Seconds between portal heartbeats
DAEMON_SCAN_RETRY_MAX3Max retry attempts on scan failure
DAEMON_SCAN_RETRY_BACKOFF60Base backoff seconds (doubles each retry)
DAEMON_LOG_LEVELinfoVerbosity: debug, info, warning, error

The six keys above (plus ALERT_ENABLED, ALERT_RISK_THRESHOLD, ALERT_ON_NEW_CRITICAL) are the allowlist that a portal push_config command may edit remotely; everything else (targets, schedules, storage path) is file-managed only.

systemd Integration

On Linux the daemon can run as a systemd service:

bash
# Install the service unit (renders templates/netvuln_daemon.service)
sudo ./scripts/netvuln_daemon_ctl.sh install-service

# Enable and start
sudo systemctl enable netvuln-daemon
sudo systemctl start netvuln-daemon

# Check status and follow logs
sudo systemctl status netvuln-daemon
journalctl -u netvuln-daemon -f

install-service writes the unit to /etc/systemd/system/netvuln-daemon.service and runs systemctl daemon-reload. uninstall-service removes it. Both refuse to run on macOS or where systemctl is absent.

bash
sudo ./scripts/netvuln_daemon_ctl.sh uninstall-service

Apache-2.0 licensed (appliance subtree proprietary)