feat: stabilize runtime lifecycle and add optional web authentication - #192
feat: stabilize runtime lifecycle and add optional web authentication#192p-keminer wants to merge 5 commits into
Conversation
️✅ There are no secrets present in this pull request anymore.If these secrets were true positive and are still valid, we highly recommend you to revoke them. 🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request. |
6eeb6f2 to
ff7ac11
Compare
There was a problem hiding this comment.
Pull request overview
This PR delivers a combined runtime-stability upgrade (bounded scanner concurrency + deterministic shutdown) and an opt-in HTTP Basic authentication layer for Bjorn’s web interface, including transactional installation/rollback and validation tooling.
Changes:
- Add optional HTTP Basic auth across the full web surface, backed by a salted scrypt credential store and management CLI.
- Stabilize lifecycle behavior (web server port reuse, bounded request loop, thread naming/joins) and bound scanner concurrency via worker pools.
- Introduce transactional deployment + rollback installer and a comprehensive unit/runtime validation suite for the combined change set.
Reviewed changes
Copilot reviewed 30 out of 31 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| webapp.py | Adds optional auth gate on all routes; makes server socket reusable and web thread lifecycle more deterministic. |
| web_auth.py | Implements credential store + Basic auth parsing and short-lived authorization caching. |
| configure_web_auth.py | Adds CLI for setting/enabling/disabling/status of web auth without plaintext args. |
| shared.py | Adds web_auth_file path to shared configuration. |
| Bjorn.py | Reworks shutdown coordination to join owned threads deterministically and report residual threads. |
| actions/scanning.py | Replaces ad-hoc threading with bounded ThreadPoolExecutor pools; adds shutdown-aware Nmap discovery. |
| display.py | Adds interruptible waits and explicit stop signaling to avoid shutdown hangs. |
| epd_helper.py | Adds explicit hardware/GPIO resource shutdown (gpiozero/lgpio) for deterministic cleanup. |
| install_bjorn.sh | Adds --show-plan, optional web-auth install/config step, and stricter verification behavior. |
| install_stability_web_auth.sh | New transactional installer with snapshot/restore and HTTP readiness verification. |
| README.md | Documents optional access control and management command usage. |
| INSTALL.md | Adds detailed stability+web-auth upgrade and validation instructions. |
| DEVELOPMENT.md | Adds developer documentation for auth behavior and combined validation runners. |
| .gitignore | Ignores config/web_auth.json so credentials aren’t committed. |
| tests/validate_web_auth_runtime.sh | New live-runtime validation for auth commands/routes, malformed requests, and restoration. |
| tests/validate_scan_runtime.sh | New runtime stability validation (PID stability, thread cap, scan completion, journal health). |
| tests/validate_scan_lifecycle.sh | New lifecycle validation for clean stop + restart and port readiness. |
| tests/run_web_auth_validation.sh | Runner for unit/runtime auth validation. |
| tests/run_stability_web_auth_validation.sh | Combined unit+runtime runner for stability + auth validation with summaries. |
| tests/run_scan_worker_validation.sh | Runner for scanner worker pool + lifecycle validation. |
| tests/run_reboot_persistence_validation.sh | Pre/post reboot persistence validation for combined feature state. |
| tests/run_fresh_installer_integration.sh | Isolated fresh-install flow test for optional auth tool + credential lifecycle. |
| tests/test_webapp_auth_integration.py | Integration tests asserting every known route is protected and lifecycle behaviors hold. |
| tests/test_web_auth.py | Unit tests for credential hashing, header parsing, caching, and fail-closed behavior. |
| tests/test_configure_web_auth.py | Unit tests for CLI behavior and stdin password handling. |
| tests/test_scanning_workers.py | Unit tests for worker pool behavior, deduped ports, and shutdown-aware discovery. |
| tests/test_install_bjorn_integration.py | Integration tests asserting installer ordering and safe credential prompting. |
| tests/test_combined_installer.py | Integration tests for transactional installer sequencing and readiness checks. |
| tests/test_epd_helper_shutdown.py | Unit tests for idempotent hardware shutdown/cleanup behavior. |
| tests/test_display_shutdown.py | Unit tests for interruptible waits and thread enumeration in display shutdown. |
| tests/test_bjorn_lifecycle.py | Unit tests for deterministic shutdown joins and residual thread reporting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 31 changed files in this pull request and generated no new comments.
Suppressed comments (3)
actions/scanning.py:492
scan_network_and_write_to_csv()similarly wrapsexecutor.map()inlist(...), which materializes an unused results list. Iterating the map results is sufficient to wait for completion without allocating extra memory.
worker_count = min(MAX_HOST_SCAN_WORKERS, len(hosts))
with ThreadPoolExecutor(max_workers=worker_count) as executor:
list(executor.map(self.scan_host, hosts))
webapp.py:47
log_message()only suppresses GET request logs. With the newdo_HEAD()handler, frequent HEAD-based health checks or browser probes can now spam the info log. Consider suppressing HEAD logs alongside GET to keep request logging behavior consistent and avoid noise.
actions/scanning.py:444PortScanner.start()forces evaluation ofexecutor.map()vialist(...), which allocates a list of one entry per scanned port (potentially very large) just to wait for completion. This increases memory pressure unnecessarily during large scans.
This issue also appears on line 490 of the same file.
worker_count = min(MAX_PORT_SCAN_WORKERS, len(ports))
with ThreadPoolExecutor(max_workers=worker_count) as executor:
list(executor.map(self.scan, ports))
Follow-up validation for
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 31 changed files in this pull request and generated no new comments.
Suppressed comments (2)
webapp.py:27
favicon_pathis defined but never used in this module, and the currentos.path.join(shared_data.webdir, '/images/favicon.ico')would ignoreshared_data.webdiranyway because of the leading/. Remove the unused variable (favicon serving is handled viaWebUtils).
Bjorn.py:161wait_for_shutdown_threads()aims to enforce a bounded shutdown deadline, butjoin_slice = min(0.25, remaining)is applied per-thread, so with many threads the loop can block for0.25 * len(alive_threads)seconds per pass—overshooting the intended deadline. Use a per-thread slice derived fromremaining / len(alive_threads)to keep the total wait bounded.
join_slice = min(0.25, remaining)
Summary
This change bounds scanner concurrency, makes service and hardware shutdown
deterministic, and adds opt-in password protection to the complete Bjorn web
surface.
Maintainer note: why this is one combined pull request
Hi, and thank you for maintaining Bjorn. I realize that this is a substantial
change set, spanning 31 files, so I want to explain the integration boundary
up front.
The two user-facing goals—runtime stability on resource-constrained Raspberry
Pi devices and optional web authentication—look separable conceptually, but
they are not independent at the implementation, deployment, and validation
boundaries:
webapp.py. The lifecycle work replaces the server loop,shutdown behavior, socket reuse, and request timing, while authentication
wraps every GET, HEAD, POST, API, static-file, and error path. Maintaining
separate versions would create overlapping and order-dependent patches to
the same request handler.
credential permissions, port readiness, service restart, and rollback must
operate against the exact web-server implementation being installed.
shutdown of the same web process. Bounded worker pools, shutdown-aware Nmap,
and deterministic thread cleanup are therefore part of keeping the
authenticated interface reliably reachable on a Pi Zero 2 W.
scan, bounded thread use, clean service stop/start, port reuse, every
authenticated route, malformed requests, credential restoration, installer
rollback, and reboot persistence against one exact runtime state.
The features could theoretically be split, but not as two independent
drop-in patches without introducing temporary compatibility layers, duplicated
installers, competing versions of shared files, order-dependent rollouts, and
a larger matrix of partially integrated states. That would make each pull
request appear smaller while moving the main risk into the untested boundary
between them.
For that reason, this contribution uses one transactional installer, one
rollback snapshot, and one combined validation entry point. Authentication
remains strictly opt-in; installations without a credential file retain the
existing open behavior.
Most of the size is verification and operational safety rather than additional
runtime behavior: 3,920 of 6,088 added lines are tests (about 64%). Tests,
documentation, and transactional installation/rollback tooling together
account for 5,008 added lines (about 82%).
Scanner and lifecycle changes
preserving worker exception propagation.
stop the aggregation pipeline at the failing step without logging false
success.
and parse completed XML through the installed
python-nmaplibrary.handle its shutdown explicitly.
factory, and lgpio's module-global notification worker.
misses its bounded deadline.
Optional web authentication
http_authmanagement commands forset,status,enable, anddisable; interactive setup keeps plaintext passwords out of arguments andshell history.
401challenge.error diagnostics.
retaining audit warnings for malformed or incorrect credentials.
for unsent body bytes.
config/web_auth.jsonfrom being served as static content.seconds and invalidate it when credential contents, mode, owner, or group
change.
Deployment and rollback
install_stability_web_auth.shvalidates the complete source set, creates onesnapshot, stops
bjorn.serviceonce, installs the exact tested files, verifiesPython compilation, restarts the service, and accepts only a healthy HTTP 200
or 401 response on port 8000.
Any validation, installation, or startup failure restores the entire previous
state. Explicit
--restorealso creates a recovery snapshot before applyingthe selected rollback. The normal fresh installer asks whether the optional
global
/usr/local/sbin/http_authmanagement command should be installed and,independently, whether credentials should be configured immediately.
Credential validation failures repeat the credential prompt without advancing
to ownership changes, while failures in required installer steps abort instead
of presenting a retry option that cannot replay the failed command. Fresh
installation starts the service and reports success only when the unit is
active and port 8000 returns HTTP 200 or 401.
Backwards compatibility
Automated validation
The combined checkout currently has 64 unique unit and integration tests
covering scanner completion, worker limits, Nmap shutdown, process lifecycle,
display/GPIO cleanup, installer recovery ordering and port readiness,
credential parsing, configuration commands, every known web route, audit
behavior, incomplete POST handling, port reuse, web-thread shutdown, and the
isolated fresh-installer decline/install/configure/manage workflow. Dedicated
regressions also verify that manual web-server shutdown never calls the
serve_forever()-only shutdown primitive, fatal installer validation cannot beswallowed by a conditional Bash function, and credential access-metadata
changes invalidate cached authorization.
Raspberry Pi Zero 2 W validation
live-status aggregation errors.
port 8000.
credential-restoration checks passed.
optional tool left no command behind; accepting it created a real symbolic
link; credential setup, status, disable, enable, and password verification
passed; and the credential file mode was exactly
0600.Raspberry Pi OS Lite 64-bit. Three deliberately invalid short-password
attempts each returned to the credential prompt. A valid fourth attempt
created the mode-
0600credential file, startedbjorn.service, returnedHTTP 401 on port 8000, and completed without any false-success warning.
in 10.483 seconds, completed a live scan, peaked at 47 threads, stopped
cleanly in 7 seconds, restarted on port 8000, exercised 30 browser
challenges, and restored the original credential state.
bjorn.service,preserved HTTP 401 access control, completed a scan, and remained at or below
47 threads.
seconds with PID 363, a 15-thread idle state, a 47-thread peak, unchanged
credential hash and management-command target, and no current-boot runtime
errors.
validated current-boot journal.
Tested with Debian 12 Bookworm arm64, Python 3.11,
python-nmap0.7.1, and aWaveshare 2.13-inch V4 display.