Fix systemd restart support

This commit is contained in:
2026-10-27 22:44:06 +02:00
parent fe7e3d61c2
commit 4b53aad20c
7 changed files with 536 additions and 112 deletions
+2 -2
View File
@@ -29,14 +29,14 @@ The user has approved the following choices. They may be introduced when their i
- Use a separate vault key pair from all WireGuard client/server key pairs. Encrypt each client secret to the vault public key; protect the vault secret key with password-based encryption using the established `age` format. Do not replace this with password-only encryption of every new client record, which would require a password on creation.
- `wireguard-uapi` remains an approved option but is not selected for the bounded runtime reader: its public read API does not expose a receive deadline or a complete-dump guarantee. A systemd D-Bus request being accepted is not proof of job completion or successful VPN application.
- Runtime observation uses the already approved `rustix` 1.1.5 safe `net` and `event` APIs for a nonblocking Linux Generic Netlink socket, polling and datagram I/O. This keeps one monotonic deadline and exact truncation/sender checks around the checked WireGuard decoder. It does not add a direct dependency or use a subprocess.
- The systemd adapter pins `zbus` 5.19.0 with default features disabled and only `async-io` enabled. It uses zbus's asynchronous signal and method APIs, driven by a small standard-library wake/park loop so every call shares the caller's monotonic deadline. zbus's async-io backend owns internal I/O executor thread(s); callers must allow the connection to be dropped after each operation. Signal polling uses zbus 5.19.0's doc-hidden `export::futures_core` re-export to avoid another direct dependency; keep zbus pinned until that implementation is deliberately migrated.
- The systemd adapter pins `zbus` 5.19.0 with default features disabled and `async-io` plus `p2p` enabled. It first uses the system bus, then selects `/run/systemd/private` peer mode only when the bus connection reports a missing/refused/disconnected endpoint, before submitting any restart request. Permission errors, authentication/protocol failures, and deadline expiry do not trigger fallback. Private mode uses EXTERNAL authentication and checks the peer UID is root. Bus calls use the systemd well-known destination; private calls omit the destination and avoid bus-daemon owner monitoring. zbus's local peer signal matching tracks `JobRemoved` after systemd `Subscribe`. Each call uses zbus's asynchronous APIs driven by a small standard-library wake/park loop so operations share the caller's monotonic deadline. zbus's async-io backend owns internal I/O executor thread(s); callers must allow the connection to be dropped after each operation. Signal polling uses zbus 5.19.0's doc-hidden `export::futures_core` re-export to avoid another direct dependency; keep zbus pinned until that implementation is deliberately migrated. The private systemd socket is an internal systemd interface and may change between releases.
- Avoid incompatible duplicate versions of secret-handling types where library APIs exchange them. Use supported re-exports where appropriate rather than adding redundant direct dependencies.
- Additional direct dependencies still require the comparison and prior agreement required by [AGENTS.md](../AGENTS.md), including any test/build helpers. Transitive dependencies of approved crates are resolved through Cargo; review their compatibility and runtime requirements. Do not add a direct `tokio` dependency merely because an approved library uses an async runtime internally.
- `clap`, `chrono`, SQLite, a generic INI writer, a mocking framework, and archive compression libraries are not part of the approved initial set. Use the standard library for the small startup argument surface and background threads/channels where sufficient.
### External runtime and build requirements
- Production requires Linux with WireGuard support, the existing configured systemd service, and the system D-Bus needed by `zbus`. Existing `wg-quick` service dependencies remain necessary if that is how the service manages the interface.
- Production requires Linux with WireGuard support and the existing configured systemd service. Restart uses the system D-Bus when available and can use systemd's root-only `/run/systemd/private` peer socket when the bus endpoint is unavailable. Permission or authentication failures and timeouts stop the operation without trying the private socket. The private socket is not a stable public systemd API. Existing `wg-quick` service dependencies remain necessary if that is how the service manages the interface.
- The application uses library APIs rather than invoking `wg`, `systemctl`, `age`, `qrencode`, `tar`, or `openssl` for its own operations. Test fixture setup and the existing WireGuard service may use their required system tools; that does not create an application subprocess fallback.
- Rust/Cargo and the target toolchain/linker are build requirements, not production application requirements. Verify the release binary's actual dynamic-library requirements rather than promising an automatically static executable.
- A Docker-API-compatible runtime, test images, and container tools belong to the integration environment only. Production does not need Docker or Testcontainers.
+1 -1
View File
@@ -235,7 +235,7 @@ the user-excluded IPv6 capability and traffic checks.
### Systemd acceptance and test execution
- Use a disposable Linux VM with WireGuard, systemd, and a system D-Bus to verify the actual `zbus` service adapter, job completion/timeouts, restart verification, and recovery from a selected backup. Do not substitute a mounted host D-Bus or claim Alpine tests cover systemd.
- Use a disposable Linux VM with WireGuard and systemd to verify the actual `zbus` service adapter, job completion/timeouts, restart verification, and recovery from a selected backup. Exercise both the system-bus path and the root-only `/run/systemd/private` path with the system bus endpoint unavailable. Also verify that permission/authentication failures and connection deadlines do not trigger fallback. Current synthetic tests cover transport selection and failure classification; they do not emulate systemd or prove the D-Bus peer protocol. Do not substitute a mounted host D-Bus or claim Alpine tests cover systemd. The private socket is an internal systemd interface and may change between releases.
- Keep container and VM suites out of the default `cargo test` execution. Once implemented, document their exact opt-in commands and prerequisites in this document. Do not list a planned command as available before its test target exists.
- When integration testing is explicitly requested, unavailable Docker, WireGuard support, IPv4 networking, or required capabilities means the suite is not executed or incomplete, not passed. Distinguish prerequisite failures from application failures in the report. IPv6 capability and traffic are intentionally not tested by M8.
- Report unit/fixture results, Alpine real-WireGuard results, and VM real-systemd results separately. Full acceptance requires the relevant real-system checks, not just mocks or successful container startup.
+72
View File
@@ -0,0 +1,72 @@
# Milestone 16 evidence: private systemd peer transport
Recorded 2026-10-27T22:42:48+02:00 on the native
`x86_64-unknown-linux-gnu` development host. This is **implementation and
synthetic evidence, not systemd acceptance**. The milestone remains blocked on
private-peer behavioral and disposable-VM checks.
## Source and protocol basis
The existing adapter connected only with `zbus::Connection::system()`. That
cannot reach systemd where no system D-Bus daemon exists. Official systemd
[source](https://github.com/systemd/systemd/blob/main/src/shared/bus-util.c)
shows root `systemctl` connecting to `unix:path=/run/systemd/private` and
checking peer credentials. Its [manual](https://www.freedesktop.org/software/systemd/man/latest/systemd.html)
calls this an internal interface; compatibility across systemd releases is
not guaranteed. Pinned [zbus 5.19.0](https://docs.rs/zbus/5.19.0/zbus/connection/struct.Builder.html)
provides `p2p` and EXTERNAL-authentication support.
The adapter now selects the system bus first and falls back to the private
socket only for a missing/refused/disconnected bus endpoint during connection
setup, before any restart request. Permission, handshake and timeout failures
do not trigger fallback. The private path checks peer UID 0; method calls omit
the bus destination; its `JobRemoved` stream uses a local peer match without
`org.freedesktop.DBus` owner monitoring. The system-bus path retains its
destination and owner-change watch. Both preserve `Subscribe` before
`RestartUnit`, canonical unit identity, the shared deadline, job matching and
conservative submission uncertainty. No retry follows a possibly submitted
restart. Application backup/confirmation/runtime/applied logic is unchanged.
## Checks, review, and gap
An independent GPT-6-Luna/high reviewer found M16-R1 (P2): private socket
permission errors were reduced to `RequestFailed`. A separate GPT-6-Luna/high
repairer changed the production builder error mapping to preserve
`PermissionDenied` and added a focused synthetic assertion. The reviewer
rechecked and closed R1. M16-R2 (P2) remains open: synthetic tests do not
execute the production private peer branch end to end. An attempted local zbus
UnixStream peer fixture failed at peer credential access with `EPERM` in the
sandbox; later fixture variants did not establish a successful protocol path.
Those experiments were removed from the test tree. They are not evidence that
real systemd rejects the implementation. No host private socket was accessed.
Pinned `rustc`/Cargo 1.97.1, rustfmt 1.9.0, and Clippy 0.1.97 were verified.
On the repaired inputs, from the repository root:
- `cargo test --locked --lib service::dbus::tests -- --test-threads=1`: 16 passed.
- `cargo fmt --all -- --check`: exit 0.
- `cargo clippy --locked --all-targets -- -D warnings`: exit 0.
- `cargo test --locked`: 367 passed, 3 explicitly ignored, 0 failed, including 2 documentation tests. Selected Docker/VM suites were not run.
- `cargo build --locked --release`: exit 0.
- `git diff --check`: exit 0.
Base commit: `fe7e3d61c22108acba5c4a86480254a12e089a53`. Dirty inputs and
SHA256 at validation:
- `Cargo.toml`: `807d08cc43b4c3f986134b7aa5b051fd2f0966fab95ac01d861c65f173c77caa`
- `Cargo.lock`: `6a929bb72d90da16b5ca7d209566e31a611d1ddcd5817ee778d7e9fe25b10c2e`
- `src/service/dbus.rs`: `d1ed34dd8a46d2495299c776176d7d5f6cfa9b6abb29ec84b6045e74bdffeb26`
- `docs/DEPENDENCIES.md`: `1db85ecd3f892e3f42f1220eb7627cfd423406e46e5024bdf6599a647f07be1f`
- `docs/TESTING.md`: `7ecab091999b50964b1641772a4cfc370639507b9c6621a058829a54b0f71325`
Native release artifact `target/release/wireguard-admin` SHA256:
`64c2851431ec406901e76dc866a05abd630a0e71adfd4efc16d3b44ea3c72b73`.
The prior disposable Debian VM was stopped before guest installation and has
no verified guest marker or running systemd acceptance environment, per
[`VM_ACCEPTANCE.md`](../VM_ACCEPTANCE.md). No private-socket or system-bus
restart was run in a disposable VM. Neither transport is accepted against real
systemd yet; production must not be used to fill this gap. The next check is
to finish and authorize a disposable VM with synthetic WireGuard configuration,
then run both transport paths and verify job completion plus full runtime
matching, with exact source/artifact identity.