Skip to content

Merge pull request #2665 from nahuhh/18.5-hashes_beta #45

Merge pull request #2665 from nahuhh/18.5-hashes_beta

Merge pull request #2665 from nahuhh/18.5-hashes_beta #45

# Upgrade and redesign of the original hashes.yml workflow by 00-matt, plowsof, binaryFate
# Remade by: redsh4de for the Monero site redesign project
# Purpose: Validate Monero download hashes, torrents, and webseeds + create PRs to update download info
name: "Download Hashes"
on:
push:
paths:
- "downloads/hashes.txt"
pull_request:
paths:
- "downloads/hashes.txt"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
validate-download-hashes:
name: "Validate signed hashes and downloads"
runs-on: ubuntu-22.04
env:
HASH_FILE: ${{ github.workspace }}/downloads/hashes.txt
outputs:
magnet_gui: ${{ steps.verify-torrents.outputs.magnet_gui }}
magnet_cli: ${{ steps.verify-torrents.outputs.magnet_cli }}
version_gui: ${{ steps.verify-filenames.outputs.version_gui }}
version_cli: ${{ steps.verify-filenames.outputs.version_cli }}
steps:
- uses: actions/checkout@v6
- name: Install dependencies
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y \
ca-certificates \
coreutils \
curl \
gnupg \
git \
bzip2 \
diffutils \
bash \
tar \
transmission-cli \
jq
- name: Verify hashes.txt signature
run: |
set -euo pipefail
export GNUPGHOME="$(mktemp -d)"
curl -sSfL --proto '=https' --tlsv1.2 \
"https://raw.githubusercontent.com/monero-project/monero/master/utils/gpg_keys/binaryfate.asc" |
gpg --import
gpg --batch --verify "$HASH_FILE"
- name: Verify filenames
id: verify-filenames
run: |
set -euo pipefail
lines="$(grep -v ^# "$HASH_FILE")"
SAVEIFS=$IFS
IFS=$'\n'
lines=($lines)
IFS=$SAVEIFS
version_gui=$(awk '/monero-gui-source-v/ {print $2}' "$HASH_FILE" | awk -F".tar.bz2" '{print $1}' | awk -F"-" '{print $4}')
version_cli=$(awk '/monero-source-v/ {print $2}' "$HASH_FILE" | awk -F".tar.bz2" '{print $1}' | awk -F"-" '{print $3}')
echo "version_gui=$version_gui" >> $GITHUB_OUTPUT
echo "version_cli=$version_cli" >> $GITHUB_OUTPUT
filenames_cli=()
filenames_gui=()
get_filename(){
line=$1
the_line=($line)
length="${#the_line[@]}"
((length-=1))
filename="${the_line[$length]}"
echo "${filename}"
}
# expects cli files between lines 2-14 and gui 15-19 (comments do not count, 1st line = 0)
# to add a new file to the cli, $num must be -gt 1 and -lt 16.
# gui $num is now -gt 15 and -lt 21 (new line has been added above)
# a new gui file will only increase the -lt number by 1
# changes to extensions / new files must be reflected in the cli_files / gui_files lists below
num=0
for line in "${lines[@]}"; do
if [ $num -gt 1 ] && [ $num -lt 15 ] ; then
#CLI
filename=$(get_filename "${line}")
filenames_cli+=("${filename}")
elif [ $num -gt 14 ] && [ $num -lt 21 ] ; then
#GUI
filename=$(get_filename "${line}")
filenames_gui+=("${filename}")
fi
((num+=1))
done
# edit/add/remove filenames below
cli_files=(\
"monero-android-armv7-${version_cli}.tar.bz2" \
"monero-android-armv8-${version_cli}.tar.bz2" \
"monero-freebsd-x64-${version_cli}.tar.bz2" \
"monero-linux-armv7-${version_cli}.tar.bz2" \
"monero-linux-armv8-${version_cli}.tar.bz2" \
"monero-linux-riscv64-${version_cli}.tar.bz2" \
"monero-linux-x64-${version_cli}.tar.bz2" \
"monero-linux-x86-${version_cli}.tar.bz2" \
"monero-mac-armv8-${version_cli}.tar.bz2" \
"monero-mac-x64-${version_cli}.tar.bz2" \
"monero-win-x64-${version_cli}.zip" \
"monero-win-x86-${version_cli}.zip" \
"monero-source-${version_cli}.tar.bz2")
gui_files=(\
"monero-gui-install-win-x64-${version_gui}.exe" \
"monero-gui-linux-x64-${version_gui}.tar.bz2" \
"monero-gui-mac-x64-${version_gui}.dmg" \
"monero-gui-mac-armv8-${version_gui}.dmg" \
"monero-gui-win-x64-${version_gui}.zip" \
"monero-gui-source-${version_gui}.tar.bz2")
check_filenames(){
local -n file_list=$1
local -n hardcoded=$2
for f in "${file_list[@]}"; do
if [[ "${hardcoded[*]}" =~ "${f}" ]]; then
echo "Filename OK: ${f}"
else
echo "Filename BAD: ${f}"
exit 1
fi
done
}
check_filenames filenames_cli cli_files
check_filenames filenames_gui gui_files
- name: Download releases and verify hashes
run: |
set -euo pipefail
for file in $(awk '/monero-/ {print $2}' "$HASH_FILE"); do
[ -f "$file" ] && continue
dir="cli"
if [[ $file =~ gui ]]; then
dir="gui"
fi
url="https://dlsrc.getmonero.org/${dir}/${file}"
echo "Downloading $file from $url..."
curl --retry 3 --retry-delay 2 -sSfL --proto '=https' --tlsv1.2 "$url" -o "$file"
sleep 0.5
done
grep monero- "$HASH_FILE" | sha256sum -c
- name: Validate source integrity
id: validate-sources
run: |
set -euo pipefail
version_gui="${{ steps.verify-filenames.outputs.version_gui }}"
version_cli="${{ steps.verify-filenames.outputs.version_cli }}"
echo -e "\n--> GUI version: $version_gui \n--> CLI version: $version_cli"
mkdir validate_sources
cd validate_sources
# Download / verify git-archive-all.sh
curl -O https://raw.githubusercontent.com/fabacab/git-archive-all.sh/fc86194f00b678438f9210859597f6eead28e765/git-archive-all.sh
echo "db62e9a824866989c9d080f008ec06d81421cf94bed3762acba3b9148607af2d git-archive-all.sh" | sha256sum -c
chmod +x git-archive-all.sh
echo -e "--> Generating tarballs..."
# CLI
git clone --recursive -b "$version_cli" --depth 1 --shallow-submodules https://github.com/monero-project/monero.git monero.git && cd monero.git && ../git-archive-all.sh --prefix monero-source-${version_cli}/ --format tar --tree-ish $version_cli ../monero-source-${version_cli}.tar && cd .. && bzip2 monero-source-${version_cli}.tar
# GUI
git clone --recursive -b "$version_gui" --depth 1 --shallow-submodules https://github.com/monero-project/monero-gui.git monero-gui.git && cd monero-gui.git && ../git-archive-all.sh --prefix monero-gui-source-${version_gui}/ --format tar --tree-ish $version_gui ../monero-gui-source-${version_gui}.tar && cd .. && bzip2 monero-gui-source-${version_gui}.tar
mkdir yours
cp monero-gui-source-${version_gui}.tar.bz2 yours/.
cp monero-source-${version_cli}.tar.bz2 yours/.
mkdir from_website
echo -e "\n--> Move tarballs from getmonero..."
cp ../monero-gui-source-${version_gui}.tar.bz2 from_website/.
cp ../monero-source-${version_cli}.tar.bz2 from_website/.
echo -e "\n--> Unpacking all..."
bunzip2 yours/*.bz2
bunzip2 from_website/*.bz2
tar xf yours/monero-source-${version_cli}.tar -C yours/
tar xf yours/monero-gui-source-${version_gui}.tar -C yours/
tar xf from_website/monero-source-${version_cli}.tar -C from_website/
tar xf from_website/monero-gui-source-${version_gui}.tar -C from_website
# Compare directories
echo -e "\n--> Comparing CLI directories"
diff -r yours/monero-source-$version_cli from_website/monero-source-$version_cli
echo -e "\n--> Comparing GUI directories"
diff -r yours/monero-gui-source-$version_gui from_website/monero-gui-source-$version_gui
- name: Verify torrent + magnet links
id: verify-torrents
run: |
set -euo pipefail
version_gui="${{ steps.verify-filenames.outputs.version_gui }}"
version_cli="${{ steps.verify-filenames.outputs.version_cli }}"
# Clone and pin the monero-torrent repo
git clone --recurse-submodules https://github.com/plowsof/monero-torrent.git
cd monero-torrent
git reset --hard 4727b8fd44b9e9b0adfa8d2dc8ed5edef517814a
cd ..
# Move the already-downloaded monero-* files into cdn
mkdir monero-torrent/cdn
awk '/monero-/ {print $2}' "$HASH_FILE" | while read -r f; do
[ -z "$f" ] && continue
mv "$f" monero-torrent/cdn/
done
# Add GPG key and hashes.txt into cdn
wget -q \
https://raw.githubusercontent.com/monero-project/monero/master/utils/gpg_keys/binaryfate.asc \
-O monero-torrent/cdn/binaryfate.asc
cp "$HASH_FILE" monero-torrent/cdn/hashes.txt
# Build torrents locally
cd monero-torrent
docker compose --profile local up --build
# Paths to locally-built torrents
local_cli_torrent="watch/monero-${version_cli}.torrent"
local_gui_torrent="watch/monero-gui-${version_gui}.torrent"
if [ ! -f "$local_cli_torrent" ] || [ ! -f "$local_gui_torrent" ]; then
echo "Local torrent files were not created as expected" >&2
ls -R
exit 1
fi
# Extract magnet links from locally built torrents
MAGNET_LINK_CLI="$(transmission-show -m "$local_cli_torrent")"
MAGNET_LINK_GUI="$(transmission-show -m "$local_gui_torrent")"
# Download torrents from CDN (replace with the monero URLs)
CDN_CLI_URL="https://github.com/plowsof/monero-torrent/releases/download/${version_cli}/monero-${version_cli}.torrent"
CDN_GUI_URL="https://github.com/plowsof/monero-torrent/releases/download/${version_cli}/monero-gui-${version_gui}.torrent"
if ! wget --spider -q "$CDN_CLI_URL" || ! wget --spider -q "$CDN_GUI_URL"; then
echo "::warning title=CDN-Torrents::CDN torrent files not available yet, unable to verify"
exit 1
fi
wget -q "$CDN_CLI_URL" -O "monero-${version_cli}.torrent"
wget -q "$CDN_GUI_URL" -O "monero-gui-${version_gui}.torrent"
CDN_MAGNET_LINK_CLI="$(transmission-show -m "monero-${version_cli}.torrent")"
CDN_MAGNET_LINK_GUI="$(transmission-show -m "monero-gui-${version_gui}.torrent")"
# Compare local vs CDN magnets
if [ "$MAGNET_LINK_CLI" != "$CDN_MAGNET_LINK_CLI" ]; then
echo "CLI torrent magnet mismatch between local build and CDN version" >&2
echo "Local: $MAGNET_LINK_CLI" >&2
echo "CDN : $CDN_MAGNET_LINK_CLI" >&2
exit 1
fi
if [ "$MAGNET_LINK_GUI" != "$CDN_MAGNET_LINK_GUI" ]; then
echo "GUI torrent magnet mismatch between local build and CDN version" >&2
echo "Local: $MAGNET_LINK_GUI" >&2
echo "CDN : $CDN_MAGNET_LINK_GUI" >&2
exit 1
fi
echo "Torrent files and magnet links match local build and CDN."
echo "magnet_gui=$MAGNET_LINK_GUI" >> $GITHUB_OUTPUT
echo "magnet_cli=$MAGNET_LINK_CLI" >> $GITHUB_OUTPUT
# Check webseed URLs
CDN_URL="https://downloads.getmonero.org"
if ! wget --spider -q "$CDN_URL/hashes-$version_cli.txt"; then
echo "::warning title=Webseed-URLs::Webseed URL unreachable: $CDN_URL/hashes-$version_cli.txt"
fi
if ! wget --spider -q "$CDN_URL/hashes-$version_gui.txt"; then
echo "::warning title=Webseed-URLs::Webseed URL unreachable: $CDN_URL/hashes-$version_gui.txt"
fi
for file in $(awk '/monero-/ {print $2}' "$HASH_FILE"); do
dir="cli"
part="monero-$version_cli"
if [[ $file =~ gui ]]; then
dir="gui"
part="monero-gui-$version_gui"
fi
url="$CDN_URL/${part}/${dir}/${file}"
if ! wget --spider -q "$url"; then
echo "::warning title=Webseed-URLs::Webseed URL unreachable: $url"
fi
done
update-repo:
needs: validate-download-hashes
name: "Update static data with new download info"
runs-on: ubuntu-22.04
if: github.event_name == 'push' && github.ref == 'refs/heads/beta'
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
- uses: pnpm/action-setup@v5
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y jq gh
- name: Update magnet links
run: |
set -euo pipefail
MAGNET_LINK_GUI="${{ needs.validate-download-hashes.outputs.magnet_gui }}"
MAGNET_LINK_CLI="${{ needs.validate-download-hashes.outputs.magnet_cli }}"
# Update magnet links in core.json only if different
CURRENT_GUI_MAGNET=$(jq -r '.gui.torrent.magnet' src/data/downloads/core.json)
if [ "$CURRENT_GUI_MAGNET" != "$MAGNET_LINK_GUI" ]; then
jq --arg m "$MAGNET_LINK_GUI" '.gui.torrent.magnet = $m' src/data/downloads/core.json > tmp.json && mv tmp.json src/data/downloads/core.json
fi
CURRENT_CLI_MAGNET=$(jq -r '.cli.torrent.magnet' src/data/downloads/core.json)
if [ "$CURRENT_CLI_MAGNET" != "$MAGNET_LINK_CLI" ]; then
jq --arg m "$MAGNET_LINK_CLI" '.cli.torrent.magnet = $m' src/data/downloads/core.json > tmp.json && mv tmp.json src/data/downloads/core.json
fi
- name: Update Core download metadata
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
version_gui="${{ needs.validate-download-hashes.outputs.version_gui }}"
version_cli="${{ needs.validate-download-hashes.outputs.version_cli }}"
current_gui_version=$(jq -r '.gui.version' src/data/downloads/core.json)
current_cli_version=$(jq -r '.cli.version' src/data/downloads/core.json)
if [ "$current_gui_version" != "$version_gui" ] || [ "$current_cli_version" != "$version_cli" ]; then
echo "Versions differ, updating core downloads"
# Get release names from GitHub
first_release_cli=$(echo "${version_cli}" | sed 's/\.[0-9]\+\.[0-9]\+$/.0.0/')
first_release_gui=$(echo "${version_gui}" | sed 's/\.[0-9]\+\.[0-9]\+$/.0.0/')
cli_name="$(gh release view "${first_release_cli}" --repo monero-project/monero --json name --jq '.name | sub("[^A-Za-z ]+.*$"; "") | sub(" +$"; "")' || echo '')"
gui_name="$(gh release view "${first_release_gui}" --repo monero-project/monero-gui --json name --jq '.name | sub("[^A-Za-z ]+.*$"; "") | sub(" +$"; "")' || echo '')"
# Update version numbers and names
jq --arg v "$version_cli" --arg n "$cli_name" '.cli.version = $v | .cli.name = $n' src/data/downloads/core.json > tmp.json && mv tmp.json src/data/downloads/core.json
jq --arg v "$version_gui" --arg n "$gui_name" '.gui.version = $v | .gui.name = $n' src/data/downloads/core.json > tmp.json && mv tmp.json src/data/downloads/core.json
# Update file sizes
for section in cli gui; do
jq -r ".$section.downloads[] | objects | select(.href) | .href" src/data/downloads/core.json | while read -r url; do
if [ -n "$url" ]; then
echo "Fetching size for $url"
size=$(curl -sIL "$url" | grep -i content-length | awk '{print $2}' | tr -d '\r\n')
if [ -n "$size" ] && [ "$size" -gt 0 ]; then
mb=$(awk -v s="$size" 'BEGIN { printf "%.2f", s/1048576 }')
jq --arg u "$url" --arg s "$mb MB" "(.${section}.downloads[] | objects | select(.href == \$u) | .size) = \$s" src/data/downloads/core.json > tmp.json && mv tmp.json src/data/downloads/core.json
fi
sleep 0.5
fi
done
done
else
echo "Versions match, skipping"
fi
- name: Format metadata file
run: |
set -euo pipefail
pnpm install --frozen-lockfile
pnpm exec prettier --write src/data/downloads/core.json
- name: Create pull request
env:
GH_TOKEN: ${{ github.token }}
BASE_BRANCH: beta
run: |
set -euo pipefail
# If core.json hasn't changed, bail out early
if git diff --quiet src/data/downloads/core.json; then
echo "No changes to commit"
exit 0
fi
PREFIX="actions/core-downloads-"
version_gui="${{ needs.validate-download-hashes.outputs.version_gui }}"
version_cli="${{ needs.validate-download-hashes.outputs.version_cli }}"
BRANCH_NAME="${PREFIX}CLI_${version_cli}-GUI_${version_gui}"
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
git checkout -b "$BRANCH_NAME"
git add src/data/downloads/core.json
git commit -m "Update Core download information (CLI ${version_cli}, GUI ${version_gui})"
git push --set-upstream origin "$BRANCH_NAME"
gh pr create \
--base "$BASE_BRANCH" \
--head "$BRANCH_NAME" \
--title "downloads: update core.json download information (CLI ${version_cli}, GUI ${version_gui})" \
--body "This PR was automatically generated by the Download Hashes workflow to update **src/data/downloads/core.json** based on the latest downloads/hashes.txt."