mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-21 16:02:20 +00:00
fix(release): name the server assets for whoever downloads them, not for cargo
`tty7-server-x86_64-unknown-linux-musl` was never a name anyone chose. Both
workflows staged the file as `tty7-server-${{ matrix.target }}`, so the build
triple went straight into a published filename — and the triple's *vendor*
field, for a Linux target with no particular vendor, is the literal word
`unknown`. It has been sitting on the releases page reading like a failed
lookup.
Of the triple's four fields only two say anything to whoever downloads this:
the architecture, which is what `asset_for_uname` picks by, and `musl`, which
is why one file runs on any distribution. So:
tty7-server-x86_64-unknown-linux-musl → tty7-server-linux-x86_64-musl
tty7-server-aarch64-unknown-linux-musl → tty7-server-linux-aarch64-musl
`<os>-<arch>` in that order because that is what the GUI assets in the same
release already use (`tty7-<version>-linux-x86_64.tar.gz`). One release should
be one naming scheme; it was two.
The triple stays everywhere it really is a build target — `cargo zigbuild
--target`, the `target/<triple>/release` path, the rust-cache key, ci.yml's
matrix. The workflows now carry both: `target` for the build, `asset` for the
filename, deliberately not the same string.
This name is a contract with more than the release step, and all of it moves
together:
- `install::asset::{ASSET_X86_64, ASSET_AARCH64}`, which is what the client
appends to a release URL.
- `bundle-windows.ps1`, which stages the musl binary for WSL. `wsl.rs` looks
for `<dir>/<asset name>` with nothing translating, so the *filename* is as
much a contract as the `server/` directory is — now said out loud in both
places, along with the consequence for `TTY7_BUNDLED_SERVER_DIR`: a
cross-compile has to be copied to the asset name, not left as `tty7-server`.
- The GUI's install prompt fixture, the checksum manifest fixtures, and the
`MissingBundled` assertions.
Nothing globs the old shape: `gh release upload dist/*`, `checksums.txt`'s
`find`, and the installer's `server\*` are all name-agnostic.
A new test pins both names as literals — the module header already says this
naming is "a *literal* contract with the release workflow", and asserting the
consts against themselves asserted nothing. It also fails on the substring
`unknown`, since that word only ever arrived here by way of `matrix.target`,
and checks neither name contains the other, which is what
`checksums::expected_digest` says out loud that it relies on.
Compatibility: a stable client asks its own frozen tag, which keeps whichever
name it shipped with, so every released client keeps working. The rolling
`nightly` tag is replaced each night and its prune step drops assets the run
did not upload — so an *already installed* nightly client 404s on the server
download until it updates itself. Accepted deliberately; the next release is
what has to be right.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,7 +33,11 @@ Copy-Item README.md "$Stage/README.md"
|
||||
# change on its own. Missing is a warning, not an error, matching `server-musl`'s
|
||||
# own skip-don't-fail probe; WSL then fails at connect time with a message
|
||||
# naming the directories it searched.
|
||||
$ServerAsset = "tty7-server-x86_64-unknown-linux-musl"
|
||||
#
|
||||
# The *filename* is a contract too, not just the directory: `wsl.rs` looks for
|
||||
# `<dir>/<asset name>`, so this string has to stay whatever
|
||||
# `install::asset::ASSET_X86_64` says it is.
|
||||
$ServerAsset = "tty7-server-linux-x86_64-musl"
|
||||
$ServerSrc = "bundled-server/$ServerAsset"
|
||||
if (Test-Path $ServerSrc) {
|
||||
New-Item -ItemType Directory -Force -Path "$Stage/server" | Out-Null
|
||||
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: nightly-server-x86_64-unknown-linux-musl
|
||||
name: nightly-tty7-server-linux-x86_64-musl
|
||||
path: tty7/bundled-server
|
||||
|
||||
- name: Package Windows installer + zip
|
||||
@@ -174,10 +174,15 @@ jobs:
|
||||
if: needs.plan.outputs.build == 'true'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# `target` is the build triple; `asset` is the published filename. Kept
|
||||
# apart for the reason release.yml spells out: the triple's vendor field is
|
||||
# `unknown`, and a download name has no business carrying it.
|
||||
matrix:
|
||||
target:
|
||||
- x86_64-unknown-linux-musl
|
||||
- aarch64-unknown-linux-musl
|
||||
include:
|
||||
- target: x86_64-unknown-linux-musl
|
||||
asset: tty7-server-linux-x86_64-musl
|
||||
- target: aarch64-unknown-linux-musl
|
||||
asset: tty7-server-linux-aarch64-musl
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTFLAGS: -C strip=symbols
|
||||
@@ -245,14 +250,14 @@ jobs:
|
||||
set -euo pipefail
|
||||
mkdir -p dist
|
||||
cp "target/${{ matrix.target }}/release/tty7-server" \
|
||||
"dist/tty7-server-${{ matrix.target }}"
|
||||
chmod +x "dist/tty7-server-${{ matrix.target }}"
|
||||
"dist/${{ matrix.asset }}"
|
||||
chmod +x "dist/${{ matrix.asset }}"
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: steps.probe.outputs.present == 'true'
|
||||
with:
|
||||
name: nightly-server-${{ matrix.target }}
|
||||
path: tty7/dist/tty7-server-${{ matrix.target }}
|
||||
name: nightly-${{ matrix.asset }}
|
||||
path: tty7/dist/${{ matrix.asset }}
|
||||
if-no-files-found: error
|
||||
|
||||
# Single publish step after all platforms succeed, so the rolling release is
|
||||
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: release-server-x86_64-unknown-linux-musl
|
||||
name: release-tty7-server-linux-x86_64-musl
|
||||
path: tty7/bundled-server
|
||||
|
||||
- name: Package Windows installer + zip
|
||||
@@ -161,10 +161,16 @@ jobs:
|
||||
server-musl:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# `target` is the build triple; `asset` is the published filename. They
|
||||
# are deliberately not the same string — the triple's vendor field is
|
||||
# `unknown`, which says nothing to anyone reading the releases page. See
|
||||
# `install::asset::ASSET_X86_64`, which pins these two names as literals.
|
||||
matrix:
|
||||
target:
|
||||
- x86_64-unknown-linux-musl
|
||||
- aarch64-unknown-linux-musl
|
||||
include:
|
||||
- target: x86_64-unknown-linux-musl
|
||||
asset: tty7-server-linux-x86_64-musl
|
||||
- target: aarch64-unknown-linux-musl
|
||||
asset: tty7-server-linux-aarch64-musl
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTFLAGS: -C strip=symbols
|
||||
@@ -234,14 +240,14 @@ jobs:
|
||||
set -euo pipefail
|
||||
mkdir -p dist
|
||||
cp "target/${{ matrix.target }}/release/tty7-server" \
|
||||
"dist/tty7-server-${{ matrix.target }}"
|
||||
chmod +x "dist/tty7-server-${{ matrix.target }}"
|
||||
"dist/${{ matrix.asset }}"
|
||||
chmod +x "dist/${{ matrix.asset }}"
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: steps.probe.outputs.present == 'true'
|
||||
with:
|
||||
name: release-server-${{ matrix.target }}
|
||||
path: tty7/dist/tty7-server-${{ matrix.target }}
|
||||
name: release-${{ matrix.asset }}
|
||||
path: tty7/dist/${{ matrix.asset }}
|
||||
if-no-files-found: error
|
||||
|
||||
# Single assembly step, after all four platforms succeed. The release object is
|
||||
|
||||
@@ -10,9 +10,23 @@
|
||||
use std::fmt;
|
||||
|
||||
/// The release asset for a 64-bit x86 Linux box.
|
||||
pub const ASSET_X86_64: &str = "tty7-server-x86_64-unknown-linux-musl";
|
||||
/// The release asset for a 64-bit ARM Linux box.
|
||||
pub const ASSET_AARCH64: &str = "tty7-server-aarch64-unknown-linux-musl";
|
||||
///
|
||||
/// **`<os>-<arch>-musl`, not the Rust target triple.** These names used to be
|
||||
/// `${{ matrix.target }}` pasted into a filename, which put `unknown` — the
|
||||
/// triple's *vendor* field, meaning "no particular vendor" — in front of anyone
|
||||
/// reading the releases page. Of the triple's four fields only two say anything
|
||||
/// to whoever downloads this: the architecture, which is what `asset_for_uname`
|
||||
/// picks by, and `musl`, which is why one file runs on any distribution. The
|
||||
/// order matches the GUI assets the same release publishes
|
||||
/// (`tty7-<version>-linux-x86_64.tar.gz`), so one release is one naming scheme.
|
||||
///
|
||||
/// The build target keeps the triple wherever it really is one — `cargo
|
||||
/// zigbuild --target`, the `target/<triple>/release` path, the cache key. This
|
||||
/// is a *download* name, and the two are no longer spelled the same on purpose.
|
||||
pub const ASSET_X86_64: &str = "tty7-server-linux-x86_64-musl";
|
||||
/// The release asset for a 64-bit ARM Linux box. See [`ASSET_X86_64`] for the
|
||||
/// naming.
|
||||
pub const ASSET_AARCH64: &str = "tty7-server-linux-aarch64-musl";
|
||||
/// The sha256 manifest published beside every asset in a release.
|
||||
pub const CHECKSUMS_ASSET: &str = "checksums.txt";
|
||||
|
||||
@@ -382,7 +396,7 @@ mod tests {
|
||||
fn download_urls_point_at_the_release_the_tag_names() {
|
||||
assert_eq!(
|
||||
download_url(&release_tag("26.7.5"), ASSET_X86_64),
|
||||
"https://github.com/l0ng-ai/tty7/releases/download/v26.7.5/tty7-server-x86_64-unknown-linux-musl"
|
||||
"https://github.com/l0ng-ai/tty7/releases/download/v26.7.5/tty7-server-linux-x86_64-musl"
|
||||
);
|
||||
assert_eq!(
|
||||
download_url(&release_tag("26.7.6-nightly.20260727"), CHECKSUMS_ASSET),
|
||||
@@ -390,6 +404,33 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// **The asset names, pinned as literals.**
|
||||
///
|
||||
/// They are one half of a contract whose other half is a `cp` in two
|
||||
/// workflow files, and checking them against the consts they come from
|
||||
/// would assert nothing. A literal here is what makes changing one side
|
||||
/// without the other a failing test rather than a 404 on a user's machine.
|
||||
///
|
||||
/// Including the absence of `unknown`: that word only ever reached these
|
||||
/// names by way of `${{ matrix.target }}`, and a build triple pasted into a
|
||||
/// download name is worth failing on rather than explaining again.
|
||||
#[test]
|
||||
fn asset_names_are_the_ones_the_release_workflow_publishes() {
|
||||
assert_eq!(ASSET_X86_64, "tty7-server-linux-x86_64-musl");
|
||||
assert_eq!(ASSET_AARCH64, "tty7-server-linux-aarch64-musl");
|
||||
for asset in [ASSET_X86_64, ASSET_AARCH64] {
|
||||
assert!(
|
||||
!asset.contains("unknown"),
|
||||
"{asset} carries the triple's vendor field"
|
||||
);
|
||||
}
|
||||
// `checksums::expected_digest` matches the filename field whole, and
|
||||
// says outright that it relies on no asset name being a substring of
|
||||
// another. Two names is the whole set, so check it here.
|
||||
assert!(!ASSET_X86_64.contains(ASSET_AARCH64));
|
||||
assert!(!ASSET_AARCH64.contains(ASSET_X86_64));
|
||||
}
|
||||
|
||||
/// Path construction, including the `mkdir` chain. Asserted literally: these
|
||||
/// strings are what an SFTP server sees, and a `\` in any of them (which is
|
||||
/// what `PathBuf::join` would produce on a Windows client) would create a file
|
||||
|
||||
@@ -96,9 +96,9 @@ fn parse_hex(s: &str) -> Option<Digest> {
|
||||
/// The digest `manifest` records for `asset`.
|
||||
///
|
||||
/// **The filename field is matched whole, never by substring.**
|
||||
/// `tty7-server-x86_64-unknown-linux-musl` happens not to be a substring of any
|
||||
/// other asset today, but that is an accident of the current release contents,
|
||||
/// not a property anyone maintains — and a substring match that drifted would
|
||||
/// `tty7-server-linux-x86_64-musl` happens not to be a substring of any other
|
||||
/// asset today, but that is an accident of the current release contents, not a
|
||||
/// property anyone maintains — and a substring match that drifted would
|
||||
/// silently verify one binary's bytes against another's digest.
|
||||
///
|
||||
/// The coreutils format is `<digest><two spaces><name>`; the second space is `*`
|
||||
@@ -239,9 +239,9 @@ mod tests {
|
||||
#[test]
|
||||
fn a_malformed_entry_aborts() {
|
||||
for bad in [
|
||||
"abc tty7-server-x86_64-unknown-linux-musl",
|
||||
"zz786850e387550fdab836ed7e6dc881de23001b4b4d8ec3a1a0b9d5e0d5c0f1x tty7-server-x86_64-unknown-linux-musl",
|
||||
" tty7-server-x86_64-unknown-linux-musl",
|
||||
"abc tty7-server-linux-x86_64-musl",
|
||||
"zz786850e387550fdab836ed7e6dc881de23001b4b4d8ec3a1a0b9d5e0d5c0f1x tty7-server-linux-x86_64-musl",
|
||||
" tty7-server-linux-x86_64-musl",
|
||||
] {
|
||||
let err = expected_digest(bad, ASSET_X86_64).unwrap_err();
|
||||
assert!(
|
||||
|
||||
@@ -396,7 +396,7 @@ pub struct InstallRequest {
|
||||
pub host: String,
|
||||
/// The version about to be installed (= the client's own version).
|
||||
pub version: String,
|
||||
/// The release asset name, e.g. `tty7-server-x86_64-unknown-linux-musl`.
|
||||
/// The release asset name, e.g. `tty7-server-linux-x86_64-musl`.
|
||||
pub asset: &'static str,
|
||||
/// The URL it was downloaded from.
|
||||
pub source_url: String,
|
||||
|
||||
@@ -768,13 +768,24 @@ impl RemoteOps for WslRemoteOps {
|
||||
/// Overrides where the bundled Linux server binaries are looked for. Exists so
|
||||
/// this can be tested, and so a developer running an unpackaged build can point
|
||||
/// at a `cargo build --target x86_64-unknown-linux-musl` output.
|
||||
///
|
||||
/// **The file in it has to carry the asset name**, not cargo's — [`Self::locate`]
|
||||
/// joins the directory with
|
||||
/// [`asset::ASSET_X86_64`](crate::daemon::install::asset::ASSET_X86_64) and
|
||||
/// nothing translates, so a cross-compile has to be copied to
|
||||
/// `tty7-server-linux-x86_64-musl` rather than left as plain `tty7-server`.
|
||||
///
|
||||
/// [`Self::locate`]: BundledServerBinary::locate
|
||||
pub const BUNDLED_DIR_ENV: &str = "TTY7_BUNDLED_SERVER_DIR";
|
||||
|
||||
/// The subdirectory beside the client executable that a packaged build puts the
|
||||
/// Linux server binaries in. **This is the contract with the release workflow**:
|
||||
/// the Windows installer must place
|
||||
/// `<install dir>/server/tty7-server-x86_64-unknown-linux-musl` (and the
|
||||
/// `aarch64` one, for WSL on ARM Windows).
|
||||
/// `<install dir>/server/tty7-server-linux-x86_64-musl` (and the `aarch64` one,
|
||||
/// for WSL on ARM Windows) — the filenames
|
||||
/// [`asset::ASSET_X86_64`](crate::daemon::install::asset::ASSET_X86_64) names,
|
||||
/// because [`BundledServerBinary::locate`] joins the directory with the asset
|
||||
/// name and nothing translates between the two.
|
||||
pub const BUNDLED_SUBDIR: &str = "server";
|
||||
|
||||
/// Directories a bundled Linux server binary is looked for in, most specific
|
||||
@@ -1681,10 +1692,7 @@ mod tests {
|
||||
.load("26.7.5", super::super::asset::ASSET_X86_64)
|
||||
.expect_err("nothing bundled yet");
|
||||
let msg = err.to_string();
|
||||
assert!(
|
||||
msg.contains("tty7-server-x86_64-unknown-linux-musl"),
|
||||
"{msg}"
|
||||
);
|
||||
assert!(msg.contains("tty7-server-linux-x86_64-musl"), "{msg}");
|
||||
assert!(msg.contains(&tmp.display().to_string()), "{msg}");
|
||||
assert!(matches!(err, InstallError::MissingBundled { .. }));
|
||||
|
||||
@@ -2074,7 +2082,7 @@ mod tests {
|
||||
.run()
|
||||
.expect_err("no aarch64 binary bundled");
|
||||
let msg = err.to_string();
|
||||
assert!(msg.contains("aarch64-unknown-linux-musl"), "{msg}");
|
||||
assert!(msg.contains("linux-aarch64-musl"), "{msg}");
|
||||
|
||||
// With it bundled, the same install goes through.
|
||||
std::fs::write(dir.join(super::super::asset::ASSET_AARCH64), b"\x7fELF arm").unwrap();
|
||||
|
||||
@@ -1151,7 +1151,7 @@ mod tests {
|
||||
InstallRequest {
|
||||
host: "me@build-box:22".into(),
|
||||
version: "0.9.1".into(),
|
||||
asset: "tty7-server-x86_64-unknown-linux-musl",
|
||||
asset: "tty7-server-linux-x86_64-musl",
|
||||
source_url: "https://example.invalid/v0.9.1/tty7-server".into(),
|
||||
remote_path: "/home/me/.local/share/tty7/bin/tty7-server-0.9.1".into(),
|
||||
size_bytes: 9_437_184,
|
||||
|
||||
Reference in New Issue
Block a user