LG Buddy uses three persistent branches as source channels:
mainpoints to the current stable release.prereleaseis equal tomainor ahead of it and points to the current prerelease when ahead.devis the ordinary integration branch and may contain unreleased work.
The intended ancestry is main <= prerelease <= dev. Ordinary changes merge
into dev. An official release requires a promotion PR whose head is the
same-repository dev branch and whose base is main or prerelease.
The promotion PR is the code and release-identity review surface. Required checks gate its merge, and merging the PR authorizes preparation of a verified release draft. The merged target-branch commit is the release commit. Publishing that draft is a separate explicit approval after its release notes and assets have been reviewed.
The release App is not a substitute for merging. After the merged commit has
passed the release build and smoke test, the App performs protected tag and
draft-release writes and keeps the persistent streams aligned. A prerelease merge
advances dev to the merged prerelease commit. A stable merge advances both
prerelease and dev to the merged stable commit.
Required promotion checks prove that:
- the PR is
dev -> mainordev -> prerelease Cargo.tomlandCargo.lockdeclare the samelg-buddyversion- a stable target has a stable SemVer and a prerelease target has a prerelease SemVer
- the version advances both existing release-channel heads
- the persistent branches have not moved or diverged before merge
- the derived
v<crate-version>tag is absent before merge - normal CI and the release-bundle smoke test pass; the bundle smoke includes a
pinned cross-version upgrade from
v1.4.0-beta.2
Requiring every candidate to advance both release-channel heads keeps release publication globally monotonic. The newest published release is therefore also the highest semantic version and prerelease-channel clients do not scan release history to determine ordering.
The tag, binary, archive, and GitHub release all use the Cargo package version. There is no separate version input.
- Prepare the exact release version in
Cargo.tomlandCargo.lockondev. - Open a PR from
devtoprereleaseormain. - Wait for
verify,bundle-smoke-test, andvalidate-promotionto pass. - Merge the promotion PR. This authorizes preparation of the release draft.
- The resulting push starts the serialized release workflow, which builds and smoke-tests the merged release commit without write credentials, including an archive-driven upgrade from the pinned public baseline.
- The staging job obtains a short-lived token from the dedicated repository-only GitHub App, aligns the remaining release streams, creates or resumes the exact release draft, uploads any missing assets, and verifies the complete asset set.
- Fill in the draft's release-specific notes and review its title, classification,
tag, and assets. Then approve the waiting
release-publicationenvironment. The approval does not accept an independent version, tag, commit, artifact, or workflow-run identifier. - The publication job downloads the same workflow artifact again, revalidates the merged refs, tag, checksums, manifest, draft classification, complete remote asset set, and non-placeholder notes, then makes only that draft public. It preserves the reviewed title and notes. Repository release immutability then locks the tag and assets.
- A published prerelease then runs
v1.4.0-beta.2through the real productionlg-buddy updates installpath. The newly installed candidate performs a cold-cache production update check, and the canary records sanitized GitHub response evidence for the deterministic mock. This is supplemental post-publication evidence, not a prerequisite for stable promotion.
Promotion through prerelease is optional. A direct dev -> main promotion
runs the same required PR validation and post-merge release build as a
dev -> prerelease promotion. During stable draft staging, the release App
fast-forwards both prerelease and dev to the reviewed stable commit.
Replace the draft's placeholder with concise, release-specific notes before approving publication. The workflow does not impose a notes template, but it refuses to publish empty, whitespace-only, or unchanged placeholder notes.
Do not push version tags manually. Protected v* tags and stream-alignment
writes permit bypass only to the dedicated release App. A failed post-merge
release run can be rerun safely: an incomplete draft remains private and is
resumed only when its tag, classification, and existing assets match the merged
release commit. If publication was attempted before the notes were ready, update
the draft, rerun the failed jobs, and approve the environment again. The staged
workflow artifact is retained for 35 days, leaving a margin beyond GitHub's
30-day maximum wait for an environment approval; after that, rerun the whole
workflow to rebuild it. A published release is accepted only when its expected
asset set is complete and byte-for-byte identical.
Repository release immutability must remain enabled. GitHub applies it only when a draft is published, so the publisher uploads and verifies every expected asset before making the release visible. Each stored asset must report the expected name, uploaded state, byte size, and server-computed SHA-256 digest. Unexpected draft assets block publication; published releases are verification-only and are never repaired in place.
The workflow:
- Validates the merged target-branch commit, Cargo version, and tag state.
- Builds a static
x86_64-unknown-linux-muslbinary with exact version and commit identity. - Generates a versioned identity manifest and packages the release bundle.
- Validates the manifest and installs the bundle in an isolated smoke-test root.
- Verifies the built and installed binary's exact version, channel, and commit.
- Upgrades a pinned real previous archive to the candidate and verifies preserved user state plus replaced owned integration files.
- Generates and verifies
sha256sums.txt. - Keeps
main,prerelease, anddevaligned for the next promotion. - Stages and verifies the exact release assets privately.
- Waits for release-note review and explicit publication approval.
- Revalidates and publishes only the staged draft, preserving its reviewed title and notes.
- Downloads the published assets and verifies their checksums independently.
- For prereleases, exercises production GitHub discovery, acquisition, confirmation, installation, and final identity from the pinned baseline.
install.sh is only an installer. It does not build the runtime or GUI.
Every release archive contains release-manifest.json at the bundle root. The
schema-versioned JSON records the exact release tag, semantic version, release
channel, headless Rust target triple, GTK GUI target triple, and full lowercase commit SHA. Version, tag, and
channel must agree: stable SemVer maps to stable, prerelease SemVer maps to
prerelease, and the tag is exactly v<version>.
Schema 1 keeps the original five identity fields critical and adds
gui_target as a required extension for GUI-bearing bundles. Keeping it
non-critical lets the public v1.4 updater read the new manifest. Validators reject an
unsupported schema, duplicate JSON fields, missing identity fields, and unknown
critical fields. Unknown non-critical fields may be ignored for compatible
schema evolution. Official manifests use a deterministic field order and JSON
rendering.
The bundle builder derives version, channel, and commit from both executables;
it cannot package binaries whose embedded identities, targets, or --version
output disagree. The archive remains named for the static
x86_64-unknown-linux-musl runtime. The dynamic
x86_64-unknown-linux-gnu GUI is stored as
docs/lg-buddy-gui-x86_64-unknown-linux-gnu: that established namespace is
understood by the v1.4 updater. The named application icon is likewise carried
under docs/ for compatibility. The installer publishes them as
/usr/bin/lg-buddy-gui and under the hicolor application-icon directory, and
safely restores GUI executable mode when an older updater extracted it as data.
Smoke validation checks the manifest before executing installer code and then
compares it with both the bundled and installed binary. Publishing validates
the manifest directly from each archive without extracting or executing archive
content. CI builds on Ubuntu 24.04, establishing GTK 4.14, libadwaita 1.5, and
a maximum GLIBC 2.39 symbol baseline. Fedora 43 and current Arch lanes install
and launch the same artifact under Xvfb, drive it without a pointer, inspect its
AT-SPI tree, and compare its light/dark and 1x/2x rendering; Ubuntu remains the
ABI baseline.
Release-bundle replacement is guarded by observed capability rather than a distribution allowlist or provenance receipt. The initial runtime preflight checks the installed mutable FHS topology, config discovery, ownership, path types and mount boundaries, trusted system containment, integration override state, and system/user service-manager availability before an updater performs release or privilege-related effects. A verified candidate's binary performs a second pass for its own installer requirements and trusted external ancestor chain before privileged mutation. The checker assigns each target an installer-operation policy so replacement, directory mutation, recursive repair, exact drop-in, and candidate-input requirements cannot silently lose their operation-specific safeguards.
The installed GUI uses the same compatibility checks with its own installed executable path; a source checkout or externally managed GUI cannot substitute for the mutable release-bundle installation. GUI updates keep confirmation and network work unprivileged, then use one graphical authorization for a bounded system-installation helper. User configuration and user service operations remain in the invoking user's process. CLI updates retain terminal confirmation and their existing sudo behavior.
The extracted candidate exposes this second pass through the hidden
upgrade-preflight installer entrypoint. install.sh --upgrade invokes it
before sudo or installation writes, loads the existing config pointer and
settings without rewriting them, and never runs configuration, discovery, or
pairing. Native and healthy compatibility installations preserve their Python
environment; an unhealthy compatibility environment must pass the additional
recursive-repair checks before it is rebuilt. After replacing owned runtime and
integration files, the installer reloads system integrations before user
integrations and verifies that the installed binary matches the candidate.
This is intentionally a conservative and evolving refusal boundary. It does not migrate legacy layouts, declare broad host support, or guarantee that a later privileged operation cannot fail.
v1.4.0-beta.2 is the first public updater-capable release and is the pinned
previous archive for the initial cross-version contract. Its
x86_64-unknown-linux-musl archive SHA-256 is
883e6cb869cbe60988a195acac2e15864d904797edfefbb7d90052eff9a17d32.
CI verifies that digest and the full release identity before extracting or
executing the baseline.
Versions before v1.4.0-beta.2 do not contain updates install. They require
one normal manual installation of an updater-capable release before assisted
upgrades become available. Arbitrary historical upgrade support is not implied.
Nix configurations may select main, prerelease, or dev as the upstream
source according to the desired stability. The Nix lock file must continue to
pin an exact commit: the branch selects the update stream, not an implicitly
moving deployment.
This source selection is separate from LG Buddy's runtime updates.channel
setting, which controls GitHub release discovery for installed release bundles.
End users can extract the release archive and run:
./install.shThat path uses the bundled lg-buddy binary and does not require a Rust toolchain.
For a fresh installation it opens the installed GUI for TV pairing and attempts
the default Idle Blanking and TV Sleep & Wake behaviors. An unavailable or
declined behavior stays off and can be retried in Settings. Run it as the regular
user; the installer and GUI request only the elevation their system operations
need. For terminal-only setup, run ./configure.sh first. Existing TV
configurations retain their saved preferences.
To update an existing compatible release-bundle installation from an already verified and extracted newer bundle, run as the installed user:
./install.sh --upgradeAn incompatible or legacy layout is refused rather than migrated. If a failure
occurs after installation writes begin, correct the reported cause and rerun the
same verified bundle with --upgrade.
If you build lg-buddy and lg-buddy-gui yourself, install them by passing both
binary paths explicitly:
./install.sh \
--runtime-binary ./target/x86_64-unknown-linux-musl/release/lg-buddy \
--gui-binary ./target/x86_64-unknown-linux-gnu/release/lg-buddy-gui