Repository navigation
Upgrade in place, depend on redis, and summarise each scan - #14
Merged
Merged
Conversation
andylemin
force-pushed
the
installer-upgrades
branch
4 times, most recently
from
September 27, 2026 08:06
41650fb to
caac9fc
Compare
Both server installers now put the service back as they found it. rcctl check records whether it was running, the daemon is stopped before anything is replaced, and started again only if it had been running. install(1) renames into place, so replacing the binary under a running daemon succeeds while that daemon keeps the old inode: an upgrade reported success and changed nothing. The Rust installer stops only once its build has succeeded, so a failed build leaves the old daemon serving, and a failed restart is reported as such, because the firewall was whitelisting a moment before. A daemon the operator had stopped stays stopped, and only then is the enable and start advice printed. Two passes in the OpenBSD job cover both cases, with the pids proving the restart and the config proving it was not replaced. The sync loop logs one line per cycle that did something: the table's size, what expired, and what was added to or removed from the table and the file. A cycle that changed nothing says nothing, so INFO stays quiet in a steady state and every line means work was done. Both rc.d scripts start Redis before the daemon when REDIS_HOST is local. The daemon whitelists without it, but nothing expires until it appears. A failure is not fatal, since a non-zero rc_pre would abort a start the daemon can manage, and a REDIS_HOST elsewhere is left alone. The daemon cannot do this itself: starting a service needs proc exec, which its pledge excludes under CTL: IOCTL. Redis connect, read and write are bounded. The redis crate blocks indefinitely on all three by default, so a stalled server held a worker for good, and enough of those retire the pool and stop the firewall whitelisting with nothing in the log. Each pass now asserts that every acknowledged message also finished, the acknowledgement preceding the store writes, which is how an unbounded store call shows up. update_dns_blocklist.sh fetches HaGeZi's pro and tif domain lists, the two the README recommends, and converts them to Unbound's local-zone format. Only plausible domains are written: the files carry a header, and the wildcard variants carry '*.' prefixes, either of which would take the whole include file with it. The guards against a short or collapsed list are unchanged. The installer keeps an existing copy rather than replacing it, since the sources, the allowlist hook and the thresholds are meant to be edited. Both workflows drop their pull_request trigger and gain a concurrency group. With a PR open they ran everything twice, and a force-push left the previous commit's jobs running; GitHub shows push-triggered runs on the PR anyway. The OpenBSD job also times compiling and running the tests separately, because the test crates pull in dev-dependencies the daemon build does not and one budget for both could not tell a slow compile from a hung test.
andylemin
force-pushed
the
installer-upgrades
branch
from
September 27, 2026 08:34
caac9fc to
eb7d4c2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four operational changes: the installers upgrade in place, the sync loop reports the scans that do work, the rc.d scripts start Redis, and the blocklist tool uses the HaGeZi lists the README recommends.
Upgrades
Both server installers put the service back as they found it.
rcctl checkrecords whether it was running, the daemon is stopped before anything is replaced, and started again only if it had been running.This was a silent failure, not a missing convenience: OpenBSD's
install(1)renames into place, so replacing the binary under a running daemon succeeds and that daemon keeps executing the old inode. An upgrade printed "All Completed successfully" and changed nothing until the next manual restart.The Rust installer stops only once its build has succeeded, so a failed build leaves the old daemon serving. A failed restart is reported as a failure, because the firewall was whitelisting a moment before. A daemon the operator had stopped stays stopped, and only then does the installer print its enable and start advice.
Two new passes in the OpenBSD job cover both cases: re-running the installer against a running daemon must stop it, replace it and start it, with the process ids proving the restart and the config proving it was untouched; against a stopped daemon it must leave it stopped.
One line per scan that does something
The sync loop logs the table's size, what expired, and what was added to or removed from the table and the persist file:
A cycle that changed nothing logs nothing, so
INFOstays quiet in a steady state and every line means work was done. This gives theINFOtier content it did not have.Redis before the daemon
Both rc.d scripts start Redis in
rc_prewhenREDIS_HOSTis local. The daemon whitelists without Redis, but nothing expires until it appears, so a boot that races Redis leaves expiry off until the next restart. A failure is not fatal, since a non-zerorc_prewould abort a start the daemon can manage, and aREDIS_HOSTelsewhere is left alone.The daemon cannot do this itself. Starting a service needs
proc exec, which its pledge deliberately excludes underCTL: IOCTL, andrc_prealready runs as root.Blocklists
update_dns_blocklist.shfetches HaGeZi'sproandtifdomain lists, the two the README already recommended, and converts them to Unbound's local-zone format. Only plausible domains are written out: the files carry a header, and the wildcard variants carry*.prefixes, either of which would take the whole include file with it when Unbound loads it. The guards against a short or collapsed list are unchanged, and a new test covers the converter.The installer keeps an existing copy rather than replacing it, since the sources, the allowlist hook and the thresholds are all meant to be edited.
Redis timeouts
The redis crate blocks indefinitely on connect, read and write by default, so a stalled server holds a worker for good. Eight of those and the pool is gone, and the firewall stops whitelisting with nothing in the log. Connect, read and write are now bounded at five seconds, which is generous because the store runs after the acknowledgement; it exists only so a worker cannot be lost. A timeout now surfaces as the "Failed to store to Redis" the code already handled.
Each pass also asserts that every acknowledged message finished. The acknowledgement precedes the Redis and persist writes, so an ack with no matching completion is a worker stuck in a store.
Verification
123 Rust unit tests, 19 integration tests, 250 Python tests,
cargo fmt --checkandclippy -D warningsclean.The OpenBSD job is green, with every new pass proving its own claim: the upgrade pass shows the installer stopping and restarting a running daemon with different pids and an untouched config, the stopped-daemon pass shows it left stopped, the Redis pass shows
rc_prestarting a Redis it had stopped first, and the expiry pass captures the summary line:21 acknowledgements, 21 completions, none out of order.