# pfSentinel command manual (v0.2.1) > pfSentinel is a free, open-source (Apache-2.0) command-line tool that backs up pfSense firewalls over SSH or HTTPS. The command is `pfs`. It runs on Windows and Linux, stores passwords in the OS keyring (never in config files), detects config changes, schedules backups with the native OS scheduler, and sends Telegram, Slack or Windows toast notifications. Zero telemetry. Source of truth: this file mirrors the web manual at https://pfs.nx1xlab.dev/docs/ and the output of `pfs --help`. Project: https://github.com/NX1X/pfSentinel. PyPI package: `pfsentinel`. --- ## 1. Requirements - Python 3.13 or newer (only for pip or pipx installs; the standalone binaries need no Python). - Windows 10/11 or Linux (tested on Ubuntu). macOS works via pip but has no prebuilt binary. - pfSense reachable over SSH (recommended) or HTTPS. Tested on pfSense CE 2.8.1. - Enable SSH on pfSense: System > Advanced > Admin Access > Secure Shell. - Extended targets (RRD, packages, DHCP, aliases, certificates, logs, ZFS, archives) need SSH. ## 2. Install All `pfs` commands are identical on Linux and Windows. Only install steps, paths, shells and scheduling differ; those are given for both below. Linux commands are bash, Windows commands are PowerShell. ### pipx (recommended on both) Installs pfSentinel into its own isolated environment and puts `pfs` on the PATH. Linux: ``` sudo apt install pipx # Debian/Ubuntu; other distros: your package manager pipx ensurepath # then open a new terminal pipx install pfsentinel pfs --version ``` Windows: ``` py -m pip install --user pipx py -m pipx ensurepath # then open a new terminal pipx install pfsentinel pfs --version ``` ### pip Linux: `python3 -m pip install --user pfsentinel`. On Ubuntu 23.04+ and Debian 12+ the system Python refuses this with "externally-managed-environment"; use pipx instead. Windows: `py -m pip install pfsentinel`. If `pfs` is not found afterwards, the Scripts folder pip printed is not on PATH; use pipx or `py -m pfsentinel`. ### Standalone binary (no Python) Download `pfs` (Linux) or `pfs.exe` (Windows) and `checksums-sha256.txt` from https://github.com/NX1X/pfSentinel/releases/latest. Install it in a folder the user owns, so `pfs update install` can replace it later without admin rights. Linux: ``` cd ~/Downloads sha256sum -c checksums-sha256.txt --ignore-missing # must print: pfs: OK mkdir -p ~/.local/bin install -m 755 pfs ~/.local/bin/pfs pfs --version # if not found: open a new terminal ``` Windows: ``` cd $env:USERPROFILE\Downloads (Get-FileHash .\pfs.exe -Algorithm SHA256).Hash.ToLower() Select-String 'pfs.exe' .\checksums-sha256.txt # both hashes must match $dir = "$env:LOCALAPPDATA\pfSentinel" New-Item -ItemType Directory -Force $dir | Out-Null Move-Item .\pfs.exe "$dir\pfs.exe" -Force [Environment]::SetEnvironmentVariable('Path', [Environment]::GetEnvironmentVariable('Path','User') + ";$dir", 'User') # open a new PowerShell window, then: pfs --version ``` The Windows binary is not code-signed yet; SmartScreen may warn on first run ("More info > Run anyway" after checking the hash). ### From source Linux: ``` git clone https://github.com/NX1X/pfSentinel.git cd pfSentinel python3 -m venv .venv source .venv/bin/activate pip install -e . pfs --version ``` Windows: ``` git clone https://github.com/NX1X/pfSentinel.git cd pfSentinel py -m venv .venv .venv\Scripts\Activate.ps1 # if blocked: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned pip install -e . pfs --version ``` ## 3. Quick start ``` pfs setup # guided first-time wizard (config + first device) pfs device trust-key home-fw # pin the SSH host key, if you skipped it in the wizard pfs device test -d home-fw # check the connection pfs backup run -d home-fw # first backup pfs schedule enable # daily 02:00 + weekly Sunday 03:00 (Windows: Administrator PowerShell) ``` Or step by step: `pfs config init`, `pfs device add`, `pfs device trust-key `, `pfs device test`, `pfs backup run`. ## 4. Files and locations | What | Linux | Windows | |------|-------|---------| | Config file (print with `pfs config path`) | `~/.pfsentinel/config.json` | `%USERPROFILE%\.pfsentinel\config.json` | | Pinned SSH host keys | `~/.pfsentinel/known_hosts` | `%USERPROFILE%\.pfsentinel\known_hosts` | | Backups (default; change with `pfs config set backup-dir PATH`) | `~/Documents/pfSentinel//` | `%USERPROFILE%\Documents\pfSentinel\\` | | Passwords and tokens | GNOME Keyring or KWallet; on servers, WSL and containers the encrypted store `~/.pfsentinel/store` | Windows Credential Manager (entries named `pfsentinel`) | | Scheduled jobs | systemd user timers `pfsentinel-daily.timer`, `pfsentinel-weekly.timer`; tagged crontab lines where there is no systemd | Task Scheduler folder `\pfSentinel\` (`DailyBackup`, `WeeklyBackup`) | | Scheduled run log | `journalctl --user -u pfsentinel-backup.service`; cron mode `~/.pfsentinel/logs/scheduled.log` | `pfs schedule status`, or Task Scheduler > pfSentinel > History | Credentials: passwords, SSH key passphrases, Telegram tokens and Slack webhooks are kept in the OS keyring (Windows Credential Manager on Windows; GNOME Keyring or KWallet on a Linux desktop). Where no keyring exists (headless Linux, WSL, containers) pfSentinel uses its own AES-256-GCM encrypted file store, which works unattended. Nothing secret is written to `config.json`. Backup filenames look like `home-fw_2026-04-30_143022_#001_interfaces+system.xml.gz`: device ID, date, time, daily sequence number, changed config sections, extension. Change detection: each config backup is compared with the previous one, section by section (interfaces, firewall, system, users, packages, dhcp, vpn, routes, minor, initial). A config backup is saved only when something changed, or when it is the first backup for the device. Retention: applied after each backup, per type (`backup_policy.max_backups_per_type`) and globally (`backup_policy.keep_days`, default 30) in `config.json`. ## 5. Command reference Usage pattern: `pfs [OPTIONS] COMMAND [ARGS]...`. Every command accepts `--help`. Running `pfs` with no arguments prints help. ### Global options | Option | Description | |--------|-------------| | `--version`, `-v` | Show version | | `--install-completion` | Install shell completion for the current shell | | `--show-completion` | Print the completion script | | `--help` | Show help and exit | ### Top-level commands `pfs setup` - Guided first-time setup: create config and add your first device. `pfs status` - Overview of devices, backups and configuration. `pfs list [--device/-d ID]` - List available backups (shortcut for `pfs backup list`). `pfs docs [--no-browser]` - Open this manual (https://pfs.nx1xlab.dev/docs) in your browser and print its URL plus the URL of this plain-text version. `--no-browser` only prints the links. ### pfs backup `pfs backup run` - Run a backup for one or all devices. Without `--include`, `--all-extras` or `--config-only` it shows an interactive menu of extra targets when run from a terminal. With `--non-interactive` (or with no terminal, as in scheduled runs) it never prompts and uses the backup policy from config. | Option | Description | |--------|-------------| | `--device`, `-d ID` | Device ID (default: all enabled devices) | | `--desc TEXT` | Description label | | `--backup-dir`, `-o PATH` | Override backup directory (default: `~/Documents/pfSentinel`, the same on both systems) | | `--no-notify` | Suppress notifications | | `--area NAME` | Back up only one config section (HTTPS only). Valid: aliases, captiveportal, cert, dhcpd, filter, interfaces, ipsec, nat, openvpn, routes, services, shaper, syslog, system, users, wol | | `--no-packages` | Exclude package config (HTTPS only) | | `--include LIST` | Extra targets, repeatable or comma-separated: rrd, pkg, dhcp, aliases, certs, logs | | `--all-extras` | Include all extra targets | | `--config-only` | Only the XML config, skip extras | | `--non-interactive` | Never prompt; use the backup policy from config (scheduled runs use this) | `pfs backup list [--device/-d ID] [--json]` - List backups. `pfs backup verify FILENAME` - Re-check SHA-256 and structure (XML for config, tar integrity for archives). `pfs backup delete FILENAME [--yes/-y]` - Delete a backup file. `pfs backup restore FILENAME [--target/-t PATH]` - Extract a backup to a local path (default `.`). This does not push anything to pfSense. `pfs backup diff FILE_A FILE_B` - Unified diff between two config backups. `pfs backup info FILENAME` - Metadata: timestamp, checksum, size, change categories, verification status. `pfs backup search` - Search backups. | Option | Description | |--------|-------------| | `--name`, `-n TEXT` | Filename substring | | `--device`, `-d ID` | Filter by device | | `--date YYYY-MM-DD` | Filter by date | | `--changes TEXT` | Filter by change label substring (for example `firewall`) | | `--min-size KB` / `--max-size KB` | Size range in KB | | `--json` | Output as JSON | `pfs backup watch [--device/-d ID] [--interval/-i SECONDS] [--desc TEXT]` - Poll the device (default every 300 s) and back up whenever the config checksum changes. Ctrl+C to stop. `pfs backup snapshot --device/-d ID [--full]` - ZFS snapshot backup (pfSense 2.5+ on ZFS). Incremental by default; `--full` forces a full stream. `pfs backup snapshot-list --device/-d ID` - List ZFS snapshots on the device. `pfs backup archive --device/-d ID [--dirs LIST]` - Filesystem tar.gz archive (non-ZFS fallback). `--dirs` takes comma-separated remote directories. Default set: `/cf/conf`, `/usr/local/etc`, `/var/db/rrd`, `/boot/loader.conf`, `/boot/loader.conf.local`. ### pfs device `pfs device add` - Add a device interactively. Every prompt can be pre-filled with options: | Option | Description | |--------|-------------| | `--id TEXT` | Device ID slug (for example `home-fw`) | | `--label TEXT` | Display name | | `--host TEXT` | IP address or hostname | | `--method TEXT` | `ssh`, `https` or `http` | | `--username`, `-u TEXT` | pfSense username | | `--ssh-port INT` | Default 22 | | `--https-port INT` | Default 443 | | `--http-port INT` | Default 80 | | `--no-verify-ssl` | Skip TLS certificate check (self-signed pfSense certificate) | For SSH devices the wizard also asks for an optional SSH private key path (with a key, the password becomes optional), then shows the device's SSH host key fingerprint and asks you to trust it, and finally offers to test the connection. `pfs device list` - List configured devices. `pfs device test [--device/-d ID]` - Test connectivity to one or all devices. `pfs device edit DEVICE_ID` - Change label, host, username, method, password, SSL verification or SSH key. `pfs device remove DEVICE_ID [--yes/-y]` - Remove a device from config. Backup files are kept. `pfs device trust-key DEVICE_ID [--yes/-y]` - Connect to the device, show its SSH host key SHA256 fingerprint, ask you to confirm, and save it to `~/.pfsentinel/known_hosts`. `--yes` skips the prompt (verify the fingerprint yourself). Since 0.2.0 new devices use strict host key checking: an SSH device must be trusted once before backups work, and a later key change is refused as a possible man-in-the-middle until you run `trust-key` again (for example after reinstalling pfSense). ### pfs config `pfs config show` - Show configuration (passwords hidden). `pfs config init [--force]` - Create a default config file. `--force` overwrites. `pfs config validate` - Validate the config and check that credentials are available. `pfs config set KEY VALUE` - Change a setting. Supported key: `backup-dir`. Other settings (extras, ZFS, archive, retention) are edited in `config.json`. `pfs config path` - Print the config file path. ### pfs schedule `pfs schedule enable` - Enable scheduled backups with the OS scheduler. Scheduled jobs run `pfs backup run --non-interactive`, which never prompts and uses the backup policy from `config.json`. The same options work on both systems. | Option | Description | |--------|-------------| | `--daily-time HH:MM` | Daily backup time (default 02:00) | | `--weekly-day DAY` | Weekly backup day (default sunday) | | `--weekly-time HH:MM` | Weekly backup time (default 03:00) | | `--no-weekly` | Disable the weekly backup | | `--task-scheduler` / `--no-task-scheduler` | Use the OS scheduler (default). `--no-task-scheduler` runs in-process and stops when the command exits | Backends: - Windows: Task Scheduler tasks `pfSentinel\DailyBackup` and `pfSentinel\WeeklyBackup`. Run from an Administrator PowerShell. Tasks run while you are signed in (a locked screen counts); a missed run starts at next sign-in. - Linux with systemd: user timers `pfsentinel-daily.timer` and `pfsentinel-weekly.timer`. Missed runs start at next boot. Enable lingering once to run while logged out: `loginctl enable-linger $USER`. - No systemd (WSL without systemd, containers, macOS): tagged crontab lines; the rest of your crontab is left alone. `pfs schedule disable` - Remove the schedule (timers, cron lines or tasks). `pfs schedule status` - Show timer or task state, next run and last result. Warns when the config says enabled but nothing is installed. `pfs schedule run-now` - Back up all devices immediately (same as `pfs backup run`). ### pfs notify `pfs notify test` - Send a test notification to all enabled channels. `pfs notify status` - Show all channel statuses. `pfs notify telegram setup | enable | disable | status` - Telegram bot notifications. Setup: in Telegram open @BotFather, send `/newbot`, copy the token, send any message to your new bot, then run `pfs notify telegram setup` and paste the token; the chat ID is auto-detected. `disable` keeps credentials. `pfs notify slack setup | enable | disable | status` - Slack incoming webhook notifications. Setup: create an app at https://api.slack.com/apps, activate Incoming Webhooks, add one to a channel, copy the URL (starts with `https://hooks.slack.com/services/`), then run `pfs notify slack setup`. `disable` keeps the webhook URL. Windows toast notifications need no setup; they show for backups run while the user is signed in. Linux desktop notifications are planned; on Linux use Telegram or Slack. ### pfs update `pfs update check` - Check GitHub Releases for a newer version. `pfs update install [--yes/-y]` - Download and install the latest version. Detects how pfSentinel was installed: binary (replaces the file, so it must sit in a folder the user can write to), pipx (`pipx upgrade pfsentinel`) or pip (`pip install --upgrade pfsentinel`). `pfs update revert [--yes/-y]` - Roll back to the previous version after a failed update. ## 6. Recipes ### 6.1 First setup of a device (SSH) ``` pfs device add --id home-fw --label "Home Firewall" --host 192.168.1.1 --method ssh -u admin pfs device trust-key home-fw pfs device test -d home-fw pfs backup run -d home-fw --config-only pfs list -d home-fw ``` ### 6.2 SSH key authentication 1. Create a key and print the public half. Linux: ``` ssh-keygen -t ed25519 -C "pfsentinel" -f ~/.ssh/pfsentinel_ed25519 cat ~/.ssh/pfsentinel_ed25519.pub ``` Windows (built-in OpenSSH client): ``` ssh-keygen -t ed25519 -C "pfsentinel" -f $env:USERPROFILE\.ssh\pfsentinel_ed25519 Get-Content $env:USERPROFILE\.ssh\pfsentinel_ed25519.pub ``` 2. In the pfSense web UI open System > User Manager > (your user) > Authorized SSH Keys, paste the `.pub` line and save. pfSense rebuilds `authorized_keys` from its config, so this is the method that survives reboots (do not use ssh-copy-id). 3. Point the device at the private key and test: `pfs device edit home-fw` (SSH private key path: `~/.ssh/pfsentinel_ed25519` on Linux, `C:\Users\YOU\.ssh\pfsentinel_ed25519` on Windows), then `pfs device test -d home-fw`. For a new device, give the path at the "SSH private key path (optional)" prompt of `pfs device add`. A key passphrase is stored in the OS keyring; for unattended schedules on Linux servers a key without a passphrase avoids the desktop-keyring problem. To check the fingerprint shown by `pfs device trust-key`, open a shell on pfSense (console menu option 8, or SSH) and run: ``` for f in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf "$f"; done ``` ### 6.3 Nightly scheduled backups on Linux (runs while logged out) ``` pfs schedule enable --daily-time 01:30 --no-weekly loginctl enable-linger $USER pfs schedule status systemctl --user list-timers 'pfsentinel-*' systemctl --user start pfsentinel-backup.service # run once now to test journalctl --user -u pfsentinel-backup.service -n 50 ``` Desktop keyrings (GNOME Keyring, KWallet) stay locked until you log in, so a backup that runs before login cannot read the password. On a server or headless box, use SSH key auth or rely on the encrypted file store. Without systemd, `pfs schedule enable` writes tagged crontab lines instead (check with `crontab -l`, log in `~/.pfsentinel/logs/scheduled.log`). ### 6.4 Nightly scheduled backups on Windows Open PowerShell with "Run as administrator" (only registering the task needs admin; backups run as the user): ``` pfs schedule enable --daily-time 01:30 --no-weekly pfs schedule status Get-ScheduledTask -TaskPath '\pfSentinel\' | Get-ScheduledTaskInfo Start-ScheduledTask -TaskPath '\pfSentinel\' -TaskName DailyBackup # run once now to test ``` Tasks run while you are signed in (a locked screen is fine), wake the machine, run on battery, and catch up at the next sign-in after a missed run. `LastTaskResult` 0 means success. ### 6.5 ZFS snapshot backups ``` pfs backup snapshot-list -d home-fw pfs backup snapshot -d home-fw --full # first run: full stream pfs backup snapshot -d home-fw # later runs: incremental ``` To include snapshots in every `pfs backup run` (and scheduled runs), set `backup_policy.zfs.enabled` to `true` in `config.json` (open it with `nano "$(pfs config path)"` on Linux or `notepad (pfs config path)` on Windows), then run `pfs config validate`. Defaults: dataset `zroot/ROOT`, incremental on, keep 3 snapshots on the device. On UFS installs use `pfs backup archive -d home-fw` instead. ### 6.6 Notifications to Telegram or Slack ``` pfs notify telegram setup pfs notify slack setup pfs notify status pfs notify test ``` Pause a channel without losing credentials: `pfs notify slack disable`, later `pfs notify slack enable`. Skip notifications for one run: `pfs backup run --no-notify`. ### 6.7 Check schedule health ``` pfs schedule status pfs status pfs backup list -d home-fw --json pfs backup search --date 2026-09-19 ``` OS-level view. Linux: `systemctl --user list-timers 'pfsentinel-*'` and `journalctl --user -u pfsentinel-backup.service --since today`. Windows: `Get-ScheduledTask -TaskPath '\pfSentinel\' | Get-ScheduledTaskInfo | Select-Object TaskName, LastRunTime, LastTaskResult, NextRunTime`. If status warns that the schedule is enabled in config but nothing is installed, run `pfs schedule enable` again. A failed last result points at credentials or connectivity: run `pfs config validate` and `pfs device test`. ### 6.8 Restore a config to pfSense ``` pfs backup search -d home-fw --changes firewall pfs backup diff FILE_A FILE_B pfs backup verify FILE pfs backup restore FILE --target config-restore.xml ``` Then in the pfSense web UI open Diagnostics > Backup & Restore, under Restore Backup choose "All" as the restore area, select `config-restore.xml`, and click Restore Configuration. pfSense reboots to apply it. pfSentinel never writes to the firewall; restoring is always a manual step you control. ### 6.9 Self-update ``` pfs update check pfs update install pfs update revert # only if the new version misbehaves ``` Binary installs must be in a folder the user can write to (`~/.local/bin` on Linux, `%LOCALAPPDATA%\pfSentinel` on Windows). A copy in `/usr/local/bin` needs `sudo pfs update install`. ## 7. Troubleshooting - SSH connection refused: enable SSH on pfSense (System > Advanced > Admin Access > Secure Shell), then `pfs device test -d ID`. - Host key unknown or changed: run `pfs device trust-key ID`. If you did not reinstall or re-key the firewall, a changed key can mean a man-in-the-middle; check the fingerprint on the pfSense console first. - TLS certificate errors (HTTPS): pfSense ships a self-signed certificate. Use SSH, or add the device with `--no-verify-ssl` or change it with `pfs device edit ID`. - Windows, scheduled task fails with `0x80070002` (ERROR_FILE_NOT_FOUND): the task points at a pfs executable that no longer exists (moved, uninstalled, or registered by an older version). Re-run `pfs schedule enable` from an Administrator PowerShell. - Windows, "Administrator privileges are required" on `pfs schedule enable`: open PowerShell with "Run as administrator" and run it again. - Windows, `pfs` is not recognized: open a new PowerShell window after installing (PATH changes apply to new windows only), or run `py -m pfsentinel`. - Linux, `pfs: command not found`: `~/.local/bin` is not on PATH yet; run `pipx ensurepath` and open a new terminal. - Linux, "externally-managed-environment" from pip: use `pipx install pfsentinel`. - Backup skipped, no changes: expected. Config backups are saved only when the config changed. - Linux desktop, scheduled run cannot read the password: the desktop keyring is locked before login. Use SSH key auth or stay logged in. - Still stuck: open an issue at https://github.com/NX1X/pfSentinel/issues with the output of `pfs --version` and the failing command. ## 8. Links - Web manual: https://pfs.nx1xlab.dev/docs/ - Website: https://pfs.nx1xlab.dev/ - Source and issues: https://github.com/NX1X/pfSentinel - Releases (binaries + checksums): https://github.com/NX1X/pfSentinel/releases - PyPI: https://pypi.org/project/pfsentinel/ - Changelog: https://github.com/NX1X/pfSentinel/blob/main/CHANGELOG.md - License: Apache-2.0