Product-owned code is Apache-2.0. The display name comes from
client/src/brand.ts; release scripts derive their archive names from it.
Cargo.toml’s workspace version is authoritative. Packaging rejects a different
version in the client package, native client manifest, or Tauri configuration.
After changing the display name, regenerate NOTICE with
python3 scripts/package-artifacts.py notice.
Prerequisites
- Rust/Cargo 1.92 or newer, edition 2024; current builds use 1.98.
- Python 3.11 or newer; packaging and licence scripts use only its standard library.
- Node.js supported by the locked Vite release and npm with lockfile v3 support.
- Linux x86_64: C/C++ compiler, pkg-config, libclang, PipeWire development files, and the Wayland/X11 libraries required by the platform adapter. Use your distribution’s packages; the build scripts never install system dependencies.
- Android: JDK 21, Android SDK platform/build-tools 37, NDK 28.2.13676358,
the
aarch64-linux-androidRust target, and the locked Gradle wrapper. The project uses Gradle 9.6.1, Android Gradle Plugin 9.3.1 and Kotlin 2.2.10. - Windows cross-build: the
x86_64-pc-windows-gnullvmRust target and LLVM/MinGW. Configure Cargo’s target linker and C/C++ compiler variables for that target. The current runtime-notice baseline is LLVM 23.1.2. Do not silently substitute a different static runtime without updating its inventory and notices. - Advisory checks (
scripts/ci.sh advisories): cargo-audit 0.22.2 and network access. The stage findscargo-auditonPATH, or else under$BUILD_TOOLS_HOME/cargo-audit/binwhenBUILD_TOOLS_HOMEis set. Install it into the build tools directory, not the user’s Cargo home:cargo install --locked --version 0.22.2 --root "$BUILD_TOOLS_HOME/cargo-audit" cargo-audit. The stage audits both Cargo lock files andnpm audit --omit=devinclient/, denying warnings, and fails when the tool is missing or a fetch fails, even withCI_ALLOW_MISSING_TOOLS=1. Each ignored advisory, with its reason, lives in.cargo/audit.tomlfor the root lock file andclient/src-tauri/.cargo/audit.tomlfor the Tauri lock file. The hosted Checks workflow runs this stage as its ownadvisoriesjob, apart from the build, so a newly published advisory fails that job by name.
Use a build-specific toolchain environment. The scripts accept
BUILD_ENV_FILE=/path/to/build-env.sh, or an environment sourced before invoking
them. It must supply JAVA_HOME, ANDROID_HOME, NDK_HOME, target linkers and
any dedicated CARGO_HOME/RUSTUP_HOME/GRADLE_USER_HOME/npm cache directories.
No script modifies shell startup files or global tool configuration.
export CARGO_TARGET_DIR="$PWD/target" CARGO_BUILD_JOBS=8
cargo build --locked -p companion -p computer-mcp
cargo fmt --all
cargo clippy --workspace --all-targets -- -D warnings
env -u DISPLAY -u WAYLAND_DISPLAY -u DBUS_SESSION_BUS_ADDRESS cargo test --workspace
npm --prefix client ci --ignore-scripts --no-audit --no-fund
npm --prefix client run typecheck
npm --prefix client test
npm --prefix client run build
python3 -m unittest discover -s scripts/tests -v
The normal tests use fakes, isolated files and loopback; desktop adapter tests must run with display/session variables unset. Never enable desktop capture or run real agent inference merely to verify a build. Native Android and Windows runtime qualification are separate from compilation.
Offline preparation
Both Cargo workspaces have independent lockfiles. Populate the selected Cargo registry cache before disconnecting:
cargo fetch --locked --target x86_64-unknown-linux-gnu
cargo fetch --locked --target x86_64-pc-windows-gnullvm
cargo fetch --locked --manifest-path client/src-tauri/Cargo.toml --target aarch64-linux-android
npm --prefix client ci --ignore-scripts --no-audit --no-fund
Android additionally needs the Gradle distribution, plugins, runtime dependency
POMs/JARs/AARs, SDK and NDK already cached. Resolve those with the standard Tauri
Android build in your own prepared build environment before requiring offline
operation. Packaging forces Cargo, npm and Gradle offline and reports missing
cache entries. It never fetches speech models. Once cached, add --offline to
Cargo commands and npm ci; no registry credentials are needed for these public
dependencies.
Gradle checks its distribution against the wrapper’s distributionSha256Sum
(Gradle’s published value from https://gradle.org/release-checksums/) and every
plugin, POM and library against SHA-256 entries in
client/src-tauri/gen/android/gradle/verification-metadata.xml, and
scripts/tests/test_android_verification.py pins gradle-wrapper.jar to Gradle’s
published wrapper checksum from the same page. After changing the Gradle version
or an Android dependency, update both checksums and regenerate
the metadata in client/src-tauri/gen/android/ after one Tauri Android build.
Use an empty Gradle home, since a warm cache omits parent POMs, and review the
diff before committing.
rm gradle/verification-metadata.xml
GRADLE_USER_HOME="$(mktemp -d)" ./gradlew --write-verification-metadata sha256 \
-x rustBuildUniversalDebug -x rustBuildUniversalRelease \
assembleUniversalDebug assembleUniversalRelease bundleUniversalRelease \
:tauri-plugin-device:assembleDebugAndroidTest :tauri-plugin-device:lintDebug
Local release candidates
Run one command from a clean checkout with prerequisites and dependency caches prepared:
| Platform | Command | Successful output |
|---|---|---|
| Linux | scripts/package-linux.sh | dist/linux/<name>-<version>-x86_64-linux.tar.gz |
| Windows, cross-build only | scripts/package-windows.sh | dist/windows/<name>-<version>-x86_64-windows.zip |
| Android ARM64 | scripts/package-android.sh | unsigned APK/AAB and separate locally test-signed APK under dist/android/ |
All scripts generate and check licences before final distribution assembly.
The archives contain both companion binaries, LICENSE, NOTICE,
THIRD-PARTY-NOTICES.txt, sbom.cdx.json, the licence review report, and platform
installation instructions. Linux additionally includes install.sh and verifies
the extracted companion’s --version and --help using isolated application
directories. It never installs or starts a service; ophio service install
writes the user unit when someone runs it.
Windows checks x64 PE headers; no Windows runtime claim follows from that check.
dist/MANIFEST.txt lists every distribution file, byte count and SHA-256. Archive
entry order, timestamps and modes are stable for identical inputs;
SOURCE_DATE_EPOCH defaults to the current commit timestamp. This does not claim
bit-identical Rust/Gradle rebuilds across different toolchains or source paths.
The temporary Android test certificate changes on every packaging invocation.
Release builds remap checkout and toolchain-cache paths in Rust and native C/C++
diagnostics so packaged executables do not disclose the builder’s local paths.
Android builds in target/packaging/android/project/, copied from tracked
checkout inputs. Tauri’s generated native files never modify the checkout.
Its first build resolves the native project, then an offline Gradle report records
universalReleaseRuntimeClasspath, including AndroidX, Kotlin and plugin runtime
dependencies. The second build embeds verified licence assets. Generated assets
are available at client/dist/licenses/ in that staged build and as APK entries
assets/licenses/{LICENSE,NOTICE,THIRD-PARTY-NOTICES.txt,sbom.cdx.json}.
Settings → Versions → Open-source notices shows the bundled NOTICE and
THIRD-PARTY-NOTICES.txt from /licenses/ through Tauri’s embedded frontend; a
development build without them says so. The script verifies all four packaged
native asset bytes.
The locally test-signed APK contains the release build, signed with a fresh
throwaway Android Debug certificate, not a production signing identity. The
keystore and password exist only in a temporary build-output directory and are
deleted after signing. apksigner verify --verbose --print-certs and
aapt2 dump badging validate the test copy; identity, version and ARM64 ABI are
checked against the source configuration. No emulator or physical device is
started or installed to by packaging.
Real APK release signing is deliberately left out. Once a release key is chosen, the exact separate step is:
zipalign -f -P 16 4 unsigned.apk release-aligned.apk
apksigner sign --ks /path/to/release.keystore --ks-key-alias RELEASE_ALIAS \
--out release.apk release-aligned.apk
apksigner verify --verbose --print-certs release.apk
Allow the signing tool to prompt for passwords; never put them in a script or
argument. AAB signing, if selected for distribution, uses the upload key:
jarsigner -keystore /path/to/upload.keystore -signedjar release.aab unsigned.aab UPLOAD_ALIAS,
then jarsigner -verify -verbose release.aab. No real signing, store submission,
publishing, installation or upload occurs in these scripts.
Licence inventory and release checks
python3 scripts/licenses.py --check
scripts/source-candidate.sh --json target/packaging/source-candidate.json
python3 scripts/earlier_names.py
The offline generator reads locked Cargo metadata and uses Cargo’s target-specific normal/build dependency tree for the two host binaries or native phone shell. It excludes development edges and test-only crates. Build inputs are deliberately included even when the linker discards them. For the web client it conservatively includes the npm production dependency closure, including peers; it does not claim an exact Rollup/Rolldown tree-shaken module inventory. Bundled font files are identified by their hashes when the Android script supplies the built web directory. Standalone checks without it inventory all production font files. Icons, native runtime libraries, embedded SQLite/IJG material and pinned speech model downloads have separate entries. Models are labelled external runtime components and are never shipped as weight files.
Cargo hashes identify registry archives; npm integrity hashes identify package archives. Maven artifacts and font files have computed SHA-256 values. Model hashes are read from the speech catalog. Artifact hashes are in the distribution manifest. Missing Gradle reports, licence texts or unrecognized licence terms fail the check; an incomplete SBOM is explicitly marked incomplete and is never silently promoted into an archive. MPL/LGPL obligations appear as review items. The generator retains all supplied licence, copyright and NOTICE files, even when the selected licence expression permits a simpler alternative.
packaging/license-policy.toml holds policy and any exact, reasoned exceptions.
packaging/license-evidence.toml records hash-pinned upstream texts omitted from
published crate archives, fetched at their Cargo VCS revisions. These are
supplemental evidence, not policy waivers. Static runtime inventory changes need
review whenever a compiler, codec feature or platform target changes.
The source checker examines git archive HEAD, not uncommitted edits or the
repository history. It reports every forbidden path/content location and missing
owned-source SPDX header without echoing potentially private line contents. That
includes home directories on Linux, macOS and Windows, files named after work
items (such as U32.json) and notes on how the product was built. When the Git
directory holds an untracked info/private-terms file (one term per line, #
for comments), each term found in a path or line is reported by rule and
location, never by the term itself, and paths are shown with the term masked.
Only marked generated bindings have a documented header exception. Fix and
commit all findings, then rerun it. A passing scan is a bounded mechanical check,
not a substitute for reviewing a proposed public export for confidential text.
scripts/earlier_names.py fails when a tracked file uses the identity the
product had before it was named: its Android namespace, link schemes,
variables, Library file prefix, directories, commands or display name. Its
ALLOWED list names the files that carry an earlier install across, each with
its reason, and fails when one of them no longer needs the exception. The CI
source stage runs it.
Build a release from a clean tree after these checks and a review of its content.
Linux release executables use glibc 2.39 as their maximum ABI requirement
(Ubuntu 24.04). A build on a newer host is not a portable release: packaging
checks the version requirements of both ophio and computer-mcp with
readelf and rejects anything above that ceiling.
The disposable Ubuntu 24.04 lab guest can build offline from committed source
and vendored dependencies using tools/lab/guest/release.sh. It reuses the
installed Rust 1.98 toolchain and native libraries, writes only to the lab user’s
build directories, and installs no packages. This baseline was chosen because
the existing guest provides a reproducible older userspace without another image
or hosted build service. Extracted release archives must also be exercised there;
a successful build alone is not runtime qualification.