Appearance
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
*.confin the configs directory is loaded; each contributes independent targets, schedules, and credentials (the dotfile.c2_overrides.confis 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_MAXtimes (default 3). Backoff isDAEMON_SCAN_RETRY_BACKOFF * 2^attemptseconds (default base 60), and the wait is interruptible so signals are still handled during backoff. - Portal heartbeats: periodic POST to the portal
/api/healthendpoint everyDAEMON_HEARTBEAT_INTERVALseconds (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=daemoninto 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 tosw_verson 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
| Signal | Behavior |
|---|---|
SIGHUP | Reload all config files. New configs are picked up, removed configs dropped, changed schedules take effect, and the persisted overrides file is re-applied. |
SIGTERM | Graceful shutdown. A running scan is allowed to finish before exit. |
SIGINT | Graceful shutdown (same as SIGTERM). |
SIGUSR1 | Dump 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:
| Variable | Default | Description |
|---|---|---|
DAEMON_ENABLED | false | Enable daemon scheduling for this config |
DAEMON_POLL_INTERVAL | 60 | Seconds between schedule checks |
DAEMON_HEARTBEAT_INTERVAL | 300 | Seconds between portal heartbeats |
DAEMON_SCAN_RETRY_MAX | 3 | Max retry attempts on scan failure |
DAEMON_SCAN_RETRY_BACKOFF | 60 | Base backoff seconds (doubles each retry) |
DAEMON_LOG_LEVEL | info | Verbosity: 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 -finstall-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-serviceRelated
- Command and Control: remote fleet management over the heartbeat channel.
- Scheduling: schedule configuration and the standalone scheduled-scan tools.
- License Provisioning: tier gating (heartbeats refresh license state each cycle).
