Skip to content

First-boot initialization hardening + systemd/packaging fixes - #157

Open
joshschmelzle wants to merge 2 commits into
devfrom
a0-first-boot-service-hardening
Open

First-boot initialization hardening + systemd/packaging fixes#157
joshschmelzle wants to merge 2 commits into
devfrom
a0-first-boot-service-hardening

Conversation

@joshschmelzle

@joshschmelzle joshschmelzle commented Jul 19, 2026

Copy link
Copy Markdown
Member

Summary

Fix a first-boot initialization failure and clean up the systemd unit + Debian maintainer scripts.

This effort does NOT include the state move to /home/wlanpi-core, auth rework, or root-only perms.

Changes

Core

  • app.py: define InitializationManager.initial_retry_delay (was undefined -> AttributeError crashed the security-init retry before it retried).
  • app.py: fail loud on critical init — readiness/security/database failures now raise CriticalInitializationError and fail the ASGI lifespan instead of logging and continuing (previously db/token failures set initialized=True and returned success). Token-manager failure stays non-fatal (limited functionality; health loop recovers).
  • database.py: fix undefined self.log -> module-level log in initialize_with_retry.

systemd / Debian

  • wlanpi-core.service: remove duplicate Type=notify; add Restart=on-failure + RestartSec=2 + StartLimitIntervalSec=60/StartLimitBurst=5 (finite burst so a permanent failure can't tight-loop) + TimeoutStartSec=30; RequiresMountsFor for the current state paths; ExecStartPre writability probe.
  • New install/usr/bin/wlanpi-core-preflight helper (probes the state dir is writable; creates it 0700) wired into debian/wlanpi-core.install.
  • wlanpi-core.socket: fix stale header comment.
  • Reconcile the gunicorn socket bind (relative -> absolute /run/wlanpi_core.sock) so it matches nginx proxy_pass and the socket unit.
  • debian/prerm: fix wrong mount-unit name (var-log-wlanpi_core-debug.mount) and add #DEBHELPER# so the service/socket are stopped on removal.

Testing

  • pytest -q: 400 passed, 20 skipped, 0 failures.
  • Note: the existing suite monkeypatches initialize_components, so the fail-loud/retry paths aren't exercised by app-level tests: TODO: follow-up to add real unit tests.

Validation this PR should exercise

  • Debian package build + lintian + systemd-analyze verify.
  • (Manual, off-CI) first-boot test on a real Pi: crash -> restart -> recover; socket + preflight behavior.

Follow-ups

Python correctness:
- app.py: define InitializationManager.initial_retry_delay (2.0) so the
  security-init retry loop no longer raises AttributeError before retrying;
  it now retries 3x with exponential backoff.
- app.py: fail loud instead of fail open. Critical init (system readiness,
  security, database) now raises CriticalInitializationError, which propagates
  out of the startup handler to fail the ASGI lifespan so systemd can detect a
  broken start. Token-manager failure remains non-fatal (limited functionality).
- database.py: fix undefined self.log -> module-level log in
  initialize_with_retry (would have AttributeError'd on the DB retry path).

systemd / debian:
- wlanpi-core.service: remove duplicate Type=notify; add Restart=on-failure +
  RestartSec=2 and StartLimitIntervalSec/StartLimitBurst in [Unit] with a finite
  burst; add RequiresMountsFor for current state/log paths; add ExecStartPre
  writable probe.
- install/usr/bin/wlanpi-core-preflight: new ExecStartPre helper (shell, since a
  unit cannot import Python constants) that verifies the current state dir is
  writable; wired into debian/wlanpi-core.install.
- wlanpi-core.socket: fix stale '# wlanpi-webui.socket' header comment.
- wlanpi-core.service: bind absolute unix:/run/wlanpi_core.sock (matches the
  socket unit ListenStream and nginx proxy_pass) instead of the relative
  unix:wlanpi_core.sock that resolved to a different socket.
- prerm: fix wrong mount-unit name (var-log-wlanpi_core-debug.mount); add
  #DEBHELPER# so dh injects the service/socket stop on removal.

Scope: A0 only. Keeps current /home/wlanpi state paths (flip is a later PR).
- wlanpi-core.service: add TimeoutStartSec=30 in [Service] to bound the start
  wait before the restart policy engages on a first-boot failure.
- wlanpi-core-preflight: mkdir -p -m 0700 so the state dir is created 0700
  instead of the default 0755.

PrivateTmp left as false intentionally (cross-process /tmp reads:
lldpneigh/cdpneigh); tmp hardening deferred to a separate PR.
@joshschmelzle joshschmelzle changed the title A0: first-boot initialization hardening + systemd/packaging fixes First-boot initialization hardening + systemd/packaging fixes Jul 20, 2026
@joshschmelzle joshschmelzle self-assigned this Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant