# Migration from v4.03.2 to v4.03.3

> Status: Version-specific
> Documentation baseline: v4.03.3

**Historical route:** This page is retained for the exact versions in its title.
The latest public release has advanced, so the updater guard in section 6 now
stops by design. Do not remove it or use this runbook as a generic route to a
newer release. The exact-version manual recovery path and snapshot requirements
remain separate from a newly reviewed upgrade plan.

This runbook covers the supported AMD64 Linux package transition from the published v4.03.2 release to the published v4.03.3 Patch release.
It also preserves the separate release-owner qualification procedure that was
performed after the candidate was merged and before it was tagged. Operators
must use only the qualified ten-asset public Release.

The v4.03.3 qualification included an Ubuntu 26.04 installation through deterministic local TLS fixtures on supported OSINT source paths.
That release-owner evidence bound the exact merged source commit, Package
workflow run, artifact identity and package digest before tagging. It was
separate from protected CI.

## 1. Safety boundary

Retain verified local console or independent SSH recovery access throughout the
migration. Schedule a maintenance window and provide an external containment
control that does not depend on SysWarden. Record the administrative source,
the HA peers, the current firewall frontend and every process allowed to create
dynamic bans.

The dynamic ban producers include:

- WAAP and UDS enforcement in `syswarden-core`;
- authenticated HA requests and native HA synchronization;
- operator commands and any local automation that calls them;
- any enabled integration that submits temporary or durable bans.

Do not continue if one of those producers cannot be quiesced or if the only
recovery path depends on the four SysWarden dynamic nftables sets.

## 2. Verify the candidate

Use exactly one of these two paths.

For the post-merge, pre-tag release-owner gate, download the package and its
complete `SHA256SUMS.txt` from the unique successful Package workflow artifact
whose workflow head is the exact merged commit on the protected default branch.
Record that merged commit SHA, workflow run ID, artifact ID and package SHA-256.
Verify that the artifact attestation names the same workflow and merged commit,
and that exactly one checksum entry names the selected package. A PR-head or
synthetic PR-merge artifact is exploratory evidence only and cannot qualify the
Release or become a final asset. A local build, artifact from a different run or
package renamed after download is not acceptable. The signed update manifest
does not exist yet and must not be fabricated for this path.

For an operator migration after publication, use `sudo syswarden update` from
the installed v4.03.2 CLI. That version already embeds the Ed25519 release trust
root. The updater obtains the latest published version, verifies the canonical
manifest and detached signature against that trust root, binds the release
version and exact AMD64 platform identity, then verifies the selected package
filename, size and SHA-256 immediately before invoking the native package
manager. There is no unsigned fallback.

Manual package download after publication is a recovery path only. Use it only
after diagnosing why the signed updater could not complete, and verify the
package, both checksum inventories, signed manifest and detached signature from
the same qualified Release before invoking a package manager.

Expected package names are:

| Host family | Candidate package |
|---|---|
| Debian or Ubuntu | `syswarden_4.03.3_amd64.deb` |
| Fedora or RHEL family | `syswarden-4.03.3-1.x86_64.rpm` |
| Alpine | `syswarden_4.03.3_x86_64.apk` |

On the pre-tag path, stop if workflow identity, attestation or checksum binding
is missing or mismatched. On the published updater path, stop on any version,
platform, key identity, signature, size, digest, secure-workspace or package
manager validation error. Do not bypass the updater with an unverified package.

## 3. Back up configuration, package and kernel state

Run the following block from a verified root shell. Keep the resulting directory
off the host before upgrading.

```bash
sudo -i
set -eu
umask 077
MIGRATION_ROOT=/root/syswarden-v4032-to-v4033
test ! -e "${MIGRATION_ROOT}"
install -d -m 0700 "${MIGRATION_ROOT}"
cp -a -- /etc/syswarden "${MIGRATION_ROOT}/etc-syswarden"
cp -a -- /var/lib/syswarden "${MIGRATION_ROOT}/var-lib-syswarden"
nft -a list ruleset > "${MIGRATION_ROOT}/nftables-before.txt"
nft -j -a list ruleset > "${MIGRATION_ROOT}/nftables-before.json"
if command -v systemctl >/dev/null 2>&1; then
  systemctl show syswarden-core.service syswarden-firewall.service \
    --property=Id,LoadState,ActiveState,SubState,UnitFileState,FragmentPath,ExecMainStatus \
    > "${MIGRATION_ROOT}/services-before.txt"
elif command -v rc-service >/dev/null 2>&1; then
  rc-service syswarden-core status > "${MIGRATION_ROOT}/services-before.txt"
  rc-service syswarden-firewall status >> "${MIGRATION_ROOT}/services-before.txt"
  rc-update show >> "${MIGRATION_ROOT}/services-before.txt"
else
  printf '%s\n' 'No supported service manager was found.' >&2
  exit 1
fi
if command -v dpkg-query >/dev/null 2>&1; then
  test "$(dpkg-query -W -f='${Status}' syswarden)" = 'install ok installed'
  test "$(dpkg-query -W -f='${Version}' syswarden)" = '4.03.2'
  test "$(dpkg-query -W -f='${Architecture}' syswarden)" = 'amd64'
  test "$(dpkg --print-architecture)" = 'amd64'
  test -z "$(dpkg --audit)"
  dpkg-query -W -f='${Package}\t${Version}\t${Architecture}\t${db:Status-Status}\n' syswarden \
    > "${MIGRATION_ROOT}/package-before.txt"
elif command -v rpm >/dev/null 2>&1; then
  test "$(rpm -q --qf '%{NAME}|%{VERSION}-%{RELEASE}|%{ARCH}' syswarden)" = \
    'syswarden|4.03.2-1|x86_64'
  rpm -q --qf '%{NAME}\t%{VERSION}-%{RELEASE}\t%{ARCH}\n' syswarden \
    > "${MIGRATION_ROOT}/package-before.txt"
elif command -v apk >/dev/null 2>&1; then
  apk info --installed syswarden >/dev/null
  APK_SOURCE_LINE="$(apk --no-network list --installed syswarden 2>/dev/null)"
  test "$(printf '%s\n' "${APK_SOURCE_LINE}" | wc -l)" -eq 1
  APK_SOURCE_VERSION="$(printf '%s\n' "${APK_SOURCE_LINE}" | \
    awk 'NR == 1 { sub(/^syswarden-/, "", $1); sub(/-r[0-9]+$/, "", $1); print $1 }')"
  test "${APK_SOURCE_VERSION}" = '4.03.2'
  test "$(apk --print-arch)" = 'x86_64'
  apk info -v syswarden > "${MIGRATION_ROOT}/package-before.txt"
else
  printf '%s\n' 'No supported package manager was found.' >&2
  exit 1
fi
find "${MIGRATION_ROOT}" -type f ! -name 'SHA256SUMS.before.txt' -print0 | sort -z | \
  xargs -0 sha256sum > "${MIGRATION_ROOT}/SHA256SUMS.before.txt"
exit
```

Every assertion in this block is a stop condition. The installed source package
must be exactly v4.03.2 on the supported AMD64 or x86_64 architecture, and a DEB
host must have no unfinished package transaction before any backup is accepted.
Do not use this runbook from another source version.

Also record application health, HA fence state if configured, active temporary
HA bans and the current business-traffic checks. A backup is recovery material,
not proof that restoring old kernel state is safe.

## 4. Diagnose all four dynamic sets

SysWarden maintains the same logical dynamic bans in four interval sets:

| Family | Table | Set |
|---|---|---|
| `inet` | `syswarden` | `banned_ips` |
| `inet` | `syswarden` | `banned_ips6` |
| `netdev` | `syswarden_hw_drop` | `banned_ips` |
| `netdev` | `syswarden_hw_drop` | `banned_ips6` |

Capture every set in text and JSON before and after migration:

```bash
sudo -i
set -eu
umask 077
MIGRATION_ROOT=/root/syswarden-v4032-to-v4033
for specification in \
  'inet|syswarden|banned_ips|inet4' \
  'inet|syswarden|banned_ips6|inet6' \
  'netdev|syswarden_hw_drop|banned_ips|netdev4' \
  'netdev|syswarden_hw_drop|banned_ips6|netdev6'
do
  saved_ifs=${IFS}
  IFS='|'
  set -- ${specification}
  IFS=${saved_ifs}
  nft -a list set "$1" "$2" "$3" > "${MIGRATION_ROOT}/$4-before.txt"
  nft -j -a list set "$1" "$2" "$3" > "${MIGRATION_ROOT}/$4-before.json"
done
exit
```

Any failed listing, wrong address family, inconsistent timeout and expiry, or
unsupported element expression is a stop condition. A difference between the
inet and netdev copies must be classified before continuing. The only repairable
case in this runbook is a captured legacy suffix ending at the address-family
maximum; v4.03.3 quarantines that entire dynamic address family across both
layers. Any other divergence is a stop condition. Preserve the evidence and use
the immediate recovery section. Do not edit a set element by guessing its hidden
interval marker.

Dynamic SysWarden targets are required to be admissible host or bounded CIDR
values. An exact singleton equal to the address-family maximum remains a closed
singleton and is not classified as a legacy suffix. A captured non-singleton
dynamic interval that ends at the family maximum identifies the legacy open
interval defect. v4.03.3 treats the whole affected dynamic address family as
unsafe preservation input.

## 5. Quiesce ban producers

Stop the core immediately before the package transaction. This stops WAAP, UDS
and the HA API. Stop external integration traffic at its independently managed
boundary and pause operator or automation calls that can create bans.

```console
sudo systemctl stop syswarden-core.service
sudo systemctl is-active syswarden-core.service
sudo rc-service syswarden-core stop
sudo rc-service syswarden-core status
```

Run only the systemd or OpenRC pair matching the host. The systemd status must
be `inactive`; OpenRC must report the service stopped. Do not flush the four
sets merely to make the upgrade appear clean. When the connectivity bootstrap
in section 5.1 is not required, the following release gate applies: The v4.03.3 reload must demonstrate that it can inspect the old state, preserve only unambiguous valid dynamic bans and discard ambiguous legacy intervals.
When that bootstrap is required, the sealed before-state is the detection
evidence and the post-upgrade checks must prove that the affected family is
clean across both layers.

### 5.1 Connectivity bootstrap for an already blocking legacy suffix

The published v4.03.2 updater must reach GitHub before any v4.03.3 code can
run. If the captured legacy suffix already blocks DNS or GitHub return traffic,
`syswarden update` cannot repair that path by itself. Use this bootstrap only
when all of these conditions are true:

1. the complete before-state evidence is stored outside the host;
2. the core and every external ban producer are stopped;
3. independent console or SSH recovery and external containment are active;
4. the diagnosis identifies an exact affected address family ending at its
   maximum;
5. no other inet or netdev divergence is present.

Create and apply only the transaction matching the affected family. For the
captured IPv4 legacy suffix:

```bash
sudo -i
set -eu
umask 077
QUARANTINE=/root/syswarden-v4032-to-v4033/quarantine-ipv4.nft
printf '%s\n' \
  'flush set inet "syswarden" banned_ips' \
  'flush set netdev "syswarden_hw_drop" banned_ips' > "${QUARANTINE}"
nft -c -f "${QUARANTINE}"
nft -f "${QUARANTINE}"
nft -j list set inet "syswarden" banned_ips
nft -j list set netdev "syswarden_hw_drop" banned_ips
exit
```

For a separately captured IPv6 legacy suffix:

```bash
sudo -i
set -eu
umask 077
QUARANTINE=/root/syswarden-v4032-to-v4033/quarantine-ipv6.nft
printf '%s\n' \
  'flush set inet "syswarden" banned_ips6' \
  'flush set netdev "syswarden_hw_drop" banned_ips6' > "${QUARANTINE}"
nft -c -f "${QUARANTINE}"
nft -f "${QUARANTINE}"
nft -j list set inet "syswarden" banned_ips6
nft -j list set netdev "syswarden_hw_drop" banned_ips6
exit
```

Use both transactions only when both families contain the identified defect.
Every selected set must be empty after the apply. These commands remove only
volatile dynamic bans; they do not edit the persistent blocklist files or the
surrounding policy. The temporary loss of dynamic enforcement is why external
containment and stopped producers are mandatory. If validation, apply or
post-apply inspection fails, keep the host isolated and use the immediate
recovery section instead of broadening the flush.

## 6. Upgrade the native package

After the qualified v4.03.3 Release is public, verify immediately before the
update that GitHub still reports exactly v4.03.3 as the latest stable Release.
The v4.03.2 updater has no target-version pin, so do not invoke it when a newer
Release is latest:

```bash
set -euo pipefail
LATEST_TAG="$(
  curl --proto '=https' --tlsv1.2 --fail --silent --show-error \
    -H 'Accept: application/vnd.github+json' \
    https://api.github.com/repos/duggytuxy/syswarden/releases/latest |
    jq -er '.tag_name'
)"
if [[ "${LATEST_TAG}" != 'v4.03.3' ]]; then
  printf 'Refusing update: latest stable Release is %s, expected v4.03.3.\n' \
    "${LATEST_TAG}" >&2
  exit 1
fi
sudo syswarden update
```

The command must report `Current Version : v4.03.2` and `Latest Version  :
v4.03.3`, select the package matching the host, authenticate it through the
embedded Ed25519 trust root and complete its native package transaction. If a
newer Release has superseded v4.03.3, do not run the updater from this
version-specific procedure.
If section 5.1 was required, keep the core and external producers stopped while
the signed updater downloads and invokes the native package manager. Do not use
an unsigned or digest-only substitute.

Only the pre-tag release-owner gate, or a separately approved recovery after a
signed-updater failure, installs a local package directly. Run only the command
matching the host:

```console
sudo apt-get install -y ./syswarden_4.03.3_amd64.deb
sudo dnf upgrade -y ./syswarden-4.03.3-1.x86_64.rpm
sudo apk add --allow-untrusted ./syswarden_4.03.3_x86_64.apk
```

For APK, `--allow-untrusted` is acceptable only because the independent
Ed25519 manifest has authenticated the exact package, or because the pre-tag
gate has bound it to the exact protected workflow artifact and SHA-256. Capture
the complete updater and package-manager output. On Debian or Ubuntu, run `sudo
dpkg --audit` immediately. Any output, including a package in `iF` state, is a
failed installation gate.

An Alpine v4.03.2 host must already have committed `cronie` and
`cronie-openrc`, active Cronie in the default OpenRC runlevel and no active or
assigned BusyBox `crond`. The v4.03.3 pre-upgrade hook reattests that state
before its configuration hooks. Stop and repair the scheduler prerequisite
independently if this check fails. apk-tools 3 may still commit package payload
while marking a failed-script transaction broken, so a scheduler mismatch is a
failed upgrade gate and must never be treated as a clean rollback.

New Data-Shield publication still requires two independent HTTPS origins with
matching canonical content. Installation-only mirror availability failure or
content disagreement preserves an exact validated last-known-good file, or
omits that optional contribution when no such file exists. It never accepts a
single mirror. Invalid mirror configuration, caller cancellation, unsafe local
state and publication failure remain fatal. The hourly `update-feeds` retry
remains non-zero on quorum loss after reapplying validated policy, which keeps
operational monitoring accurate.

The package hook starts or restarts `syswarden-core` as part of the supported
online installation pipeline. Keep every external producer paused and generate
no matching WAAP or UDS input during this bounded window. As soon as the package
manager returns successfully, stop the core again before the post-upgrade
package and four-set attestations:

```console
sudo systemctl stop syswarden-core.service
sudo systemctl is-active syswarden-core.service
sudo rc-service syswarden-core stop
sudo rc-service syswarden-core status
```

Run only the systemd or OpenRC pair matching the host. Require `inactive` from
systemd or a stopped OpenRC service. If the core cannot be stopped immediately,
retain external containment, preserve the transaction output and treat the
migration gate as failed.

Before its own network-dependent configuration, the packaged candidate inspects
the four dynamic sets. An ambiguous legacy dynamic interval is flushed only for
its affected address family across inet and netdev. The hook restarts the
packaged core service when a repair was required and repeats the quarantine to
close the final-mutation race. Transactional reload retains the same quarantine
contract: its first warning reports detection and states that live firewall
state is unchanged until a successful commit. A second warning may report
completed quarantine only after application, verification and persistence
succeed. If a post-apply step fails, rollback restores the previous persistent
policy but intentionally omits the quarantined dynamic address family; the
returned error must state that surviving boundary. Persistent blocklist files
are not changed by either dynamic-set repair.

## 7. Validate install, configuration and reload

With the core stopped again, attest the exact installed package before any
producer can change the post-upgrade sets:

```bash
sudo -i
set -eu
if command -v dpkg-query >/dev/null 2>&1; then
  test "$(dpkg-query -W -f='${Status}' syswarden)" = 'install ok installed'
  test "$(dpkg-query -W -f='${Version}' syswarden)" = '4.03.3'
  test "$(dpkg-query -W -f='${Architecture}' syswarden)" = 'amd64'
  test "$(dpkg --print-architecture)" = 'amd64'
  test -z "$(dpkg --audit)"
elif command -v rpm >/dev/null 2>&1; then
  test "$(rpm -q --qf '%{NAME}|%{VERSION}-%{RELEASE}|%{ARCH}' syswarden)" = \
    'syswarden|4.03.3-1|x86_64'
elif command -v apk >/dev/null 2>&1; then
  apk info --installed syswarden >/dev/null
  APK_CANDIDATE_LINE="$(apk --no-network list --installed syswarden 2>/dev/null)"
  test "$(printf '%s\n' "${APK_CANDIDATE_LINE}" | wc -l)" -eq 1
  APK_CANDIDATE_VERSION="$(printf '%s\n' "${APK_CANDIDATE_LINE}" | \
    awk 'NR == 1 { sub(/^syswarden-/, "", $1); sub(/-r[0-9]+$/, "", $1); print $1 }')"
  test "${APK_CANDIDATE_VERSION}" = '4.03.3'
  test "$(apk --print-arch)" = 'x86_64'
else
  printf '%s\n' 'No supported package manager was found.' >&2
  exit 1
fi
exit
```

Every assertion is a stop condition. Repeat the four-set capture now, while the
core and all external producers remain stopped, with filenames ending in
`-after`. Compare inet and netdev membership for each address family and inspect
the package log for every legacy-interval warning. Each warned interval must be
absent from all four post-upgrade sets. Treat any unexpected new dynamic entry
as evidence that producer quiescence was lost.

After sealing that package and four-set evidence, re-run the supported
installation and reload steps to prove idempotence and a complete current
configuration:

```console
sudo syswarden
sudo syswarden config validate --path /etc/syswarden/config
sudo syswarden install
sudo syswarden reload
sudo syswarden audit
sudo systemctl is-active syswarden-firewall.service
sudo systemctl is-active syswarden-core.service
sudo rc-service syswarden-firewall status
sudo rc-service syswarden-core status
sudo dpkg --audit
```

Run only the service-manager pair matching the host, and run `dpkg --audit` only
on Debian or Ubuntu. The root command must print `SYSWARDEN v4.03.3 CLI`; both
services must be active; validation, install, reload and the bounded audit must
complete without an unreviewed error. Keep external integrations and operator
automation paused until these checks finish. A newly produced host or CIDR ban
in the subsequent disposable-node tests must use a complete start and
exclusive-end pair in each applicable set.

Then perform the [real WAAP and HA propagation case](https://syswarden.io/docs/use-cases/#10-qualify-real-waap-and-ha-ban-propagation)
on disposable nodes. A unit test or `syswarden check` result is supporting
evidence only and does not replace privileged kernel inspection.

## 8. Recorded Ubuntu 26.04 OSINT installation gate

The v4.03.3 release owner performed a fresh Ubuntu 26.04 DEB installation while a
deterministic local TLS fixture served one supported multi-origin OSINT source
path containing a syntactically valid 6to4 entry under `2002::/16`. This manual
host gate was not executed or implied by the protected container checks. It
bound the exact source SHA, workflow artifact identity and package SHA-256.
The acceptance evidence required:

- the package transaction reaches the configured state with no `iF` package;
- the warning contains only the normalized origin and discarded-entry count;
- the 6to4 entry appears in no published IPv4 or IPv6 list;
- malformed input and an insufficient post-filter source still fail closed;
- both native SysWarden services pass their normal health checks.

This gate completed before the v4.03.3 tag and public Release. Protected CI, a
PR-head artifact, the source fix, unit tests or this runbook alone would not
have established that result.

## 9. Rollback boundary

Rolling back to v4.03.2 is unsafe while any ban producer is active. The older
runtime can recreate the legacy interval form through WAAP, UDS, HA, operator or
automation activity. Restoring the pre-upgrade nftables text or JSON while those
producers run can also reintroduce the defect.

There is no qualified in-place package downgrade for this correction. The only
bounded rollback is a complete pre-upgrade VM or volume snapshot restored under
external network containment, with `syswarden-core`, WAAP, UDS, HA, operator
commands and automation, enabled integrations and every other ban producer kept
inactive. Do not reactivate v4.03.2 until an operator has accepted that known
defect and established a separate mitigation.

If a full snapshot is unavailable, use immediate v4.03.3 recovery instead of
installing the old package over a live host.

## 10. Immediate recovery

If installation, configuration or reload fails:

1. keep the verified console open and retain external containment;
2. stop `syswarden-core.service` and all external ban producers;
3. preserve the package output, journal, four-set text and JSON, configuration,
   package status and `/var/lib/syswarden` before making another change;
4. confirm that the installed CLI prints v4.03.3 before using v4.03.3 repair;
5. rerun configuration validation and v4.03.3 reload;
6. on Debian or Ubuntu, rerun package configuration only after the original
   failure is understood.

```console
sudo systemctl stop syswarden-core.service
sudo rc-service syswarden-core stop
sudo syswarden
sudo syswarden config validate --path /etc/syswarden/config
sudo syswarden reload
sudo dpkg --configure -a
sudo dpkg --audit
```

Run only the first or second service-manager command matching the host. The last
two commands apply only to Debian or Ubuntu. If reload cannot parse or replace
the four dynamic sets, preserve the evidence and keep the host isolated. Flushing
all four dynamic sets removes active protections and is an emergency containment
decision, not a routine repair. It requires explicit operator approval and an
independent boundary firewall.

## 11. Uninstall and purge

Uninstall is destructive removal, not rollback. Back up required material,
retain external containment and stop all ban producers first. When a package
manager owns SysWarden, that package manager is the only supported removal
authority. Do not run `syswarden uninstall` before the package removal or purge.

```console
sudo apt-get purge -y syswarden
sudo dnf remove -y syswarden
sudo apk del syswarden
```

Run only the package-manager command matching the host. After removal, verify
that both SysWarden tables, both native services, `/etc/syswarden`,
`/var/lib/syswarden` and `/var/log/syswarden` are absent. Also verify that
unrelated firewall tables, services, packages and operator data remain intact.

On Debian or Ubuntu, `apt-get remove syswarden` is deliberately non-destructive
for `/etc/syswarden`, `/var/lib/syswarden` and `/var/log/syswarden`; it leaves an
exact private deferred-purge barrier. A subsequent package reinstall preserves
those roots and consumes that barrier only after installation succeeds. A later
`apt-get purge syswarden` performs final destructive cleanup. RPM final erase
and APK post-deinstall are destructive final-removal paths. Ambiguous, modified
or simultaneous removal barriers fail closed for manual recovery.

## 12. Acceptance checklist

- [ ] Applicable pre-tag artifact binding or published `syswarden update` signature chain verified.
- [ ] Exact v4.03.2 source version and AMD64 or x86_64 package state verified.
- [ ] Configuration, data, package, service and full ruleset backups sealed.
- [ ] All four dynamic sets captured before and after migration.
- [ ] If the legacy suffix blocked updater connectivity, the exact
      affected-family bootstrap and external containment were recorded.
- [ ] Package-hook core restart recorded and core stopped again before post-capture.
- [ ] External producers paused through the package and post-capture window.
- [ ] On Alpine, the committed active Cronie prerequisite passed before the
      v4.03.2 to v4.03.3 upgrade configuration hooks.
- [ ] Exact v4.03.3 version, architecture and package-manager state verified.
- [ ] No ambiguous legacy interval preserved after v4.03.3 reload.
- [ ] Configuration validation, install, reload and audit completed.
- [ ] Native services active and package manager reports a clean state.
- [ ] Real WAAP mutation and authenticated HA propagation verified on disposable
      nodes, followed by mandatory snapshot restoration.
- [ ] Rollback remains blocked while any v4.03.2 ban producer could run.
- [ ] Published v4.03.3 qualification evidence includes the Ubuntu 26.04 OSINT gate.
