The previous fix closed one branch and left its neighbours open. Both remaining paths could
still end with the host having no swap, and one of them said the opposite in its own error
message.
1. The message claimed a fallback that was no longer running.
The forward path deactivates the staging swap *before* renaming it onto the managed
path. A failure at that rename, or at anything after it, therefore reached the rollback
with staging_active = false. The rollback then returned "the staging swap is left
active" - which was false: it had been switched off several steps earlier, and with the
original also unable to activate, the host had nothing.
The rollback now brings the replacement back up as the fallback when it is not running,
and the error describes what is actually active rather than assuming.
2. The replacement was deleted before the original was known to work.
With new_installed the rollback removed the file at the managed path to make room for
the restore. That discarded the only other swap area on the host before finding out
whether the original could be activated. The replacement is now moved aside to the
staging name instead, so it is still available as the fallback if the restore fails.
3. An installed replacement could be moved while the kernel was swapping to it.
ReplacementState had no way to express "installed and active", so a failure in the final
verification - a failed read of /proc/swaps after a successful swapon - left the
rollback moving a live swapfile. new_active closes that: if the replacement is active it
is deactivated first, and if that fails the rollback stops and reports that the
replacement is in place and serving, because a working swap is the outcome to keep.
Also from the same audit:
- mkswap --file creates the file itself, so a non-zero exit is not a guarantee that nothing
was left behind. Both creation paths now remove the file on failure; previously a failed
mkswap left an unowned file at the managed path, which the next run correctly called
Foreign and refused, wedging the module.
- stale_ownership_help gave advice that could not work. Removing the ownership record
leaves the file Foreign, which is refused just as firmly, so "mv previous main, delete the
record" only changed which error appeared. Both cases now give one recipe that actually
restores managed state: swapoff, remove the three files and the record, re-run
ensure-swap. Swap holds no persistent data, so a rebuild loses nothing.
- LastActionState kept a single error slot shared by all subsystems, so a successful
firewall reconcile cleared a live traffic failure and logged firewall=recovered for
something that had never failed. Errors are now tracked per subsystem; the shared field
that status reads mirrors whatever is still outstanding.
- tools/README.md still showed 0.1.6.
Five rollback tests covering the branches that exist: original unrestorable with staging
running, original unrestorable with staging already stopped (it is reactivated and never
released), an installed replacement preserved by being moved aside rather than deleted,
the success path releasing the replacement only after the original is back, and the
no-op case.
cargo fmt --check, clippy -D warnings, cargo test --locked --workspace --release: 193 + 4
passed.
The rollback claimed an invariant in its own comment - restore the original, confirm it,
and only then release staging - and did not enforce it. original_back was computed and then
ignored: the staging swapoff ran unconditionally.
The failure path that mattered: staging is active, the original is already off, reactivating
the original fails (swapon can fail under the memory pressure that made the resize
necessary), and the rollback then switched off the staging swap - the last thing carrying
load. Recovering from a failed resize left the host with no swap at all, which is worse
than the failure being recovered from.
original_back now gates the release. If the original cannot be brought back the rollback
returns early with the staging swap still active, and the error carries the two commands
that finish the job by hand. An extra temporary swap is always the better end state than
none. The staging file is also only unlinked once swapoff has actually succeeded - an
active swapfile cannot be removed, and attempting it produced an error that explained
nothing.
Asserting this needs a seam, because the invariant is a sequence of calls rather than a
value: a small SwapCommands trait covers swapon/swapoff, with the real implementation
behind it. Three tests: the original cannot be restored and swapoff is never called with
the message naming the preserved fallback; the original comes back and staging is released
in that order; nothing was taken down so nothing is touched.
Also from the same audit:
- StaleOwnership advice was wrong. It suggested deleting the ownership record, which turns
the file Foreign - refused just as firmly. The message now distinguishes the two cases: a
leftover .previous next to the swapfile is the signature of a replacement killed between
installing the new file and recording its identity, so the error says nothing is lost,
points at the intact previous swapfile, and gives the exact commands for going back or
keeping the new file. Full automatic crash recovery is deliberately not added here - it
needs a transition journal, and that belongs with the Debian acceptance work.
- The acceptance checklist still described the PID-file lock ("re-acquires by the absence
of /proc/<pid>"), which stopped being true when locking moved to flock. The kernel
releases it on process exit, and the leftover file means nothing.
- shellcheck was installed by --prepare-build-env but never run. It now runs in
prepare_build_gate: the script installs systemd units and writes to /usr/sbin, so it is
part of the release surface.
- The skip message for systemd-analyze verify now says what the check actually is - a
post-install gate, expected to be skipped on a clean builder - rather than reading like a
build gate that silently did not run.
One audit item did not reproduce: there is no duplicated -j in antitorrent_drop_counter,
the flag appears once.
Version raised to 0.3.0 in traffic_core/Cargo.toml, with the packaging examples in README
and INSTALLATION updated to match. The reboot scheduler keeps its own 0.2.0.
cargo fmt --check, clippy -D warnings, cargo test --locked --workspace --release: 191 + 4
passed. build.sh not syntax-checked here - no bash on this host; the shellcheck gate now
covers it on the builder.
wireguard-tools was still declared in Depends and in the runtime package list installed by
tools/build.sh --prepare-runtime-env. Since peer discovery moved to docker exec, no code
path invokes a host-side wg, and check_host_tools stopped requiring it. The declaration
was therefore installing a package the harness never calls and misrepresenting the actual
architecture to anyone reading the control file. Removed from both places.
Three items were carrying #[allow(dead_code)], which is a way of silencing the compiler
about a real question rather than answering it:
- SysctlTuningStatus.desired duplicated data that is already in .current - every
SysctlValue holds both the desired and the current value, so the second Vec was the same
information cloned on every inspect call and read by nobody;
- SysctlValue.managed was always set to true and never read. Ownership is decided per file
by the marker, not per key, so the field encoded nothing;
- unix_now() in the reboot scheduler, along with the SystemTime and UNIX_EPOCH imports it
needed. The module works in chrono NaiveDateTime throughout.
All three are removed rather than annotated. The workspace now builds clean under
-D warnings with no dead_code exceptions anywhere.
cargo test --workspace: 158 + 4 passed. clippy -D warnings clean.
- Полностью переписан swap-модуль в режиме exclusive ownership\n- Добавлены verify/checks, статус фактического swap-state и drift\n- Интегрирован ensure-swap в daemon и systemd ExecStartPre\n- Усилен lock API (таймауты) и применение в traffic/firewall/swap\n- Усилен parser firewall (JSON-first + устойчивый text fallback)\n- Обновлены config validation, runtime tools и default priority\n- Синхронизированы docs и build/package hygiene\n- Удалены member Cargo.lock в пользу корневого lockfile
Удалён Recommends: docker.io из control. Ужесточено предупреждение, что --install не является production-path. В .deb docs переведены на repo-like структуру (/usr/share/doc/awg2-harness/repo/...), чтобы не ломались относительные ссылки.
Добавлен package-режим в tools/build.sh: --package, --dist-dir, --version. Реализована сборка Debian-пакета, metadata/control, postinst/postrm и SHA256SUMS. Установочные пути унифицированы под /usr/sbin и /lib/systemd/system для production-модели.