Commit Graph

7 Commits

Author SHA1 Message Date
mikkeli 86b290d978 ci: run the runner as a dedicated account under a system service
CI / Analyze & Test (push) Successful in 13s
CI / Release APK (push) Successful in 24s
The earlier script installed a systemd *user* service, which conflated two
separate decisions. What matters in host mode is which account jobs run as,
because there is no container boundary and the service account is the only
sandbox. A system service with User= set gets unprivileged jobs *and* a
root-managed lifecycle that starts at boot with no lingering session.

Jobs now run as a dedicated `gitea-runner` account in the flutterdev group,
with its own HOME so its Gradle and pub caches are its own. The alternative —
a human account — would hand every workflow that user's home directory and SSH
keys, which is a sharper edge than usual here because a workflow file is code
and much of this repo is agent-authored. Running as root would be worse still,
and would also miss the warm caches in a human HOME and re-download
dependencies every run.

flutterdev is what grants write access to /opt/flutter and /opt/android-sdk.
Group membership alone does not prove it, since both carry ACLs, so the script
tests writability as the service account and fails with the getfacl command to
run. Tool lookups use systemd's default PATH rather than an interactive shell's,
because that is what the service will actually have.

Adds --check, which runs pre-flight and exits without creating anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 23:44:05 +09:00
mikkeli 0dd0b95afb ci: run on the self-hosted x86_64 runner and drop the toolchain installers
Targets runs-on: flutter-android for both jobs. Since that runner is host mode
with the toolchain preinstalled, subosito/flutter-action and
actions/setup-java are removed — they would have downloaded a second Flutter
and JDK over the ones being used, and CI would no longer be building with the
toolchain the release was verified against. A Toolchain step fails loudly if
flutter or ANDROID_HOME is missing rather than letting a later step fail
obscurely.

Documents in docs/README.md why the runner cannot be the Gitea host, what host
mode trades away, and what each job protects — including why building the
release APK is necessary but not sufficient, which is the reason the JNI name
guard exists at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 23:21:17 +09:00
mikkeli 6e35d724dc docs: record that the core runs without the UI, and the CPU cost of the UI
States where run data lives now that Iperf3Session owns it, so the next person
does not put accumulation back in the view model, and documents the headless
entry point as the way to check that claim rather than trust it.

Includes the measurement that motivates using it: 30.2% local CPU headless
versus 79.9% through the UI on a comparable 4-stream run. That gap is worth
recording, because it means throughput read off the app's own screen is
pessimistic at high rates and should not be compared against a headless or
desktop figure without noting which path produced it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 20:25:06 +09:00
mikkeli fe042efc29 docs: record the layering rule and where it is enforced
The architecture section listed files but said nothing about which layer may
import what, so the direction of dependency was only visible by reading the
imports. States the rule as a table, notes that the UI depends on the
Iperf3Runner contract rather than on a transport and what that buys, and points
at the test that enforces it so the rule is not just prose.

Also brings the tree up to date: it was missing the domain services directory,
the event model and the formatter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:56:25 +09:00
mikkeli 857b79fbaf docs: replace the pipe design with json_callback and note the embedding costs
The output-streaming section described the pipe and reader thread that no
longer exist, including the join-ordering rule that was the reason they were
dangerous. Replaced with how the json_callback path works and why, and an
explicit note not to reintroduce the pipe.

Adds a section on what embedding a CLI in a long-lived app process actually
costs, since that is the recurring trap here: iperf3's main() can leave
process-global state behind because it is about to exit, and this app cannot.
Records what iperf3 does clean up — joined stream threads, closed fds, an
immediately unlinked temp file — with the measurements taken on device, so the
next person does not have to re-derive whether repeated runs leak.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:35:41 +09:00
mikkeli 0dc747edef docs: describe the JNI architecture the app actually uses
The root README still documented the abandoned approach: iperf3 3.21 binaries
bundled in assets and run through a Process runner, and a lib/ layout that no
longer matched. It now covers the JNI design, the current feature set and how
to test against a local iperf3 server, and says plainly that the binaries
still sitting in assets/iperf3/ are undeclared in pubspec.yaml and therefore
never reach the APK.

docs/README.md gains the threading, pipe-shutdown and stop-handling
constraints that the fixes on this branch depend on, so the next change does
not have to rediscover why the join order and the worker thread matter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 19:03:59 +09:00
irene 7263808918 docs: add current structure/implementation/design documentation
CI / Analyze & Test (push) Failing after 27s
2026-08-25 19:25:40 +09:00