Skip to content

Installation ​

NetVuln Tool supports macOS (development) and Linux/Debian (deployment, including Raspberry Pi). All scan logic is pure Bash with no compiled binaries, and a Python orchestrator (netvuln) drives the same pipeline with typed data models. There are two supported install paths:

  • install.sh (this page, deployment path): sets up netvuln-tool for use on a scanning host or agent. It runs a preflight dependency check, installs the netvuln CLI, provisions a license key, scaffolds a per-engagement config, optionally installs the systemd daemon, and verifies portal connectivity.
  • setup.sh (developer path): sets up a development checkout (git hooks, lint dependencies, editable pip install). Use this when you intend to modify the tool, not just run it.

If you want a container instead of a host install, jump to the Docker guide. For always-on edge scanners, see Raspberry Pi deployment.

Platform Support ​

macOSLinux (Debian/Ubuntu/Raspberry Pi OS)
ScanningYesYes
systemd daemonNo (systemd is Linux-only; install.sh prints a notice)Yes
Recommended roleDevelopmentDeployment / agents

Requirements ​

install.sh installs everything in this table unprompted by default (apt on Linux, Homebrew on macOS); pass --minimal to skip that and fall back to warn-only checks with install hints instead. The table is still the reference for what each tool is for and how to install it by hand.

DependencyRequiredmacOSLinux (Debian/Ubuntu/Raspberry Pi)
Bash 4+Yesbrew install bashPre-installed (verify: bash --version)
nmapYesbrew install nmapsudo apt-get install -y nmap
jqRecommendedbrew install jqsudo apt-get install -y jq
python3 3.10+Recommendedbrew install pythonPre-installed on Debian 12, Raspberry Pi OS Bookworm and Ubuntu 22.04 or later
digOptionalPre-installedsudo apt-get install -y dnsutils
whoisOptionalPre-installedsudo apt-get install -y whois
opensslOptionalPre-installedPre-installed
curlOptionalPre-installedsudo apt-get install -y curl
sshOptionalPre-installedsudo apt-get install -y openssh-client
ncOptionalPre-installedsudo apt-get install -y netcat-openbsd
tracerouteOptionalPre-installedsudo apt-get install -y traceroute
smbclientOptionalbrew install sambasudo apt-get install -y smbclient
snmpwalk / snmpgetOptionalbrew install net-snmpsudo apt-get install -y snmp
wkhtmltopdfOptional (PDF export)brew install wkhtmltopdfsudo apt-get install -y wkhtmltopdf
msmtpOptional (email alerts)brew install msmtpsudo apt-get install -y msmtp
iwOptional (WiFi, Linux only)n/asudo apt-get install -y iw
rfkillOptional (WiFi, Linux only)n/asudo apt-get install -y rfkill
nmcliOptional (WiFi, Linux only)n/asudo apt-get install -y network-manager

Missing optional tools are gracefully skipped with a warning. iw, rfkill and nmcli are Linux-only and are never auto-installed on macOS.

Dependencies by purpose ​

ToolStatusPurpose
nmapRequiredCore scanning engine (host discovery, port/service detection, vuln scripts)
jqRecommendedJSON processing, required for HTML reports
python3RecommendedThe netvuln orchestrator CLI (recon/scan/upload/push/diff/status/daemon)
dig / host / nslookupOptionalDNS enumeration
whoisOptionalWHOIS / OSINT lookups
opensslOptionalSSL/TLS analysis
curlOptionalHTTP header checks, CVE CVSS lookups, session upload, license validation
sshOptionalSSH auth-method enumeration
nc / ncatOptionalSSH banner grabbing
tracerouteOptionalHop-distance / route discovery
smbclientOptionalSMB share enumeration
snmpwalk / snmpgetOptionalSNMP enumeration
wkhtmltopdfOptionalPDF export for executive summaries
msmtpOptionalEmail alerts and scan reminders (one of several accepted mail backends)
iwOptional (WiFi, Linux only)WiFi interface discovery
rfkillOptional (WiFi, Linux only)Unblocking a soft-blocked WiFi radio
nmcliOptional (WiFi, Linux only)WiFi network enumeration

One-command install ​

On a Linux or macOS host with curl and git, run as a normal user with sudo rights (not as root):

bash
curl -fsSL https://netvulntool.com/install.sh | bash

The bootstrap clones the public Community Edition at the latest release tag (or at --version vX.Y.Z) into ~/netvuln-tool, checks the cloned commit against the release, and runs that tree's install.sh, which asks for what it needs on the terminal. See Deployment install: install.sh for what that installer does.

License key ​

Set the key in the environment first (the silent read below keeps it out of shell history), or paste it at install.sh's silent prompt (nothing is echoed):

bash
read -rs NV_LICENSE_KEY && export NV_LICENSE_KEY
curl -fsSL https://netvulntool.com/install.sh | bash

The key is never a command-line argument: the bootstrap refuses --license-key (exit 2) so a key cannot land in shell history or the process list. With no key and no terminal the install proceeds unlicensed. See License Provisioning for key formats and tiers.

Automation ​

Pass --yes (also -y or --non-interactive) together with NV_LICENSE_KEY in the environment. Other install.sh flags pass straight through (--api-url, --client-id, --targets, --service-user, --no-daemon, and so on):

bash
read -rs NV_LICENSE_KEY && export NV_LICENSE_KEY
curl -fsSL https://netvulntool.com/install.sh | bash -s -- --yes --client-id acme

--yes makes install.sh run non-interactively, so it also runs its platform fixes in preflight. It never deletes anything (see Cleanup).

Upgrading an existing install ​

The bootstrap looks for an existing checkout in ~/netvuln-tool and /opt/netvuln-tool. When it finds one it offers an upgrade instead of installing, or takes --upgrade when there is no terminal. The upgrade uses the same git steps as the fleet update command (fetch tags, check out the release tag), then restarts the daemon only if it was running. It never re-runs install.sh and never touches your configs.

  • A checkout cloned from another source, or one with local edits to tracked files, is refused (exit 6).
  • Nothing changes without a yes: answer the prompt, or pass --yes or --upgrade.
  • After an upgrade of an unlicensed install, the bootstrap offers to apply a license key with scripts/set_license_key.sh when that script is present in the tree.

Verification and cleanup ​

After an install or an upgrade the bootstrap runs its own local checks and prints each result, because install.sh treats several failures (a failed portal check, the CLI install) as warnings. A failed check exits 8.

On the nvt path (/opt/netvuln-tool, a Raspberry Pi by default or --service-user nvt), the home clone is a duplicate once the install is verified. If this run created it, the bootstrap offers to remove it, showing the exact path. Pass --cleanup to remove it without asking (this works without a terminal too). A ~/netvuln-tool that existed before the run is never removed.

Flags ​

FlagPurpose
--version X.Y.ZInstall or move to this release (default: latest)
--upgradeUpgrade an existing checkout without asking
--yes, -y, --non-interactiveAnswer yes to prompts and run install.sh non-interactively
--cleanupRemove the temporary home copy this run created (nvt path, after verification)
--help, -hShow usage

Any other flag is passed to the release's install.sh.

Exit codes ​

CodeMeaning
0Success, or nothing to do
2Usage error, including --license-key
3Preflight, version or fetch failure
4Target is not a git checkout
5Integrity mismatch against the release
6Existing checkout refused (another source, or local edits)
7install.sh, an upgrade step or the license key script failed
8Verification failed
9Daemon restart failed after the upgrade (the message gives the commands to go back)

What you are trusting ​

curl | bash runs whatever the server returns, so this command trusts TLS to netvulntool.com and gitlab.com. To inspect first, clone the public repository, read it, and run the installer yourself:

bash
git clone https://gitlab.com/netvulntool/netvuln-tool.git
cd netvuln-tool
git checkout <release tag>
bash install.sh

A release tarball from the project's public releases page works the same way.

Deployment install: install.sh ​

install.sh (repo root) prepares a host to run scans, either interactively (an analyst laptop) or as an unattended agent. It is idempotent and safe to re-run.

What it does ​

  1. Preflight: detects the platform (macOS or Linux) and installs the full scanner toolchain unprompted, skipping any binary already on PATH. On Linux it runs one batched apt-get update && apt-get install -y <packages>, falling back to installing each package individually (warning on any that fails) if the batch fails; on macOS it installs the Homebrew-formula subset per formula (WiFi tools iw, rfkill and nmcli are Linux-only and are only reported on macOS). Pass --minimal to skip all of this and fall back to the old warn-only nmap/jq check with install hints instead.
  2. Service account (Linux only): resolves who the daemon runs as and where the tree lives, either the current user (default) or a dedicated nvt account, always on Raspberry Pi and opt-in elsewhere with --service-user nvt. See Where things live below for the exact paths each choice produces.
  3. Python CLI: installs the netvuln console script (skip with --no-python). On an externally managed interpreter (see PEP 668 below) it builds a virtualenv and links the script into ~/.local/bin; elsewhere it runs pip install -e . directly. The venv path depends on step 2 (see Where things live).
  4. License key: installs the provided key (mode 600); the destination depends on step 2 (see Where things live). See License Provisioning for key formats and tiers.
  5. Config scaffold: creates a per-engagement config from templates/report_config.conf.example, filling in the API URL, client ID, and targets you passed; the destination depends on step 2 (see Where things live). The portal URL defaults to https://portal.netvulntool.com/api/upload (see Portal URL below).
  6. Systemd daemon (Linux only): installs and enables the netvuln-daemon service, but leaves scanning off until a config sets DAEMON_ENABLED=true with TARGETS (the daemon stays up with scanning disabled and heartbeats once a key is set). As of 4.9.0, install-service always renders the unit from explicit --user/--group/--home/--install-dir/--base-dir flags (install.sh supplies them) and refuses to render an implicit User=root unit; the daemon runs as the user step 2 resolved (see Where things live) and is granted CAP_NET_RAW (AmbientCapabilities=CAP_NET_RAW on the unit, plus a supplementary sudo setcap cap_net_raw+eip on the nmap binary), so nmap OS fingerprinting (-O) and SNMP/UDP scans (-sU) work without running as root. An nmap package upgrade resets that setcap capability, but this no longer needs a manual fix: the daemon's own scans keep working off the unit's AmbientCapabilities grant regardless, an apt post-invoke hook reapplies the file capability automatically after every apt/dpkg run, and the daemon self-checks the capability at startup and every heartbeat, logging a loud warning and reporting raw_scan_capable: false if it is ever actually lost. See Daemon Mode for the full picture. On macOS it prints a notice that systemd is Linux-only (use launchd or run the daemon in the foreground). Skip with --no-daemon.
  7. Portal verification: checks /api/health reachability and sends an authenticated heartbeat to confirm the license key and API URL are valid.
  8. Summary: prints a pi_validate.sh --json readiness summary and the next steps.

Where things live ​

Every path above depends on which account the daemon runs as (step 2), decided by --service-user, --pi/--no-pi, or the interactive prompt; see Raspberry Pi for the account itself.

PathDefault (current user)nvt path (Raspberry Pi by default, or --service-user nvt)
Install treethe checkout you ran install.sh from/opt/netvuln-tool (owned nvt:nvt)
Python venv~/.netvuln/venv/opt/netvuln-tool/venv
License key~/.netvuln/license.key/home/nvt/.netvuln/license.key
Configs~/netvuln-tool/configs/<name>.conf/opt/netvuln-tool/configs/<name>.conf
Daemon runs asthe invoking usernvt

PEP 668 and virtualenvs ​

Raspberry Pi OS (Bookworm and later), Debian 12+, Ubuntu 23.04+ and Homebrew Python mark their system interpreter as externally managed, which makes a system-wide pip install a hard error rather than a warning:

error: externally-managed-environment

install.sh detects this and installs into a virtualenv instead, so a stock Pi image needs no manual preparation:

PathPurpose
~/.netvuln/venvthe virtualenv holding the netvuln package
~/.local/bin/netvulnsymlink to the console script, no sudo required

If ~/.local/bin is not already on your PATH, the installer says so and prints the line to add:

bash
export PATH="$HOME/.local/bin:$PATH"

Debian packages the venv module separately. If the virtualenv cannot be created, install it and re-run:

bash
sudo apt-get install -y python3-venv

The netvuln CLI is optional. The Bash scan pipeline is self-contained, so a Python install that fails leaves scanning fully functional and only costs you the netvuln command.

Flags ​

FlagPurpose
--license-key <key>License key to install to ~/.netvuln/license.key (e.g. nvt_pro_<hex>)
--api-url <url>Portal upload endpoint. Defaults to https://portal.netvulntool.com/api/upload; pass none for no portal (see Portal URL)
--client-id <id>Client identifier injected into the config for portal multi-tenancy
--targets <spec>Comma-separated IPs, CIDRs, ranges, or domains for the scaffolded config
--config-name <name>Basename for the scaffolded config (see Where things live for the full path)
--service-user <name>Run the daemon as a dedicated account (nvt) instead of the current user. Prompted on non-Pi Linux when omitted; automatic (no prompt) on Raspberry Pi
--piForce Raspberry Pi behavior (auto-detected via /proc/device-tree/model otherwise)
--no-piForce non-Pi behavior on a host that looks like a Pi
--migrateDelegate to scripts/migrate_to_opt.sh (moves a hand-patched /home/nvt/netvuln-tool install onto the supported /opt/netvuln-tool layout) and exit
--minimalSkip the toolchain auto-install in preflight; only check for nmap/jq and print install hints (the pre-#208 behavior)
--no-daemonSkip installing/enabling the systemd service
--no-pythonSkip the pip install -e . step (bash-only install)
--verify-onlyRun the portal connectivity + readiness checks only, no install steps
--full-verifyRun the extended verification (health + authenticated heartbeat + pi_validate)
--offlineSkip the portal connectivity check (step 7/8). Preflight's toolchain install still runs; use --minimal to skip that too
--non-interactive, --yesAssume defaults and never prompt (agent/automation mode); the toolchain install already runs unprompted either way
-h, --helpShow usage

Portal URL ​

--api-url defaults to https://portal.netvulntool.com/api/upload, the hosted portal. The interactive prompt offers it, so Enter accepts it:

  Portal upload URL ('none' for no portal) [https://portal.netvulntool.com/api/upload]:

The same default applies to --non-interactive runs that pass no --api-url, so an unattended agent is portal-connected out of the box. Three ways to change that:

You wantDo this
A self-hosted portalType the URL at the prompt, or pass --api-url <your-portal-host>/api/upload
No portal at allType none at the prompt, or pass --api-url none. The config gets an empty CONSULTATION_API_URL and step 7 is skipped
A portal, but no network calls during installPass --offline. The URL is still written to the config; only the verification is skipped

Because a URL is now always configured by default, step 7 runs on every install rather than being skipped. Uploads still require an API key as well, so a config with a URL and no key does not send anything.

Example: unattended agent install ​

bash
bash install.sh --non-interactive \
  --license-key nvt_pro_<hex> \
  --api-url https://portal.netvulntool.com/api/upload \
  --client-id acme \
  --targets 192.168.1.0/24

This installs the CLI, writes the license key, scaffolds ~/netvuln-tool/configs/acme.conf with the URL/client/targets pre-filled, installs the systemd daemon (Linux), verifies portal connectivity, and prints a readiness summary. The daemon runs no scans until you set DAEMON_ENABLED=true in that config, but it stays up with scanning disabled and, once a license key is set, heartbeats so the agent appears in the portal.

Enable scheduled scanning after install

install.sh installs the daemon but does not start scanning. Edit the scaffolded config, set DAEMON_ENABLED="true" with a SCAN_SCHEDULE and TARGETS, then reload the daemon. See Daemon Mode and Scheduling.

Only scan authorized networks

Provisioning targets in a config does not grant authorization. Only scan hosts and networks you are explicitly authorized to assess.

Developer install: setup.sh ​

The public repository and the download addresses go live with the first public release of 4.10.0; if a link does not resolve yet, the release has not been published.

Use setup.sh when you are working on netvuln-tool itself. The source is public: clone it directly, or download a release tarball and verify it against its checksum. Clone the repository and run setup.sh once:

bash
git clone https://gitlab.com/netvulntool/netvuln-tool.git
cd netvuln-tool
bash setup.sh

Prefer a release tarball over a git clone? Download and verify it (replace vX.Y.Z with the release you want; /download/latest and /download/latest.sha256 on the same host always resolve to the current release):

bash
curl -Lfo netvuln-tool-vX.Y.Z.tar.gz https://netvulntool.com/download/vX.Y.Z
curl -Lfo netvuln-tool-vX.Y.Z.tar.gz.sha256 https://netvulntool.com/download/vX.Y.Z.sha256
sha256sum -c netvuln-tool-vX.Y.Z.tar.gz.sha256   # Linux
shasum -a 256 -c netvuln-tool-vX.Y.Z.tar.gz.sha256   # macOS

The /download/ addresses resolve to the assets on the GitLab release page at gitlab.com/netvulntool/netvuln-tool/-/releases, where the same files can be fetched directly if an alias is not yet in place.

The tarball extracts into a single top-level netvuln-tool-vX.Y.Z/ directory.

See the open source boundary for the full license and functional boundary.

setup.sh performs a five-step developer bootstrap:

  1. Configures git hooks (git config core.hooksPath .githooks) so the pre-commit ShellCheck lint and pre-push test suite run automatically.
  2. Checks linting dependencies (shellcheck, bats) and prints install hints for any that are missing.
  3. Checks runtime dependencies (nmap, jq).
  4. Installs the Python orchestrator in editable mode with dev extras (pip install -e ".[dev]"), putting the netvuln CLI on your PATH. Unlike install.sh, the developer path does not build a virtualenv for you: on an externally managed interpreter it prints pip's error and the venv command to run instead (python3 -m venv .venv && .venv/bin/pip install -e '.[dev]').
  5. Verifies the git hooks are executable.

It does not install a license, scaffold an engagement config, or install the systemd daemon; those belong to the deployment path (install.sh).

Dependency check ​

At any time, verify your environment with:

bash
bash scripts/dependency_check.sh

This detects your platform (macOS or Linux) and prints the correct install command for each missing tool: brew install on macOS, apt-get install on Linux. It reports and prompts; it does not batch-install the way install.sh's default preflight does. Useful for a standalone check outside of a full install.sh run, or after --minimal.

Platform validation ​

For deployment readiness (especially on Raspberry Pi or other edge devices), use the platform validation script:

bash
bash scripts/pi_validate.sh          # Run the deployment checks
bash scripts/pi_validate.sh --json   # JSON output (used by install.sh's summary)
bash scripts/pi_validate.sh --fix    # Attempt to install missing dependencies

It checks disk space, memory, required tools, permissions, and portal (/api/health) reachability, and can emit JSON for monitoring integration. Combine flags, for example pi_validate.sh --fix --json.

Install snapshot ​

netvuln_snapshot.sh prints a read-only report of the local install: the host details the portal shows for the agent, where the install lives, the agent identity and license tier, the daemon state, and every file, account, unit and crontab line an uninstall would touch. Run it before an uninstall, or whenever you need to know what is on a host.

bash
# Service-account layout
/opt/netvuln-tool/scripts/netvuln_snapshot.sh

# Current-user layout
~/netvuln-tool/scripts/netvuln_snapshot.sh

# One JSON document, for automation
/opt/netvuln-tool/scripts/netvuln_snapshot.sh --json
  • It writes nothing, starts and stops nothing, and makes no network call. It may use sudo -n to read files the service account owns, and never to change anything.
  • It never prints a key, a token or a full config file. The license appears as its tier prefix only, API-key environment variables show as set or unset, and config files contribute only their DAEMON_ENABLED and CLIENT_ID values. Credentials embedded in a git remote URL are masked.
  • --install-dir DIR inspects one install when more than one is found. --operator USER names the account whose home holds the CLI link and crontab, which matters only when you run it as root without sudo.
  • It exits 0 whenever it ran, including when it finds no install. Exit 2 means a bad argument or a missing jq.

Uninstall ​

netvuln_uninstall.sh removes an install. It shows a plan first, asks one confirmation, then removes. It copies itself to a private temporary directory before it deletes the install.

By default it is a full removal. What goes depends on the layout:

  • Service-account layout: the install directory /opt/netvuln-tool (sessions, configs, logs and the virtualenv), the nvt account and its home /home/nvt (where the license lives), the systemd unit, the sudoers drop-in, the apt hook, the nmap capability, the operator's netvuln CLI link and the netvuln-tool crontab lines.
  • Current-user layout: ~/netvuln-tool (sessions, configs, logs and the virtualenv), the whole ~/.netvuln directory (the license and the license cache), the netvuln CLI link and the netvuln-tool crontab lines. The service-only steps (unit, sudoers drop-in, apt hook, nmap capability, account) are skipped.

It never removes packages, and it never kills a process. It stops the daemon itself (systemctl disable --now on the service layout, the daemon control script on the current-user layout). If processes remain after the stop, step 1 fails and the daemon stays stopped.

bash
# Dry run: print the plan and any blockers, change nothing
sudo /opt/netvuln-tool/scripts/netvuln_uninstall.sh --dry-run

# Real run: shows the plan, then asks you to type yes
sudo /opt/netvuln-tool/scripts/netvuln_uninstall.sh

# Keep a copy of sessions and configs first
sudo /opt/netvuln-tool/scripts/netvuln_uninstall.sh --keep-data /var/tmp/netvuln-export

A service-account host needs sudo (or passwordless sudo) for the plan to be complete: the license, the sessions and the checkout state sit in paths owned by root and nvt. On the current-user layout the same commands run without sudo, from ~/netvuln-tool/scripts/.

  • Confirmation. The answer must be the literal text yes, read from the terminal. --yes skips it, for automation. With no terminal and no --yes the run exits 2. Anything other than yes exits 4 and removes nothing.
  • Privilege. Run a service-account host's uninstall with sudo. An unprivileged run is accepted only when sudo -n works without a password (the run then re-runs itself under sudo after you confirm). When it does not, the run is blocked with exit 2, in a dry run too, and prints the sudo command to use, because a plan built without those reads would hide the license and the session count.
  • --keep-data DIR exports sessions and configs to DIR before anything is removed. The path must be absolute with no . or .. component, its parent must already exist, it must be empty or not yet exist, it must not be inside the install directory, and no part of it may be a symlink, and the operator must be able to write to it. The configs in the export drop their license key lines (NV_API_KEY, CONSULTATION_API_KEY, NV_LICENSE_KEY) unless you add --keep-license, which also copies the license file. The export keeps every other setting, including webhook URLs and SMTP settings with the SMTP password, so guard it like a credential. --keep-license is only accepted together with --keep-data.
  • --install-dir DIR names the install to remove. It is required when more than one install is found.
  • --operator USER names the account whose home holds the CLI link and crontab. It matters when you run as root without sudo. Without it, those steps are skipped and reported.
  • --force removes a git checkout that is dirty, has unpushed commits, or cannot be read. It overrides that one check and no other.
  • --dry-run prints the plan and any blockers and changes nothing.

Before anything is removed, the run is blocked (exit 2) when any of these holds:

  • the checkout is dirty, has unpushed commits, or cannot be read (run with sudo first on a service host; --force overrides only this)
  • more than one install is found
  • the nvt account does not match what the installer creates
  • the install directory is unsafe, such as /, a home directory, or a symlink
  • the install directory does not carry netvuln-tool's signature files
  • the --keep-data target fails the rules above, including a target that is not writable or exists and is not a directory
  • root is needed and sudo is not available, or sudo needs a password (run the command with sudo)

If processes are still running after the daemon is stopped, step 1 fails and nothing is deleted. On the service layout that means any process owned by nvt, for example a sudo -u nvt shell, not only the daemon. Stop them yourself and run the command again. Every step resumes, so after a failure fix the cause and re-run.

Anything the uninstaller does not remove is listed under "not handled", in the plan and again after the run, and does not make the run fail:

  • Files the installer did not create, such as a hand-copied ~/install_bootstrap.sh or ~/set_license_key.sh in the operator's home, and other paths named netvuln or nvt.
  • The nmap capability when netvuln's apt hook is gone. The hook is the sign that netvuln set the capability, so without it the capability is not provably netvuln's (netvuln_daemon_ctl.sh uninstall-service removes the hook and keeps the capability). Remove it with setcap -r if you want.
  • A sessions or configs directory that is a symlink. It is reported with its target, never followed, and the data it points to is not removed. The link itself goes with the install directory.
  • A partial export left by a failed --keep-data run, which is never deleted.

The install directory is removed without crossing into another filesystem (GNU rm --one-file-system). If a bind mount or another volume sits under it, step 7 fails and names the path: unmount it and run the command again. macOS rm has no such flag, so there the removal proceeds as before.

Portal deregistration is not part of an uninstall. The agent stops sending heartbeats and goes offline, and an admin removes it on the portal.

Exit codes ​

Exit codeMeaning
0Clean, nothing installed, or a dry run with no blockers
1A step failed; run it again to resume
2Blocked, a bad argument, or a refused real run (see below)
3Removal finished with leftovers
4You declined the confirmation

A real run refuses with exit 2 when CI, GITEA_ACTIONS, GITHUB_ACTIONS or BATS_TEST_FILENAME is set in the environment, which can surprise you in a pipeline or an automation shell. Unset those variables to run it. --dry-run is not affected.

Next steps ​

Apache-2.0 licensed (appliance subtree proprietary)