Skip to content

Commit d659de5

Browse files
authored
feat(release): publish checksums.txt and verify downloads in the installer (#68)
The build now writes a sha256sum-compatible checksums.txt next to the release archives and uploads it with them. The install script fetches it and verifies the downloaded archive before extraction, failing with the expected/actual digests on mismatch. Releases published before checksums.txt skip verification, as do systems with no sha256 tool.
1 parent 6f2fadf commit d659de5

3 files changed

Lines changed: 62 additions & 4 deletions

File tree

backend/cli/script/build.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,15 +207,21 @@ for (const item of targets) {
207207
}
208208

209209
if (Script.release) {
210+
const checksums: string[] = []
210211
for (const key of Object.keys(binaries)) {
211-
const archiveName = key.replace(`${pkg.name}-`, "openscience-")
212+
const archiveName = key.replace(`${pkg.name}-`, "openscience-") + (key.includes("linux") ? ".tar.gz" : ".zip")
212213
if (key.includes("linux")) {
213-
await $`tar -czf ${path.resolve(dir, `dist/${archiveName}.tar.gz`)} *`.cwd(`dist/${key}/bin`)
214+
await $`tar -czf ${path.resolve(dir, `dist/${archiveName}`)} *`.cwd(`dist/${key}/bin`)
214215
} else {
215-
await $`zip -r ${path.resolve(dir, `dist/${archiveName}.zip`)} *`.cwd(`dist/${key}/bin`)
216+
await $`zip -r ${path.resolve(dir, `dist/${archiveName}`)} *`.cwd(`dist/${key}/bin`)
216217
}
218+
const bytes = await Bun.file(path.resolve(dir, `dist/${archiveName}`)).arrayBuffer()
219+
const hash = new Bun.CryptoHasher("sha256").update(bytes).digest("hex")
220+
checksums.push(`${hash} ${archiveName}`)
217221
}
218-
await $`gh release upload v${Script.version} ./dist/*.zip ./dist/*.tar.gz --clobber`
222+
// sha256sum-compatible manifest; the install script verifies against it
223+
await Bun.write(path.resolve(dir, "dist/checksums.txt"), checksums.join("\n") + "\n")
224+
await $`gh release upload v${Script.version} ./dist/*.zip ./dist/*.tar.gz ./dist/checksums.txt --clobber`
219225
}
220226

221227
export { binaries }

frontend/landing/public/install

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,32 @@ download_and_install() {
331331
exit 1
332332
fi
333333

334+
# Verify against the release's checksums.txt when a sha256 tool exists.
335+
# Releases published before checksums.txt simply skip verification.
336+
local shatool=""
337+
if command -v sha256sum >/dev/null 2>&1; then
338+
shatool="sha256sum"
339+
elif command -v shasum >/dev/null 2>&1; then
340+
shatool="shasum -a 256"
341+
fi
342+
if [ -n "$shatool" ]; then
343+
local checksums_url="${url%/*}/checksums.txt"
344+
local expected
345+
expected=$(curl -fsSL "$checksums_url" 2>/dev/null | awk -v f="$filename" '$2 == f {print $1}') || expected=""
346+
if [ -n "$expected" ]; then
347+
local actual
348+
actual=$($shatool "$tmp_dir/$filename" | awk '{print $1}')
349+
if [ "$actual" != "$expected" ]; then
350+
echo -e "${RED}Checksum mismatch for ${filename}${NC}"
351+
echo -e "${MUTED}expected: ${expected}${NC}"
352+
echo -e "${MUTED}actual: ${actual}${NC}"
353+
rm -rf "$tmp_dir"
354+
exit 1
355+
fi
356+
print_message info "${MUTED}Checksum verified${NC}"
357+
fi
358+
fi
359+
334360
if [ "$os" = "linux" ]; then
335361
tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"
336362
else

install

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,32 @@ download_and_install() {
331331
exit 1
332332
fi
333333

334+
# Verify against the release's checksums.txt when a sha256 tool exists.
335+
# Releases published before checksums.txt simply skip verification.
336+
local shatool=""
337+
if command -v sha256sum >/dev/null 2>&1; then
338+
shatool="sha256sum"
339+
elif command -v shasum >/dev/null 2>&1; then
340+
shatool="shasum -a 256"
341+
fi
342+
if [ -n "$shatool" ]; then
343+
local checksums_url="${url%/*}/checksums.txt"
344+
local expected
345+
expected=$(curl -fsSL "$checksums_url" 2>/dev/null | awk -v f="$filename" '$2 == f {print $1}') || expected=""
346+
if [ -n "$expected" ]; then
347+
local actual
348+
actual=$($shatool "$tmp_dir/$filename" | awk '{print $1}')
349+
if [ "$actual" != "$expected" ]; then
350+
echo -e "${RED}Checksum mismatch for ${filename}${NC}"
351+
echo -e "${MUTED}expected: ${expected}${NC}"
352+
echo -e "${MUTED}actual: ${actual}${NC}"
353+
rm -rf "$tmp_dir"
354+
exit 1
355+
fi
356+
print_message info "${MUTED}Checksum verified${NC}"
357+
fi
358+
fi
359+
334360
if [ "$os" = "linux" ]; then
335361
tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"
336362
else

0 commit comments

Comments
 (0)