HSLG Media

Automatic Android builds, queue security and debug exports

Automatic Android builds

This chapter specifies how HSLG WordPress uploads initiate restricted native builds without granting the website shell access. It distinguishes manifest queueing, a rootless worker, private artifact storage and real device verification.

Decision and division of responsibility

A WordPress plugin upload is the operator's release trigger. WordPress must not be the Android compiler. PHP handles one narrowly scoped release record, WordPress administrator authentication, a progress endpoint and authenticated artifact downloads. A separate media user process reads the reviewed build record and compiles the real HSLG mobile code in /home/media/.hslg-build/. This respects the non-negotiable requirement that the existing Virtualmin installation and unrelated hosted sites remain unchanged.

The presenter and native app continue to be different artifacts. The presenter renders the shared UI through demonstration adapters and may simulate bookings, network changes or haptic actions. The APK is compiled from apps/mobile, packages/app-ui, packages/app-core and the approved native build tooling. It must never acquire the demonstration entry, fake payment success or WordPress PHP code. The source payload shipped alongside the presenter is used only as build input; it is not the running mobile app.

When a build is required

The plugin includes release/android-build.json, with a reviewed build identifier, Android application ID, minimum supported SDK and SHA-256 checksum of release/native-app.php. The latter contains an embedded ZIP of the real mobile source behind a PHP ABSPATH guard. The direct HTTP route cannot return the binary when PHP is correctly configured. The manifest is reviewed before packaging, and the plugin recomputes its checksum after an update. If the release has no native manifest, no build is scheduled. If a matching payload has a verified signed APK at the expected private path, it is considered built; a prior queue attempt or failed build is not sufficient. Documentation-only and presenter-only modifications do not trigger unnecessary Android compilation.

A changed manifest with a mismatched or missing source payload is an integrity error, not a reason to run a partial build. The plugin stores a blocked status and exposes it to administrators. A matching manifest queues one random job ID. Repeated WordPress page loads must not queue duplicate jobs. A newer presenter release retries a previously failed or incomplete payload once. An authorised administrator can also use the fixed-function Retry Android build action, which accepts no arbitrary command, source path or executable input.

WordPress and worker trust boundaries

The Web/WordPress process does not invoke Gradle, accept a command string or execute subprocesses. It registers WordPress's authenticated admin AJAX endpoint for status and nonce-protected download endpoints for two fixed artifact types: APK and debug ZIP. WordPress stores a small release queue record in its normal options system. The build worker claims a pending, manifest-verified request through the restricted wp hslg-native claim command when WP-CLI is present; otherwise it uses the PHP-CLI-only worker/wordpress-bridge.php under the media Unix identity. It does not take source paths or shell arguments from a browser. The worker also reports completion through the same WordPress queue adapter. Neither route accepts an arbitrary build command.

The worker runs as Unix account media; never root. Its one-time setup installs a per-user cron entry. A cron worker checks once per minute, then holds a non-blocking private filesystem lock so concurrent requests cannot build simultaneously. Each job uses an unpredictable identifier and an isolated workspace. The worker validates the source checksum and refuses ZIP path traversal, symbolic-link entries and oversized archives before writing any source files. It deletes temporary compilation directories in both success and failure paths. JavaScript and Gradle dependency caches remain private for reuse.

Compiler phases and evidence

The worker reads the known private env.sh without printing environment values. It installs the workspace dependencies with pnpm, generates a reviewed mobile runtime stage, bundles the real Capacitor TypeScript entry through Vite, regenerates Capacitor Android platform files, pins Android minSdkVersion to 26 and synchronises the seven installed plugins. It retains approved Android manifest camera permissions and verified HTTPS app-link intent filters. Gradle then assembles a signed development APK. The worker requires Android apksigner to verify the signature and records the SHA-256 digest and byte length; without this evidence the build fails. Every stage writes real subprocess output to a private build.log; the progress file provides status, stage name, percentage and final state.

The first environment proof was a test Android project built with 207 successful Gradle tasks after correcting SDK 24 versus 26. That proof establishes that the Ubuntu toolchain can compile the seven plugins. It does not establish that the full HSLG source build has passed, that camera or app links work on the user's physical Android device, or that any customer API and payment backend is live. This release adds the build mechanism. An end-to-end upload/compile/download/device test is still required on media.hslg.uk.

Diagnostics, traceability and security

The browser polls WordPress with AJAX approximately every two seconds. It displays the latest 16 KB of text logs, status and stage progress; progress is not an estimate of remaining elapsed time. When the worker finishes, the administrator receives a link to the compiled APK and a link to the full debug ZIP. Even a failed build generates a debug ZIP when the worker has claimed the job, containing the full build log, bounded diagnostic JSON, job/build hashes and the resolved npm lockfile if available. Logs, artifacts and signing identities are never placed in the WordPress uploads folder or a publicly accessible downloads directory. Downloads stream through capability and nonce checks.

The debug ZIP can still contain information reported by compilers, including internal filesystem paths and dependency names. Inspect it before sharing outside the project. It must never contain a WordPress configuration, MariaDB backup, private Android signing key, environment-secret dump or customer personal information. The source packager excludes those classes. WordPress installation credentials and API keys must not be included in source builds. Reusable Gradle, pnpm and Android SDK caches remain under /home/media/.hslg-build, not under /home/media/public_html.

Costs, limits and rejected alternatives

This design keeps ongoing hosted service fees near zero by reusing Ubuntu and its existing CPU, memory and storage. It consumes server resources during a build. It creates a maintenance burden for the per-user cron worker, the privately installed SDK, cache hygiene, signing keys and retention. It avoids introducing Firebase, cloud CI or a second build server before a working customer APK exists. A dedicated CI runner is a later option if server load or release frequency justifies it.

Running Gradle directly in a WordPress AJAX request was rejected because it could exceed PHP timeouts and create a remote shell attack surface. Automatically granting the WordPress process sudo or altering the Virtualmin-wide PHP-FPM configuration was rejected. Building directly from an unauthenticated URL or user-submitted ZIP was rejected. The worker is installed once by a deliberately reviewed command as media; future plugin upgrades do not require repeated SSH setup. If cron, WP-CLI, file permissions or the private toolchain are missing, the build stays queued or fails visibly rather than inventing a success outcome.

Recovery and acceptance

To reconstruct the worker after total server loss, first restore Ubuntu, Virtualmin, the media virtual server, WordPress and the private Android toolchain. Install the presenter plugin and run its one-time worker setup from the media account. Inspect the installed cron entry and the protected release directory. Restore signing identity only from independently surviving backups; without the old signing key an APK may not install as an update over the previously signed application.

Before calling this feature complete, verify a fresh native release triggers exactly one job, a docs-only update triggers none, both success and intentional Gradle failure produce a debug ZIP, an unauthorised visitor cannot fetch the APK, a second job cannot claim while one runs, and the installed APK executes the actual QR, haptic, network, orientation and splash flows on Android. Retain these test results in the release history and recovery documentation.

Verified completion versus historical attempts

The queue option identifies an attempt; it does not certify that its compiler finished. A separate verified APK identifier is recorded only after output and signature validation. This distinction is essential to fault recovery on a single-server project: plugin activation, scheduler startup and Gradle execution can all succeed before the final APK fails. Persisting only an attempted source checksum would hide those failures on future releases.

The administrator's retry button is the recovery valve for an unchanged reviewed source after a failed job. Reusing the same source bytes is safe because the worker creates a fresh random job ID and private release directory. Routine admin views must never enqueue repeatedly, or a broken dependency could exhaust build resources. Version changes allow one new attempt; manual retries require manage_options and a WordPress nonce. Concurrency remains locked under the media account.

The reference setup script is stored within the HSLG presenter plugin source and named in the single Disaster Recovery page. A read-only recovery checker verifies cron registration, installer availability and private paths. It does not alter Virtualmin, PHP-FPM, MariaDB or any other hosted domain. Its success cannot substitute for a real signed APK and on-device acceptance.