Private Google Drive build delivery¶
Run and find the files¶
On firmware main, open Actions → Build Firmware → Run workflow. Select the device and variant, and enter a new build_tag (letters/digits, dots, hyphens/underscores; start with a letter or digit; 1–80 characters, e.g. v1.2.3, 25.12.5-rc1 or Release-MF286D_1.0). Start with validate_only=true and a separate unused validation tag. This is a manually requested transport check, not a public-repository test workflow. Full compilation uses validate_only=false.
All generated files are ordinary, individually downloadable Drive files:
ROOT/
passwall1/
v2026-11-08-passwall1/
images/
openwrt-ipq40xx-generic-zte_mf286d-squashfs-sysupgrade.bin
metadata/
build.json
profiles.json
config
sources.lock.json
...
logs/
build-<timestamp>-<id>.log
result.json
SHA256SUMS
PENDING.json
COMPLETE.json
The exact layout is ROOT/
One-time authentication prerequisites (operator controlled)¶
- In BOTH repositories set Actions variable DRIVE_ROOT_FOLDER_ID to the chosen existing private root folder ID. It is request-bound, checked against the builder variable, and must match the rclone remote root. No root is device-hardcoded. Different devices sharing a variant must use different tags.
- On your own PC configure maintained rclone, remote
name noobwrt, backend drive, normal personal Drive (no Shared Drive),
own Desktop OAuth client, scope drive, and root_folder_id equal to ROOT.
Scope drive.file cannot generally access a pre-existing web-created folder.
Test with
rclone lsd noobwrt:. Do not run copy/sync/link/public-sharing tests. - Store ONLY the [noobwrt] section as private builder Actions secret RCLONE_CONFIG, preferably in its protected firmware-build environment. Accepted keys: type, client_id, client_secret, scope, token, root_folder_id, and optional empty team_drive. Never put OAuth config in chat, source, logs, the public caller, or an issue. Other remotes/extra settings are rejected.
- The workflow creates a unique temporary config file with mode 0600, removes the secret and all inherited RCLONE_* overrides from the subprocess environment, and deletes the config on normal success/failure. Rclone can refresh OAuth in that temporary file. Abrupt host loss/SIGKILL cannot guarantee cleanup: secure RUNNER_TEMP/host storage and rotate credentials after suspected compromise. No credentials enter Docker compilation or build logs. Provider diagnostics are suppressed, not echoed.
- The runner requires an operator-reviewed rclone installation on PATH. This change does not install software or alter the host. Validation fails before compilation if rclone is unavailable. Reserve sufficient temporary disk for payload plus a full downloaded readback (twice sequentially).
- Public firmware secret TOKEN must be valid and scoped ONLY to private NoobWRT/noobwrt-builder: Actions read/write, Contents read, Metadata read. It dispatches/inspects exact runs and reads job metadata; no Drive, Checks, release-write or callback credential is required in the caller. HTTP 401 is expired/invalid auth; 403/404 may require repository/org approval/SSO.
root_folder_id is a path boundary, NOT an OAuth security boundary. Scope drive can access the account's other Drive data. Prefer a dedicated Google account with only this folder shared to it as Editor. Keep the root and inherited permissions private; the uploader never creates/changes sharing permissions. GitHub environment/branch protection and trusted workflow writers remain required.
Fail-closed publication and receipt¶
Schema-2 request fields are exactly schema_version, firmware_repository, firmware_sha, builder_sha, request_id, device, variant, validate_only, clean, verbose, build_tag and drive_root_id. Both repositories share identical scripts/dispatch_protocol.py. Inputs use canonical full SHA/identifier/boolean validation; subprocesses use argument arrays, not caller-controlled shell code. The trusted repository allowlist is authorization, not device policy.
Builder allowlist
The builder pins FIRMWARE_REPO in its scripts/dispatch_protocol.py and
checks it out in .github/workflows/external-build.yml. Before this
repository can dispatch a build, the builder must allowlist
nooblk-98/noobwrt-zte-mf286d and check that repository out by
firmware_sha. Until then the request is rejected with
unapproved firmware repository.
The builder verifies exact checked-out commits and origin/main ancestry. Full build packaging verifies the complete local SHA256SUMS file set, engine commits, device/variant and individual image digests. Delivery also checks target/profile against the reviewed definition and exact profiles.json selected image set. Output/log/result directories are unique under RUNNER_TEMP per job/attempt; existing runner checkout output, legacy volumes and active builds are untouched.
The root must have ONE serialized publisher. The workflow retains global external-firmware-runner concurrency with cancel-in-progress=false. Do not publish from another repository/workflow/manual uploader to the same root while it runs. Google Drive permits duplicate names and has no atomic create-if-absent folder operation: rclone cannot supply a distributed lock. Under this explicit single-writer contract the builder refuses every existing tag, including empty, failed or partial folders. It checks duplicate paths/shortcuts and folder ID, uses immutable copy with automatic retries disabled, and never delete/sync/ overwrite/resume operations. External simultaneous writers violate the contract; this is not a claim of atomic multi-writer tag reservation.
PENDING.json is uploaded first. Payload files follow. The builder lists the exact remote file set, downloads ALL remote bytes to a fresh temporary directory using rclone, and computes SHA-256 for every file (including SHA256SUMS). No size-only, provider-MD5-only, or upload-success-only verification is accepted. Only after that passes is COMPLETE.json uploaded LAST. The entire final set, including COMPLETE.json, is downloaded and SHA-256 checked again before emitting an attestation. SHA256SUMS covers payload files; COMPLETE.json additionally binds its digest and all payload hashes to the complete request, run ID/attempt, folder ID, path and profile. Publication controls are not circularly self-hashed. PENDING.json remains for history and does not negate a verified COMPLETE.json.
The successful receipt job has an authenticated Actions job name:
drive-v1:<request-sha256>:<SHA256SUMS-sha256>:<COMPLETE-sha256>:<folder-id>
The caller selects only external-
Trust statement: caller authenticates the trusted builder's downloaded-byte verification attestation; caller does NOT independently download Drive data. This has the existing trusted-builder/workflow-writer boundary, not a signature or proof that firmware is safe to flash. For manual independent verification, download files from the private folder and compare SHA256SUMS plus the SHA256SUMS/COMPLETE digests displayed in the caller summary.
Failure and rollout¶
A compile failure attempts to deliver logs and failed result/checksums to the same unused tag, with failed/pending state and NO COMPLETE or success attestation. Network/delivery failures can leave partial folders; never automatically delete, resume, rerun, or reuse a tag. Pick a NEW tag only after inspecting the prior run. A final readback failure can leave COMPLETE remotely but produces no successful receipt: trust the exact successful run/attestation as well as the completion file. The caller wait is bounded at 300 minutes (315-minute hosted job timeout). The obsolete Jenkins-to-GitHub release workflow is removed; historical published releases are untouched. No new public tests/validate workflow is introduced. Builder tests cover mocked transport and caller protocol; real Drive and Actions job-name roundtrip remain unproved until auth and an operator-reviewed rclone prerequisite are available.
Merge the paired protocol changes together only after review. First prove one new validation tag, inspect private ACLs/files/checksums/receipt and collision failure, then authorize one new full-build tag. Do not touch/retry an existing full compilation just to test transport. Do not blindly retry on timeout.
References: rclone Drive, immutable copy, JSON inventory.
The GitHub job metadata transport uses the documented workflow run attempt jobs API.