Chapters & navigation

VERSIONED REFERENCES

Deployment reference

The complete historical v4.04.3 deployment contract: packages, configuration, firewall service ownership and audit.

v4.04.3Updated October 1, 202629 min read
On this page

This tutorial describes the Linux-only v4.04.3 release contract. The version-specific v4.04.0 change set was first published in v4.04.2 and is carried forward in v4.04.3. Use a version only after its exact Release inventory is public and the protected qualification is green for this historical v4.04.3 contract. The new IVV/IVVQ strategy defines future assurance by release track; it does not alter this baseline's package inventory or recorded acceptance requirements.

For local testing of newer source, use the separate build and install from source procedure. Its version-specific candidate instructions do not change this stable release baseline.

Keep verified console or SSH recovery access before changing packages, firewall, SSH, HA or migration state.

1. Supported package matrix#

Distribution familyArchitectureExpected package
Debian or Ubuntuamd64syswarden_4.04.3_amd64.deb
Fedora or RHEL familyx86_64syswarden-4.04.3-1.x86_64.rpm
Alpinex86_64syswarden_4.04.3_x86_64.apk

The package workflow is configured to generate one DEB, one RPM and one APK package plus SHA256SUMS.txt. No current package, updater or qualification route exists outside this AMD64/x86_64 matrix. Native package lifecycle evidence, not a filename or successful build, decides whether a target is releasable.

2. Recovery prerequisites#

Before installation or upgrade:

  1. retain a verified local console or second SSH session;
  2. save /etc/syswarden, required list files and HA trust material;
  3. save the active firewall service state, nftables ruleset and installed package version;
  4. verify that TCP 62027 is blocked at the host boundary;
  5. verify that the applicable recovery artifact and recovery channel work on a representative host. For the version-specific migration to v4.03.3, that artifact is a complete pre-upgrade VM or volume snapshot; no in-place package downgrade is supported;
  6. record any unrelated process that already owns a path, service name or port that migration cleanup may inspect.

Do not rely on an HA withdrawal, package hook or remote dashboard as the only recovery path.

3. Exact public release inventory#

A qualified v4.04.3 Release contains exactly these ten assets:

  1. syswarden_4.04.3_amd64.deb
  2. syswarden-4.04.3-1.x86_64.rpm
  3. syswarden_4.04.3_x86_64.apk
  4. SHA256SUMS.txt
  5. RELEASE_SHA256SUMS.txt
  6. syswarden-release.tar.gz
  7. syswarden-sbom.spdx.json
  8. plumber-report.zip
  9. syswarden-update-manifest-v1.json
  10. syswarden-update-manifest-v1.json.sig

Reject a Release with a missing, duplicate or unexpected asset. The package checksum inventory, release checksum inventory and detached Ed25519 update signature must all verify before publication or installation.

4. Verify and install one package#

For packages built from the latest source, follow Build and Install from Source. The steps below use the qualified stable release.

Download exactly one package matching the host and SHA256SUMS.txt from the same Release. Verify the selected package before invoking its package manager:

console
sha256sum --check --ignore-missing SHA256SUMS.txt

Stop unless the selected filename appears exactly once in the manifest and is reported as valid. On Debian, Ubuntu, Fedora or a RHEL-family host, run only the matching command:

console
sudo apt-get update
sudo apt-get install -y ./syswarden_4.04.3_amd64.deb
sudo dnf install -y ./syswarden-4.04.3-1.x86_64.rpm

The APT index refresh uses only the distribution repositories already configured on the host. Stop if those sources are unsigned, unexpected or do not expose every declared package dependency. Do not enable an unreviewed third-party repository to make the transaction proceed.

On an online Alpine host, prepare Cronie first when automatic activation is required:

sh
set -eu
sudo apk add --no-cache cronie cronie-openrc
for service in crond cronie; do
  if sudo rc-service "${service}" status >/dev/null 2>&1; then
    sudo rc-service "${service}" stop
  fi
  for link in /etc/runlevels/*/"${service}"; do
    [ -L "${link}" ] || continue
    sudo rc-update del "${service}" "$(basename "$(dirname "${link}")")"
  done
done
sudo rc-update add cronie default
sudo rc-service cronie start
sudo apk add --allow-untrusted ./syswarden_4.04.3_x86_64.apk

The automatic path requires committed cronie and cronie-openrc, an active Cronie service assigned only to the default runlevel, and an inactive BusyBox crond with no runlevel. apk-tools 3 can commit package payload after a failed pre-install script, so v4.04.3 does not return a failing fresh hook for an unprepared scheduler. A fresh APK transaction without prepared Cronie remains payload-only and inactive: no SysWarden configuration, data, logs, runtime, cron, service, process or firewall policy is created, and the package is not marked broken. The immutable payload already contains the two package-owned launchers, but neither performs activation.

For the deferred payload-only path, the package prints a bounded two-phase activation block. It validates the complete OpenRC inventory before its first mutation, executes fail-fast and reattests the exact provider and runlevel state before activation. Run that emitted block exactly, only after the initial APK transaction has completed and both Cronie packages are present. Do not replace it with shortened scheduler commands. If the exact emitted block and its package-manager log are unavailable, do not activate the payload manually; repeat the transaction on a disposable recovered host and preserve the output.

--allow-untrusted is acceptable only after the exact SHA-256 check has succeeded. A prepared automatic installation or the exact deferred activation path mutates dependencies, services, firewall policy, hardening and scheduled jobs. Review package scripts and use a disposable lifecycle host before production rollout.

During installation, the supported multi-origin OSINT path may encounter a syntactically valid private, 6to4 or other non-public or special-use host entry. The candidate discards that entry before consensus and emits a bounded warning containing only the normalized origin and discarded-entry count. It does not publish the entry. Malformed syntax, an insufficient source after filtering and custom feed validation failures still stop installation. On DEB hosts, verify that package configuration completed and that dpkg --audit reports no half-configured package before continuing.

New Data-Shield content still requires matching canonical bytes from at least two independent HTTPS origins. During package installation only, a validly configured external fetch failure, rejected remote candidates or content disagreement preserves an exact validated last-known-good file; if none exists, the optional Data-Shield contribution is omitted and the independent OSINT path continues. One mirror is never published alone. Invalid mirror configuration, caller cancellation, unsafe local state and publication errors remain fatal. The explicit or scheduled update-feeds path reports quorum loss as a failure after reapplying validated policy, so monitoring remains truthful.

Version-specific GeoIP and blocklist sources in v4.04.0#

The historical v4.04.0 change set was first published in the historical v4.04.2 release and is carried forward in v4.04.3. It embeds one deterministic GeoIP allocation snapshot inside the SysWarden CLI. It is generated from the CC0-1.0 IPverse country-ip-blocks repository, whose country files represent RIR allocation or delegation data. The generator validates the immutable source archive, its complete CC0 legal text, the JSON and plaintext forms of every address family, canonical public prefixes and deterministic accounting before embedding. Runtime validation then revalidates the complete snapshot structure and content identity. If a source allocation only partly overlaps special-use space, the generator removes the reserved portion and retains the exact canonical public remainder; it does not discard the whole public allocation.

The reviewed source boundary is exact:

  • commit: 7443b1e07cae182ac864a2a4247ea4b641970dfe;
  • commit timestamp: 2026-08-28T01:55:22Z;
  • archive size: 1760020 bytes;
  • archive SHA-256: ab2de300d06fcf63cdaac7f3cb093d17a1d509b2a32455c731e18331506b28a8;
  • CC0 license SHA-256: a2010f343487d3f7618affe54f789f5487602331c0a8d03f49e9a7c547cf0499;
  • embedded snapshot ID: ipverse-20260828T015522Z-07f1cb9cae15413e;
  • embedded snapshot gzip SHA-256: d467540cef649539488dbeab76dbf808349d000a6fedfef7cdc66735e2339400.

The exact source URL, commit, timestamp, archive size, archive digest and license digest are part of the embedded snapshot metadata. Only an official SysWarden release whose signed manifest and package digest verify successfully authenticates the reviewed CLI and snapshot bytes. A local or otherwise unsigned build provides deterministic content binding and structural validation, not official release authenticity.

Both network.geo.blocked_countries and strict network.geo.allowed_countries select prefixes directly from this embedded snapshot.

Runtime operation performs no IPverse or other country-data request. It does not read or require <country>.ipv4, <country>.ipv6, allowed_<country>.ipv4 or allowed_<country>.ipv6 files in /etc/syswarden/lists, and existing country files cannot override the embedded selection.

All 249 canonical ISO 3166 alpha-2 codes are valid configuration values. The pinned source has no allocation data for bv, cc, cx, eh, gs, hm, pn, sh, sj, tf and um; these remain valid codes with empty IPv4 and IPv6 contributions. It has IPv4 data but no IPv6 data for cf, er, fk, kp, ms and yt; that missing family is a valid empty contribution. Empty contributions add nothing to a deny selection.

Strict allow policy is intentionally more restrictive. SysWarden merges the configured GeoIP and operator-provisioned ASN allow populations before any nftables mutation. If both merged IPv4 and IPv6 populations are empty, policy application is rejected before mutation. If exactly one merged family is empty, the empty set remains fail-closed and all non-whitelisted traffic for that family is denied. Qualify both address families and preserve verified recovery access before enabling strict allow mode.

eu and zz are not supported country codes. Replace either legacy value with the explicit canonical ISO country codes required by the policy before upgrade, configuration validation or reload. SysWarden never expands eu into a mutable regional grouping and never treats zz as a country.

syswarden update-feeds validates every configured country code against the embedded snapshot and reports its immutable snapshot identifier and selection counts. It performs no country-data network request. Outside LAN mode, the command separately refreshes configured external Data-Shield, custom and OSINT inputs, so one of those external paths can still fail independently.

IP country data describes address allocation or assignment and is not an authoritative statement of where an address is currently used. Routing, transfers, anycast, VPNs and proxies can make allocation-based policy differ from physical location. The pinned source currently attributes 199.19.76.0/23 to both nl and us; either country selection therefore contains that prefix. Treat strict country allow mode as a lockout-sensitive allocation policy, not an identity control, and retain verified console or independent SSH recovery access.

Source and license: IPverse country-ip-blocks, CC0-1.0. Linux packages install the intact upstream legal text as /usr/share/doc/syswarden/GEOIP-DATA-LICENSE.txt, and the embedded snapshot binds the same license digest.

Blocklist selection remains independent of GeoIP:

  • choices 1 and 2 select the standard and critical multi-origin Data-Shield feeds. They require quorum and do not require a custom SHA-256 value; leave all custom URL and hash fields empty;
  • choice 3 is the custom mode. Configure at least one absolute HTTPS URL and pair every configured IPv4 or IPv6 URL with its exact SHA-256 digest;
  • choice 4 disables the Data-Shield, custom and OSINT blocklist contributions.

The embedded GeoIP snapshot does not supply ASN policy. Configured ASN deny and strict-allow selections remain operator-provisioned as described in the image procedure below. Unsigned single-origin RADB and Spamhaus data is not accepted as firewall authority; update-feeds reports that boundary and leaves any existing operator-provisioned ASN files unchanged.

The historical public v4.02.8 binary predates the signed updater protocol. Its historical first hop to v4.03.2 must use a separately downloaded and checksum-verified Linux package. Use the dedicated historical v4.02.8 to v4.03.2 migration runbook for the Debian 13 backup, provenance, acceptance and rollback sequence. Use the separate version-specific v4.03.2 to v4.03.3 migration runbook for the four dynamic nftables set diagnosis, legacy interval removal, the recorded Ubuntu 26.04 OSINT installation gate and restricted rollback boundary. After a qualified signed-protocol release is installed, syswarden update verifies the canonical manifest, detached Ed25519 signature, platform identity, package size and SHA-256 immediately before installation.

5. Optional RHEL-compatible image staging#

The RHEL 9+ image extensions page records the exact availability boundary. Only the offline RPM staging extension described below exists in v4.04.3.

The repository includes a separate, opt-in image-builder extension for an extracted, fresh and unmounted RHEL-family 9 or newer root. It is not called by the normal package installation, update or reload paths.

The image recipe must prepare all RPM dependencies first. The following RHEL 9 reference uses firewalld as the preserved frontend and prepares Cronie as the required scheduler. The image root must be disposable and unmounted:

bash
set -euo pipefail
IMAGE_ROOT=/srv/image-root
CANDIDATE_RPM=/srv/image-input/syswarden-4.04.3-1.x86_64.rpm
# Copy this value from the independently authenticated package inventory.
EXPECTED_RPM_SHA256=REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS

[[ "${EXPECTED_RPM_SHA256}" =~ ^[0-9a-f]{64}$ ]]
printf '%s  %s\n' "${EXPECTED_RPM_SHA256}" "${CANDIDATE_RPM}" | sha256sum --check --strict -
sudo extensions/rhel-image/stage-syswarden-rhel-image.sh \
  --preflight-root \
  --root "${IMAGE_ROOT}"

sudo dnf -y --installroot="${IMAGE_ROOT}" --releasever=9 \
  --setopt=install_weak_deps=False install \
  nftables ipset curl wget rsyslog cronie bash-completion \
  wireguard-tools jq checkpolicy policycoreutils-python-utils \
  dnf-automatic procps-ng e2fsprogs firewalld
sudo systemctl --root="${IMAGE_ROOT}" enable crond.service firewalld.service
sudo systemctl --root="${IMAGE_ROOT}" disable nftables.service

qrencode is optional on RHEL-family images. Install it only from an operator-approved repository when terminal WireGuard QR rendering is required; the protected client configuration file remains available without it.

The read-only --preflight-root call must precede dnf and systemctl --root. It rejects /, a non-canonical or symlinked root, unsafe ancestor directories, mounts below the image root and live runtime markers. Stop on any failure. It performs no image or host mutation.

The image recipe may use repositories and service-manager tooling while it prepares the tree. The extension itself does neither. Do not install or enable iptables-services for this profile. Unmount every temporary image-builder mount before invoking the extension.

Verify the exact candidate RPM and invoke the extension from the source tree:

bash
set -euo pipefail
IMAGE_ROOT=/srv/image-root
CANDIDATE_RPM=/srv/image-input/syswarden-4.04.3-1.x86_64.rpm
# Copy this value from the independently authenticated package inventory.
EXPECTED_RPM_SHA256=REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS

[[ "${EXPECTED_RPM_SHA256}" =~ ^[0-9a-f]{64}$ ]]
printf '%s  %s\n' "${EXPECTED_RPM_SHA256}" "${CANDIDATE_RPM}" | sha256sum --check --strict -
sudo extensions/rhel-image/stage-syswarden-rhel-image.sh \
  --root "${IMAGE_ROOT}" \
  --rpm "${CANDIDATE_RPM}" \
  --sha256 "${EXPECTED_RPM_SHA256}"

Never derive the expected digest from the candidate RPM. Obtain it from the separately authenticated package inventory.

The extension installs that RPM with plugins, package scripts and triggers disabled. It does not enter the root or invoke product binaries, services, firewall tools, kernel-policy tools, cron, network endpoints or process signals during staging. A transaction journal permits bounded resume after an interruption; altered or unknown state fails closed.

After the extension succeeds, prepare three image-owner source files below /srv/image-input/syswarden-config. This step must follow staging because the extension accepts only a fresh image at entry. Save this first block as config.toml with owner root:root and mode 0640:

toml
schema_version = 1

[core]
config_dir = "/etc/syswarden/config/modules"
enterprise_mode = false
log_level = "INFO"

Save this block as modules/00-core.toml with owner root:root and mode 0640:

toml
[core]
firewall_backend = "keep"
hardening_enabled = false
cis_l2_hardening = false
secure_wipe_conf = false
ssh_port = ""

Save this block as modules/10-network.toml with owner root:root and mode 0640:

toml
[network]
whitelist_infra = true
lan_subnets = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
whitelist_ips = []
interfaces = ""

[network.geo]
enabled = true
blocked_countries = ["ru", "cn", "kp", "ir"]
allowed_countries = []

[network.asn]
enabled = true
# This intentionally invalid value blocks first boot until it is replaced.
blocked_asns = ["REPLACE_WITH_APPROVED_HIGH_RISK_ASN"]
allowed_asns = []

[network.saas]
allow_monitors = false

[network.blocklists]
list_choice = "1"
custom_url = ""
custom_url_ipv6 = ""
custom_hash = ""
custom_hash_ipv6 = ""
use_spamhaus = false

[network.wireguard]
enabled = false
port = "51820"
subnet = ""

This profile selects standard blocklist choice 1, automatic infrastructure protection, explicit country and ASN deny sets, no remote monitor allowlisting, and no WireGuard. The country values are a reviewable example, not a universal traffic policy. Remove a country if the deployed services require it. Replace the intentionally invalid ASN marker with the exact high-risk ASN values from the image owner's current approved risk register. Until it is replaced, configuration validation fails and the first-boot marker is retained. The repository does not assign risk labels to network operators.

After replacing the ASN marker in the protected source file, publish all three configuration files as one directory transaction from a trusted root shell:

bash
set -euo pipefail
umask 077
if (( EUID != 0 )) || [[ "${GROUPS[0]}" != 0 ]]; then
  printf '%s\n' 'Run this complete configuration publication block as root.' >&2
  exit 1
fi
unset BASH_ENV ENV CDPATH GLOBIGNORE LD_PRELOAD LD_LIBRARY_PATH PYTHONPATH
PATH=/usr/bin:/bin
LC_ALL=C
export PATH LC_ALL
IMAGE_ROOT=/srv/image-root
CONFIG_SOURCE=/srv/image-input/syswarden-config
CONFIG_DESTINATION="${IMAGE_ROOT}/etc/syswarden/config"
CONFIG_STAGE="${IMAGE_ROOT}/etc/syswarden/.config.pending-v1"
CONFIG_FILES=(config.toml modules/00-core.toml modules/10-network.toml)

extensions/rhel-image/stage-syswarden-rhel-image.sh \
  --preflight-root \
  --root "${IMAGE_ROOT}"
for SOURCE_DIRECTORY in \
  /srv \
  /srv/image-input \
  "${CONFIG_SOURCE}" \
  "${CONFIG_SOURCE}/modules"; do
  [[ -d "${SOURCE_DIRECTORY}" && ! -L "${SOURCE_DIRECTORY}" ]]
  [[ "$(/usr/bin/stat -c '%u:%g' -- "${SOURCE_DIRECTORY}")" == "0:0" ]]
  SOURCE_MODE=$(/usr/bin/stat -c '%a' -- "${SOURCE_DIRECTORY}")
  (( (8#${SOURCE_MODE} & 8#022) == 0 ))
done
for CONFIG_FILE in "${CONFIG_FILES[@]}"; do
  SOURCE_FILE="${CONFIG_SOURCE}/${CONFIG_FILE}"
  [[ -f "${SOURCE_FILE}" && ! -L "${SOURCE_FILE}" ]]
  [[ "$(/usr/bin/stat -c '%u:%g:%a:%h' -- "${SOURCE_FILE}")" == "0:0:640:1" ]]
done

SYSWARDEN_PARENT="${IMAGE_ROOT}/etc/syswarden"
[[ ! -L "${SYSWARDEN_PARENT}" ]]
[[ ! -e "${SYSWARDEN_PARENT}" || -d "${SYSWARDEN_PARENT}" ]]
/usr/bin/install -d -o root -g root -m 0750 "${SYSWARDEN_PARENT}"
[[ ! -e "${CONFIG_DESTINATION}" && ! -L "${CONFIG_DESTINATION}" ]]
[[ ! -e "${CONFIG_STAGE}" && ! -L "${CONFIG_STAGE}" ]]
/usr/bin/install -d -o root -g root -m 0750 "${CONFIG_STAGE}/modules"
for CONFIG_FILE in "${CONFIG_FILES[@]}"; do
  /usr/bin/install -o root -g root -m 0640 \
    "${CONFIG_SOURCE}/${CONFIG_FILE}" "${CONFIG_STAGE}/${CONFIG_FILE}"
  /usr/bin/cmp --silent -- \
    "${CONFIG_SOURCE}/${CONFIG_FILE}" "${CONFIG_STAGE}/${CONFIG_FILE}"
done
/usr/bin/mv --no-clobber --no-target-directory \
  "${CONFIG_STAGE}" "${CONFIG_DESTINATION}"
[[ ! -e "${CONFIG_STAGE}" && -d "${CONFIG_DESTINATION}" && ! -L "${CONFIG_DESTINATION}" ]]
[[ "$(/usr/bin/stat -c '%u:%g:%a' -- "${CONFIG_DESTINATION}")" == "0:0:750" ]]
[[ "$(/usr/bin/stat -c '%u:%g:%a' -- "${CONFIG_DESTINATION}/modules")" == "0:0:750" ]]
for CONFIG_FILE in "${CONFIG_FILES[@]}"; do
  [[ "$(/usr/bin/stat -c '%u:%g:%a:%h' -- "${CONFIG_DESTINATION}/${CONFIG_FILE}")" == "0:0:640:1" ]]
  /usr/bin/cmp --silent -- \
    "${CONFIG_SOURCE}/${CONFIG_FILE}" "${CONFIG_DESTINATION}/${CONFIG_FILE}"
done

If this block fails or leaves a pending directory, discard and rebuild the disposable image root.

The historical v4.04.0 change set was first published in the historical v4.04.2 release and is carried forward in v4.04.3. It supplies the configured country selections from its embedded release-bound snapshot, including during image first boot. When delivered through an official signed release, the signed manifest and package digest authenticate the binary and its snapshot. A locally built or otherwise unsigned binary provides content binding and structural validation, not release authenticity. Do not stage country files in /etc/syswarden/lists.

Every configured ASN still requires one non-empty canonical IPv4 file and one non-empty canonical IPv6 file in /etc/syswarden/lists. For a blocked ASN, name the pair <AS-number>.ipv4 and <AS-number>.ipv6. For a strict-allow ASN, use allowed_<AS-number>.ipv4 and allowed_<AS-number>.ipv6. Replace the marker in the example TOML before creating the blocked-ASN filenames. Generate the CIDRs through an authenticated image-owner pipeline, verify an exact SHA256SUMS manifest, and publish only regular non-symlink files as root with mode 0640. Each file contains one canonical IP or CIDR per line for the exact ASN and address family. Runtime validation rejects missing or empty files, mixed-family entries, default routes, IPv4 prefixes broader than /24, and IPv6 prefixes broader than /64 before nftables policy publication. The authenticated image-owner pipeline must exclude private and other non-public ranges because they do not establish ASN ownership.

Direct RADB or single-origin Spamhaus downloads are not authenticated authority for ASN policy. Existing verified operator-provisioned files remain unchanged.

Publish the exact policy set as one exclusive transaction from a trusted root shell. The block refuses partial per-command elevation. Replace the invalid ASN in both the TOML and APPROVED_ASNS before running it:

bash
set -euo pipefail
umask 077
if (( EUID != 0 )) || [[ "${GROUPS[0]}" != 0 ]]; then
  printf '%s\n' 'Run this complete policy publication block as root.' >&2
  exit 1
fi
unset BASH_ENV ENV CDPATH GLOBIGNORE LD_PRELOAD LD_LIBRARY_PATH PYTHONPATH
PATH=/usr/bin:/bin
LC_ALL=C
export PATH LC_ALL
IMAGE_ROOT=/srv/image-root
POLICY_SOURCE=/srv/image-input/policy-lists
extensions/rhel-image/stage-syswarden-rhel-image.sh \
  --preflight-root \
  --root "${IMAGE_ROOT}"
POLICY_DESTINATION="${IMAGE_ROOT}/etc/syswarden/lists"
POLICY_STAGE="${IMAGE_ROOT}/etc/syswarden/.lists.pending-v1"
# Copy this value from the authenticated image-owner policy inventory.
EXPECTED_POLICY_MANIFEST_SHA256=REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS
APPROVED_ASNS=(REPLACE_WITH_APPROVED_HIGH_RISK_ASN)

[[ "${EXPECTED_POLICY_MANIFEST_SHA256}" =~ ^[0-9a-f]{64}$ ]]
printf '%s  %s\n' "${EXPECTED_POLICY_MANIFEST_SHA256}" \
  "${POLICY_SOURCE}/SHA256SUMS" | /usr/bin/sha256sum --check --strict -

POLICY_FILES=()
for APPROVED_ASN in "${APPROVED_ASNS[@]}"; do
  [[ "${APPROVED_ASN}" =~ ^AS[1-9][0-9]{0,9}$ ]]
  ASN_NUMBER="${APPROVED_ASN#AS}"
  (( 10#${ASN_NUMBER} <= 4294967295 ))
  POLICY_FILES+=("${APPROVED_ASN}.ipv4" "${APPROVED_ASN}.ipv6")
done

mapfile -t MANIFEST_LINES < "${POLICY_SOURCE}/SHA256SUMS"
MANIFEST_FILES=()
declare -A EXPECTED_POLICY_SET=()
for POLICY_FILE in "${POLICY_FILES[@]}"; do
  [[ -z "${EXPECTED_POLICY_SET[${POLICY_FILE}]+x}" ]]
  EXPECTED_POLICY_SET["${POLICY_FILE}"]=1
done
declare -A MANIFEST_POLICY_SET=()
for MANIFEST_LINE in "${MANIFEST_LINES[@]}"; do
  [[ "${MANIFEST_LINE}" =~ ^[0-9a-f]{64}\ \ ([A-Za-z0-9]+\.(ipv4|ipv6))$ ]]
  MANIFEST_FILE="${BASH_REMATCH[1]}"
  [[ -z "${MANIFEST_POLICY_SET[${MANIFEST_FILE}]+x}" ]]
  MANIFEST_POLICY_SET["${MANIFEST_FILE}"]=1
  MANIFEST_FILES+=("${MANIFEST_FILE}")
done
(( ${#EXPECTED_POLICY_SET[@]} == ${#MANIFEST_POLICY_SET[@]} ))
for POLICY_FILE in "${POLICY_FILES[@]}"; do
  [[ -n "${MANIFEST_POLICY_SET[${POLICY_FILE}]+x}" ]]
done

(
  cd "${POLICY_SOURCE}"
  /usr/bin/sha256sum --check --strict SHA256SUMS
)
[[ ! -e "${POLICY_DESTINATION}" && ! -L "${POLICY_DESTINATION}" ]]
[[ ! -e "${POLICY_STAGE}" && ! -L "${POLICY_STAGE}" ]]
/usr/bin/install -d -o root -g root -m 0750 "${POLICY_STAGE}"
for POLICY_FILE in "${POLICY_FILES[@]}"; do
  [[ -f "${POLICY_SOURCE}/${POLICY_FILE}" && ! -L "${POLICY_SOURCE}/${POLICY_FILE}" ]]
  /usr/bin/install -o root -g root -m 0640 \
    "${POLICY_SOURCE}/${POLICY_FILE}" "${POLICY_STAGE}/${POLICY_FILE}"
done
/usr/bin/install -o root -g root -m 0640 \
  "${POLICY_SOURCE}/SHA256SUMS" "${POLICY_STAGE}/SHA256SUMS"
printf '%s  %s\n' "${EXPECTED_POLICY_MANIFEST_SHA256}" \
  "${POLICY_STAGE}/SHA256SUMS" | /usr/bin/sha256sum --check --strict -
(
  cd "${POLICY_STAGE}"
  /usr/bin/sha256sum --check --strict SHA256SUMS
)
/usr/bin/mv --no-clobber --no-target-directory \
  "${POLICY_STAGE}" "${POLICY_DESTINATION}"
[[ ! -e "${POLICY_STAGE}" && -d "${POLICY_DESTINATION}" && ! -L "${POLICY_DESTINATION}" ]]
printf '%s  %s\n' "${EXPECTED_POLICY_MANIFEST_SHA256}" \
  "${POLICY_DESTINATION}/SHA256SUMS" | /usr/bin/sha256sum --check --strict -
(
  cd "${POLICY_DESTINATION}"
  /usr/bin/sha256sum --check --strict SHA256SUMS
)

The block copies no glob and no unlisted input. If it fails or leaves a pending directory, discard and rebuild the disposable image root.

The extension enables a marker-guarded unit that requires crond.service. On the real first boot, the normal install and reload workflows validate the configuration, prove a live enabled Cronie provider, preserve the active firewalld frontend through keep, and converge runtime state. nftables remains the authoritative SysWarden policy engine.

Run the image builder's normal SELinux relabel, ownership, bootloader, package inventory and ISO assembly steps. Boot a disposable target and verify:

console
sudo systemctl is-active crond.service firewalld.service
sudo systemctl is-enabled crond.service firewalld.service
sudo systemctl status syswarden-image-firstboot.service
sudo journalctl -u syswarden-image-firstboot.service --no-pager
sudo syswarden config validate --path /etc/syswarden/config
sudo nft -j list table inet syswarden
sudo test ! -e /var/lib/syswarden/image/firstboot.pending

Confirm recovery access and expected business traffic before reusing the image recipe. A failed first-boot unit retains its marker and retries on the next boot; correct the cause rather than deleting the marker.

Offline staging is not runtime-readiness evidence. ISO assembly, signing, bootloader integration and target-host validation remain separate image-builder responsibilities. See the repository extension README for the complete boundary and recovery contract.

6. Safe upgrade offboarding#

The current product has no network terminal service. Upgrade qualification must prove that verified historical SysWarden-owned listener, credential, browser asset and service state is removed while unrelated configuration and unrelated processes are preserved.

After upgrade:

  1. confirm the package owns no TCP 62027 listener or firewall permission;
  2. confirm only the native local TUI entry point remains;
  3. confirm no remote PTY, browser asset or token-management route remains;
  4. confirm an unrelated process using the same numeric port was not stopped;
  5. confirm unrelated configuration was not deleted;
  6. confirm the core, authoritative nftables policy and selected firewall service state pass their normal health checks.

A downgrade is an emergency operation and can restore an older network surface. Keep TCP 62027 blocked at the host boundary and do not restore retired authentication material.

7. Configuration layout#

The modular root is /etc/syswarden/config. Later module filenames have higher precedence. Back up the complete directory, validate the candidate and inspect the effective diff before applying it.

schema_version = 1 is the current modular schema. When schema_version is absent, the configuration is treated as historical input. An explicit future, negative or non-integer schema version fails closed.

console
sudo syswarden config validate --path /etc/syswarden/config
sudo syswarden config
sudo syswarden reload

syswarden config validate is read-only. All unknown and deprecated keys are reported as diagnostics; a semantic or structural violation still fails validation. The CLI validator and core loader enforce the same semantic matrix, including SSH/HA port separation while WireGuard is enabled.

Firewall service selection#

core.firewall_backend accepts keep, nftables or iptables:

  • keep is the default and performs no firewall service transition, but refuses policy mutation while an iptables-services or netfilter-persistent service is active or enabled;
  • nftables validates that nftables.service is already active and enabled and refuses any active or enabled firewalld, UFW, iptables-services or netfilter-persistent frontend;
  • iptables remains parseable for configuration compatibility, but operational firewall policy mutation paths reject it in v4.04.3 before changing persistent policy inputs or kernel firewall state.

Operational policy mutation requires an active, unambiguous supported service manager. Package hooks running with no service-manager runtime defer install and reload instead of invoking host firewall or kernel tools.

The packaged candidate always validates and commits the authoritative SysWarden nftables policy. If exactly one supported firewalld or UFW frontend is already active, SysWarden may reconcile only bounded owned compatibility rules. Installed but inactive frontends are not activated. Automatic service migration is outside the qualified v4.04.3 contract. Prepare and verify any required service change through an independent recovery procedure before selecting nftables, then run syswarden reload from a verified recovery session.

Native host and CIDR mutations in nftables interval sets use a start element and the exact exclusive end marker required by the kernel. The manager repairs a detected unterminated start before replacement and rejects ambiguous state or an interval containing internal boundaries. An exclusive end marker left alone after timed-start expiry is treated as functionally absent; a later re-ban must install and verify one complete pair. Unit tests are supporting evidence only. The protected release still requires add, timed renewal, permanent replacement, replay and removal for both address families in the isolated real-kernel nftables laboratory.

WireGuard requires the explicit nftables backend. The bounded firewalld and UFW compatibility path does not open the WireGuard UDP port or forwarding rules. Active firewalld and UFW target-host execution remains outside the qualified evidence.

Recover exact historical WireGuard state#

Availability: The following is a version-specific v4.10.0 source procedure. The public stable CLI does not provide recover-wireguard. Do not run these commands on that release or infer availability from this page's stable baseline. The candidate implementation remains subject to release validation under the IVV/IVVQ strategy.

An upgrade can encounter an unmarked inet syswarden_wg table created by an older SysWarden generation. Reload and uninstall remain fail-closed when the table cannot be proven against the current ownership manifest. Do not delete the table or shared forwarding rules manually.

Keep verified console access, stop and disable the historical WireGuard service, and confirm that its interface is absent. Then generate a read-only, secret-redacted recovery plan:

console
sudo syswarden recover-wireguard

The command accepts only the exact historical pre-v3.75.7 wg0 generation or the exact historical v3.75.7 through historical v4.03.0 wg-syswarden generation. It refuses unknown tables, changed configuration, active services, present interfaces, duplicate rules and ambiguous ownership. Review the JSON plan and its SHA-256 digest. Apply only that exact plan with the digest printed by the dry run:

console
sudo syswarden recover-wireguard --apply --plan-sha256 <exact-sha256>

The apply phase reattests the host state immediately before the bounded nftables transaction and verifies that the historical state is absent. If any evidence changes, generate and review a new plan. Run syswarden reload only after recovery succeeds.

Use syswarden config migrate for the structured migration path. Preview it with --dry-run before publishing any file. syswarden config migrate --dry-run performs zero source or destination writes, including when a previous migration transaction requires recovery. syswarden migrate-config remains a compatibility alias with the same transactional and dry-run contract.

A historical SYSWARDEN_FIREWALL_BACKEND="firewalld" value migrates to core.firewall_backend = "keep", preserving the existing service without an automatic transition. A historical configuration that enables WireGuard with any backend other than nftables is rejected for explicit operator remediation rather than silently changing the backend choice.

The official SaaS monitor setting is disabled unless explicitly enabled:

toml
[network.saas]
allow_monitors = false

network.saas.allow_monitors takes precedence over the deprecated integrations.saas.enabled alias; if neither is set, monitor allowlisting is disabled. Enabled monitor feeds must use absolute HTTPS URLs without credentials or fragments. TLS 1.3 is required, redirects are rejected and each download is bounded to 10 seconds, 1 MiB, 1,024 bytes per line, 20,000 lines and 10,000 entries. A required-feed, syntax or bound failure preserves the previous lists. Valid canonical IPv4 and IPv6 results are published as one lock-coordinated atomic pair with an SHA-256 pair manifest and rollback on publication failure.

WAAP log settings use canonical single-space-separated absolute patterns. Configured globs are reduced to verified exact real regular-file matches. The core opens them descriptor-relative without following links and revalidates the file identity and type after every rotation; it never creates a missing target. Rsyslog inputs are emitted only for paths that pass the same validation at configuration time, but rsyslog later reopens those names itself, so operators must keep every parent directory protected against untrusted replacement. Rsyslog strings and WireGuard values are validated and encoded for their destination grammars before configuration publication.

Persistent list grammar#

Blocklist, whitelist and SSH-exception values use exact canonical IPv4 or IPv6 addresses and masked CIDRs. Hostnames, address zones, IPv4-mapped IPv6 values, invalid ports, control characters and ambiguous substring matches are rejected. The address family must match the destination list.

Use the explicit flag to limit a whitelist entry to one TCP service:

console
sudo syswarden whitelist 192.0.2.10 --port 443

--port scopes a whitelist entry to one TCP service; omitting it creates an address-wide whitelist entry. SSH exceptions are rendered only for the effective SSH port. A port-qualified SSH entry must equal that effective port, and a changed-port mismatch fails closed before candidate policy application. Reconcile the SSH exception registry from verified console access before applying an SSH port change.

Minimal authenticated HA configuration:

toml
[integrations.ha]
enabled = true
peer_ips = ["192.0.2.10", "192.0.2.11"]
peer_port = 62026
token = "replace-with-a-secret-from-a-protected-channel"

[integrations.bunkerweb]
enabled = true

Do not commit a real token. Exact peer IPs may be dialed. Canonical CIDRs authorize inbound peers only and are never outbound destinations. If /etc/syswarden/ha-ca.pem exists, clients use it as the exclusive HA trust pool; otherwise they use system trust roots.

8. User interfaces and ports#

Launch the dashboard from a trusted local console or SSH terminal:

console
sudo syswarden tui

The TUI reads local telemetry and HA status in the invoking terminal. It opens no listening socket. SysWarden owns no listener or generated firewall permission on TCP 62027.

The authenticated HA API uses TLS 1.3 and configurable TCP port 62026 by default. Every HA route requires the bearer token. Configuration validation rejects an empty token or any token containing whitespace or control characters before reload mutation or service restart. The previously validated configuration and its running listener remain active until a valid candidate is successfully reloaded.

9. HA dialect and ownership#

For each peer and serialized synchronization cycle, use one authenticated GET /ha/status to select the dialect. Enriched operations require both sync_ttl and sync_provenance. Missing, partial, malformed or failed capabilities stop the handoff for that peer and never relax TLS or bearer authentication.

The ownership stores are intentionally separate:

  • DELETE {"bans": [...]} removes provenance ledger entries only;
  • DELETE {"ips": [...]} explicitly removes historical static entries;
  • neither delete cascades to another peer;
  • the integrator cleans only addresses present in its durable, peer-specific record of historical submissions;
  • ownership is never inferred from GET /ha/sync, provenance pagination or an effective union returned by a peer.

The enriched DELETE contract introduced by the historical v4.04.0 change set was first published in the historical v4.04.2 release and is carried forward in v4.04.3. After successful processing, it returns {"status":"ok","deleted":N}. deleted is the number of exact provenance claims removed for the canonical IP, source and authenticated peer scope. It is not a count of distinct IP addresses and is not proof that nftables unblocked the address, because another owner or a historical static claim can keep it banned. A source or scope mismatch is a safe idempotent no-op with deleted:0.

10. Cluster migration fence#

The operator, not the integrator, supplies the complete member and external legacy-writer inventory. One trusted control host creates one canonical activation manifest and distributes the same protected file to every member and to the integrator:

json
{
  "schema_version": 1,
  "membership_scope": "one_receiving_api_endpoint_per_syswarden_node",
  "legacy_writer_ids": [
    "bunkerweb-primary"
  ],
  "members": [
    {
      "address": "192.0.2.10",
      "port": 62026
    }
  ]
}

Replace the documentation address with each receiving member's canonical IP literal. The inventory must be an absolute canonical path to a regular, root-owned mode 0600 file no larger than 1 MiB under a protected directory chain. JSON is strict: no byte-order mark, duplicate or unknown keys, or trailing data. At least one unique address and port endpoint is required. Addresses cannot be CIDRs, zoned, bracketed or IPv4-mapped IPv6 values, and ports range from 1 through 65535. legacy_writer_ids is mandatory but may be empty. Non-empty IDs match [a-z0-9][a-z0-9._-]{0,63} and are unique. --assert-complete attests that the member and legacy-writer inventories are complete.

console
sudo syswarden ha-fence manifest create \
  --inventory /root/syswarden-ha-inventory.json \
  --output /root/syswarden-ha-manifest.json \
  --assert-complete
sudo syswarden ha-fence manifest verify \
  --manifest /root/syswarden-ha-manifest.json
sudo syswarden ha-fence engage \
  --manifest /root/syswarden-ha-manifest.json
sudo syswarden ha-fence status --json

The capability native_sync_fence_v1 means only that a node understands the schema. The proof is the fresh, dynamic and challenge-bound native_sync_fence object returned by the exact member over the verified TLS connection.

The integrator accepts proof only when state is active_drained, challenge, leaf identity, server instance, generation and condition are stable, and the following operator-manifest values match exactly:

  • epoch;
  • membership_sha256;
  • legacy_writer_inventory_sha256.

These values are opaque, case-sensitive strings. The integrator compares exact strings and does not recalculate digests or reimplement canonicalization. The response header X-SysWarden-HA-Fence-Condition must equal the JSON condition value from the same response.

Every historical static cleanup request sends the observed condition in X-SysWarden-HA-Fence-Condition. A missing condition receives HTTP 428, a malformed condition receives HTTP 400 and a stale condition receives HTTP 412. All three reject without mutation. HTTP 412 means that the fence changed; stop, refresh the complete all-member proof and require an operator decision.

An unreachable member, changed inventory, changed certificate, restarted server instance, changed generation, blind interval or reappearing address resets the observation. A one-hour continuous absence window across every declared peer is supporting evidence only. It is never proof of drain and never permits claim release from a partial view.

Fence release requires the unchanged manifest and durable terminal closure evidence for every declared external writer:

json
{
  "schema_version": 1,
  "epoch": "COPY_EXACT_EPOCH_FROM_MANIFEST",
  "membership_sha256": "COPY_EXACT_MEMBERSHIP_SHA256_FROM_MANIFEST",
  "legacy_writer_inventory_sha256": "COPY_EXACT_WRITER_SHA256_FROM_MANIFEST",
  "legacy_retry_queue_drained": true,
  "writers": [
    {
      "id": "bunkerweb-primary",
      "disposition": "migrated_enriched_only",
      "closure_generation": "REPLACE_WITH_DURABLE_GENERATION",
      "closed_at": "REPLACE_WITH_CANONICAL_UTC_RFC3339",
      "evidence_sha256": "REPLACE_WITH_LOWERCASE_EVIDENCE_SHA256"
    }
  ]
}

Replace every placeholder with values bound to the unchanged manifest and durable closure evidence. The writer closure follows the same protected-file rules and must use the canonical indented layout shown with one final newline. Its writers exactly match the manifest writer IDs in canonical order; use an empty array when the manifest contains no writers. The retry queue flag is true. Terminal dispositions are migrated_enriched_only, disabled, credential_revoked or network_quarantined. The generation is 1 through 256 non-space printable ASCII characters, the closure time is canonical whole-second UTC RFC3339, and the evidence digest is lowercase SHA-256.

console
sudo syswarden ha-fence release \
  --manifest /root/syswarden-ha-manifest.json \
  --writer-closure /root/syswarden-ha-writer-closure.json

Before the historical v4.03.2 freeze, the BunkerWeb partner had to provide written confirmation against the exact frozen contract. No unexplained partner-attributable static residue may remain.

See BunkerWeb integration for the full integrator contract.

11. Command inventory#

The table lists product commands only. Cobra help and shell-completion utilities remain available separately. Unmarked rows belong to the stable baseline. Rows marked "source candidate only" belong to the version-specific v4.10.0 source at commit 8fc22a6bae4adfcf62d80e90da6387f2aa6db1c7; they are absent from the stable release and are not a release qualification claim.

CommandPurpose
alertsStream kernel and WAAP alert events
allow-sshAdd an address to the SSH exception registry
auditRun a bounded local operational diagnostic
blockAdd addresses or CIDRs to the persistent blocklist
checkInspect recorded firewall state for an address
configValidate, inspect and migrate modular configuration
config-getRead one effective configuration key
ha-fenceAdminister a cluster migration fence
ha-syncPush missing local durable entries to exact peers
installRun the host-mutating installation pipeline
listDisplay local registries and active HA bans
manualDisplay the built-in operator reference
migrate-configRun the compatible configuration migration entry point
reloadReapply policy and normally restart the core
recover-wireguardSource candidate only: inspect and explicitly recover exact historical WireGuard nftables state
revoke-sshRemove an address from the SSH exception registry
runtime-unblockSource candidate only: remove local runtime claims through the authenticated core; persistent blocklists and independent HA claims remain effective
tuiLaunch the native local terminal dashboard
unblockRemove addresses or CIDRs from the blocklist
uninstallDelete SysWarden services, rules, configuration, data and logs
unwhitelistRemove addresses or CIDRs from the whitelist
updateInstall an update verified by the signed manifest
update-feedsValidate embedded GeoIP, refresh external feeds and reapply policy
whitelistAdd addresses or CIDRs to the persistent whitelist
whitelist-infraDetect and add local infrastructure addresses

Use syswarden <command> --help before any host-mutating action.

12. Audit and removal#

syswarden audit is a bounded local diagnostic. It does not certify compliance. syswarden uninstall deletes SysWarden configuration, data, logs, services and firewall state. It is not a general host rollback. Back up required material, retain console access and verify unrelated host state before removal.

Removal intentionally does not reverse successfully applied OS or CIS hardening. Exact hardening policy files may therefore remain after the product runtime is gone. On a Fedora or RHEL-family host where SysWarden installed and configured dnf-automatic, dependency cleanup may also preserve the hardened configuration as /etc/dnf/automatic.conf.rpmsave. Treat these files as host security policy, not active SysWarden runtime. Review their contents and package provenance from console access before deciding whether to keep or retire them; do not delete them merely to make a residue scan empty.

Continue with the bounded use cases before production rollout.

Search pages and sections. Nothing leaves your browser.

Tab to a result, Enter to open. Escape to close.