Skip to content

Rewrite: layered architecture, working AP+STA on real hardware, modern GUI, gated CI/CD - #1

Merged
krotrn merged 9 commits into
mainfrom
refactor/architecture
Oct 4, 2026
Merged

krotrn merged 9 commits into
mainfrom
refactor/architecture

Conversation

@krotrn

@krotrn krotrn commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

Summary

A rewrite of apsta's internals, fixing the correctness, reliability and security problems found in review, plus a redesigned GUI and a gated release pipeline. Verified end to end on real hardware (Intel AX201, Arch, NetworkManager 1.58): a phone joined, got DHCP and internet while the laptop stayed on Wi-Fi, and stop restored the system exactly.

Core

  • Layered architecture (core → config/state → hw/net → services → cmd), argv-only subprocesses, errors with hints.
  • Correct AP+STA detection from nl80211 interface combinations (per-group limits, same-channel requirement).
  • Strategies (hostapd, NetworkManager virtual, single-interface) with transactional rollback; runtime state in /run.
  • NetworkManager kept away from the AP interface; liveness = actually broadcasting; supervised daemons with restart limits.
  • hostapd: country code (incl. self-managed regdomains), 802.11n/ac, WMM, control socket.
  • Conflict-free subnet, system DNS, firewalld/iptables/nftables backends, ip_forward restored.
  • apsta run watcher (resume, channel changes), sleep hook, systemd/OpenRC/runit.
  • Random password on first start; secrets never in argv or the journal; polkit action instead of pkexec sh.
  • Clients: real station list, disconnect/block, per-client rate limits.

GUI

  • Hotspot / Devices / Settings, adaptive layout, QR sharing, device management.
  • Uses the newest libadwaita widgets with fallbacks down to 1.1 (Ubuntu 22.04).

Quality

  • 257 unit + integration tests (simulated network stack, no root needed), 97% coverage, gate 94%.
  • Headless GUI smoke test on 7 distributions.

CI/CD

  • release.yml: verify versions + CHANGELOG → CI + package builds → provenance attestation → PyPI, GitHub release, pacman repo, AUR, PPA. Nothing publishes unless everything passed.
  • actionlint, shellcheck, lintian, install tests, CodeQL, Dependabot; latest action versions; timeouts, concurrency, least-privilege permissions.

See CHANGELOG.md for upgrade notes (Python ≥ 3.10, --force → --allow-disconnect, deprecated status flags, detect --json keys).

Test plan

  • Lint, tests on Python 3.10/3.12/3.14/3.15, coverage gate (local)
  • GUI smoke on Ubuntu 22.04/24.04/26.04, Debian 12/13, Fedora, Arch (local containers)
  • Arch + Debian package builds, lintian, install tests (local containers)
  • Real hardware: CLI start/clients/stop and GUI start/share/limit/stop
  • GitHub Actions on this PR

krotrn added 7 commits October 4, 2026 23:07
…issues

Restructure apsta into layers (core -> config/state -> hw/net -> services ->
cmd) with errors carrying hints, rendered only by the CLI. The GUI becomes a
client of the CLI via JSON and pkexec.

Fixes:
- AP+STA detection respects per-group limits; reports same-channel needs
- strategies (hostapd, nmcli virtual, nmcli single) with transactional
  rollback; runtime state moved to /run/apsta; stale state cleaned on start
- hostapd config sets country code, 802.11n/ac, WMM and a control socket
- conflict-free subnet, system DNS, firewalld/iptables/nftables backends,
  ip_forward restored on stop
- `apsta run` supervises the hotspot (resume, driver reset, channel changes);
  daemons run as transient systemd units; enable supports OpenRC and runit
- random password on first start; no passwords in argv or the journal;
  polkit action instead of `pkexec sh`; no shell=True
- client list from associated stations; working disconnect/block; unique
  tc priorities for rate limits

Adds unit and integration tests (simulated network stack, no root or
hardware needed), coverage gate in CI, and contributor docs.
The previous GUI used Adw.ToolbarView, Adw.Banner and Adw.EntryRow, so it
crashed on Ubuntu/Pop!_OS 22.04, Linux Mint 21 and Debian 12. Newer widgets
now go through apsta_gui/compat.py with baseline fallbacks.

- Hotspot / Devices / Settings tabs: status hero with one start/stop
  button, per-device speed limit / disconnect / block, Share dialog with
  QR code, band and interface pickers, profiles, start-at-boot toggle,
  hardware report, toasts, shortcuts and an About window
- escape SSIDs and hostnames before display (Pango markup)
- explain pkexec failures; show a window when the CLI is missing
- own app icon; StartupWMClass matches the app ID
- CLI: `config --json` and autostart state in `status --json`
- scripts/gui_smoke.py builds every view headlessly; CI runs it on
  Ubuntu 22.04, Debian 12, Ubuntu 24.04, Fedora and Arch
- portability test rejects libadwaita APIs newer than the baseline
GUI
- prefer the newest libadwaita widgets (ToolbarView, adaptive tabs with a
  bottom bar on narrow windows, SwitchRow, ButtonRow, Adw.Dialog,
  AboutDialog, Adw.Spinner) with fallbacks down to libadwaita 1.1
- work around libadwaita 1.5 collapsing page width without a font DPI
- replace the deprecated Gdk.Texture.new_for_pixbuf
- smoke test: Xvfb fallback, per-process display, dialog screenshots;
  checked on Ubuntu 22.04/24.04, Debian 12 and Arch

CLI
- --json output is clean JSON; messages go to stderr in that mode
- status --limit-kbps without --limit-client is a usage error, not a crash
- detect and status share one interface JSON shape (`type`)

Docs
- ARCHITECTURE: GUI structure, data flow, compatibility strategy, testing
- new docs/json-output.md documenting --json output and exit codes
- README desktop-app, scripting and troubleshooting sections; CONTRIBUTING
  setup for uv and pip, GUI rules, screenshot steps

Tooling
- dev tools as a PEP 735 dependency group; `make dev` (uv or pip);
  Makefile runs tools via `python -m` and explains missing tools
- uv.lock; Python >= 3.10; CI on 3.10/3.12/3.14; latest action versions

Tests
- TTY-independent output tests; sleep hook tests; apsta run, prompts,
  supervisor SIGKILL escalation, deprecated flags; coverage gate 94 %
…nager)

Verified end to end on Arch: phone joined, got DHCP and internet while the
laptop stayed on Wi-Fi; stop restored the system exactly.

- keep NetworkManager away from the AP interface with a runtime
  unmanaged-devices config (appended with +=) written before the interface
  exists; wpa_supplicant had blocked hostapd ("Match already configured")
- consider hostapd up only at state=ENABLED and a hotspot alive only when
  it broadcasts an SSID; a __ap vif reports type AP before anything runs
- cap transient unit restarts (StartLimitBurst=5 per 60 s)
- take the regulatory country from the phy's self-managed domain when the
  global domain is 00
- skip non-netdev (P2P-device) blocks in `iw dev`; AP interfaces have no
  connected_ssid
- GUI: hide the profile switcher when there is only one profile
- scripts/hardware_check.sh: start, keep up for a phone, stop, verify cleanup
- fake test world mirrors the real driver; regression tests for each fix
- release.yml: verify versions + CHANGELOG -> full CI and package builds
  (reusable workflows) -> build provenance attestation -> publish to PyPI,
  GitHub release (notes from CHANGELOG.md, SHA256SUMS), pacman repo, AUR,
  PPA. Nothing publishes unless every check passed.
- ci.yml: actionlint, shellcheck for all scripts, tests on Python 3.10,
  3.12, 3.14 (+3.15 pre-release, non-blocking), GUI on Ubuntu 22.04/24.04/
  26.04, Debian 12/13, Fedora, Arch; wheel/sdist build with twine check
- packages.yml: lintian; install tests of the .deb on Ubuntu 24.04/26.04
  and Debian 13, and of the Arch package
- codeql.yml (python + actions) and dependabot.yml (actions + uv)
- actions on their latest major versions; concurrency and timeouts on
  every workflow; least-privilege permissions per job
- scripts/release_check.py, scripts/release_notes.py; bump_version.py
  releases the CHANGELOG "Unreleased" section
- shellcheck fixes in apsta-sleep, install.sh, hardware_check.sh
- GUI: shorter band subtitle; `make gui`
Copilot AI balanced review requested due to automatic review settings October 4, 2026 20:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

From the first GitHub Actions run and CodeQL:
- `config --generate-password` wrote the new password to /var/log/apsta.log;
  secrets now go through output.reveal_secret(), which never logs
- log file is owner-only (0600) and existing world-readable logs are tightened
- SIGKILL escalation test treats zombies as dead (containers without init)
- shellcheck: pinned v0.11.0 in CI (was the runner's copy); fix SC2015 and
  SC2336 in install.sh; `make lint-ci` runs the same linters locally
- CodeQL quality notes: explained empty excepts, no cli<->completion import
  cycle, abstract method body, test cleanups
- release_check test keeps its ::error:: output out of CI annotations
- PyPI publish action on its explicit latest version (v1.14.2)
Comment thread .github/workflows/release.yml Dismissed
Comment thread apsta_cli/cmd/config.py Dismissed
Comment thread apsta_cli/core/output.py Dismissed
Comment thread tests/integration/test_lifecycle.py Fixed
Comment thread tests/unit/test_firewall.py Fixed
@krotrn
krotrn merged commit 9ed12d3 into main Oct 4, 2026
21 checks passed
@krotrn
krotrn deleted the refactor/architecture branch October 4, 2026 21:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants