diff --git a/README.md b/README.md index 4996e66..6b46134 100644 --- a/README.md +++ b/README.md @@ -355,8 +355,8 @@ Upgrading the fabric binary under a running daemon — especially on a remote machine reached only over `fabric shell` — must be done lockout-safe: a botched restart can sever the only path back to the box. Follow this order. -Download a release asset directly, verify it against the release's combined -`SHA256SUMS` manifest, and stage both the old and new binaries with same-directory +Download a release asset directly, verify it against that asset's published +`.sha256`, and stage both the old and new binaries with same-directory renames. A release archive contains exactly one member named literal `fabric` (not `./fabric`); verify that shape before extracting: @@ -377,9 +377,12 @@ download_dir="$(mktemp -d)" trap 'rm -rf "$download_dir"' EXIT curl --fail --location "$release_url/$asset" --output "$download_dir/$asset" -curl --fail --location "$release_url/SHA256SUMS" --output "$download_dir/SHA256SUMS" +curl --fail --location "$release_url/$asset.sha256" --output "$download_dir/$asset.sha256" -expected="$(awk -v asset="$asset" '$2 == asset { print $1 }' "$download_dir/SHA256SUMS")" +# The release publishes ONE .sha256 per asset, not a combined manifest. The file +# holds " dist/", with the builder's path still in it, so +# `shasum -c` fails here. Take field one and compare it ourselves. +expected="$(awk 'NR == 1 { print $1 }' "$download_dir/$asset.sha256")" test -n "$expected" if command -v sha256sum >/dev/null 2>&1; then actual="$(sha256sum "$download_dir/$asset" | awk '{ print $1 }')" diff --git a/src/sync/manifest.rs b/src/sync/manifest.rs index 3561242..4c6db05 100644 --- a/src/sync/manifest.rs +++ b/src/sync/manifest.rs @@ -56,8 +56,24 @@ pub struct FileMeta { pub hash: ContentHash, /// File size in bytes (informational; the hash is the identity). pub size: u64, - /// Whether the file is executable, the way git tracks it: a file is either - /// mode 644 or 755, and no other permission bit is replicated. + /// Whether the file is executable, the way git tracks it. ONLY this bit + /// replicates; no other permission bit crosses. + /// + /// The resulting MODE is not fixed, and an earlier version of this comment + /// claimed it was. Materialization creates the file with `fs::write`, which + /// yields `0666 & ~umask`, and then ORs in `0o111`. So the mode depends on + /// the RECEIVING host's umask: + /// + /// | receiver umask | plain file | executable file | + /// | --- | --- | --- | + /// | 022 | 0644 | 0755 | + /// | 002 | 0664 | 0775 | + /// + /// Observed on hetz and droppy on 2026-08-23: 0664 and 0775, both umask + /// 002. Do NOT depend on an exact mode here; depend only on the executable + /// bit. st2's render deliberately differs: it writes an exact mode that is + /// immune to umask, so the two systems do not agree on the other bits and + /// are not meant to. /// /// Defaulted so a peer that predates this field still parses. Note the /// asymmetry: adding a field with a default is safe, removing one is not.