Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/scripts/apt-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
#
# Install apt packages in CI without refreshing the whole package index.
#
# `apt-get update` on a GitHub runner refreshes SIX repositories — the Ubuntu
# archive plus Microsoft, azure-cli, Google and Chrome — none of which carry
# anything simplepool builds against, and it makes the job depend on all of
# them being reachable. On 2026-08-18 the Azure mirror `Ign`'d every entry,
# apt fell back to archive.ubuntu.com, and `build-test` sat on
# `Get:5 .../noble-security InRelease` for 29 minutes until the run was
# cancelled. It never reached package download at all.
#
# The runner image ships current lists for the Ubuntu archive — a plain
# `apt-get update` reports `Hit:` on the base suite — so the index already on
# disk is enough to install from. Packages that are already present (most of
# these, on a GitHub image) cost nothing.
#
# The one case the shipped index cannot serve is a package superseded by a
# security update whose old .deb has left the pool, which 404s. That is the
# only reason the fallback below exists, and every network wait in it is
# bounded so it cannot repeat the stall it was written to prevent.
#
# Usage: .github/scripts/apt-install.sh <pkg>...
#
set -euo pipefail

[ $# -gt 0 ] || { echo "apt-install.sh: no packages given" >&2; exit 2; }

export DEBIAN_FRONTEND=noninteractive

# Bound every fetch: without a timeout a stalled mirror holds the connection
# open until the job's own limit kills it, which is the failure mode here.
APT_OPTS=(
-o Acquire::Retries=3
-o Acquire::http::Timeout=20
-o Acquire::https::Timeout=20
)

apt_install() {
sudo -E apt-get install -y --no-install-recommends "${APT_OPTS[@]}" "$@"
}

echo "==> installing without an index refresh: $*"
if apt_install "$@"; then
exit 0
fi

echo "::warning::apt-get install failed against the image's package index." \
"Refreshing it once, then retrying. If this becomes routine, the runner" \
"image's lists have drifted and this script's assumption needs revisiting."

# `sudo timeout` rather than `timeout sudo`, so the kill lands on apt-get
# itself instead of on sudo, which may or may not forward the signal.
sudo -E timeout 180 apt-get update "${APT_OPTS[@]}" \
|| echo "::warning::index refresh did not finish in 180s; retrying the install regardless"

apt_install "$@"
19 changes: 12 additions & 7 deletions .github/workflows/check_build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ on:
jobs:
build-test:
runs-on: ubuntu-latest
# This job had no limit and stalled for 29 minutes inside `apt-get update`
# before a human cancelled it. A build-and-unit-test of a 13-file C project
# has no business taking longer than this.
timeout-minutes: 15
# Match build_docker.yaml: CI runs only in the LayerTwo-Labs org repo, so
# pushing to a personal repo doesn't spend Actions minutes. Remove this line
# if you *do* want build/test CI to run on your own fork too.
Expand All @@ -22,13 +26,14 @@ jobs:
uses: actions/checkout@v4

- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential \
libsqlite3-dev \
libcurl4-openssl-dev \
libhiredis-dev
timeout-minutes: 5
# No `apt-get update` — see .github/scripts/apt-install.sh for why.
run: >
.github/scripts/apt-install.sh
build-essential
libsqlite3-dev
libcurl4-openssl-dev
libhiredis-dev

- name: Build
run: make -j"$(nproc)"
Expand Down
40 changes: 22 additions & 18 deletions .github/workflows/integration_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,18 @@ jobs:
uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential \
libsqlite3-dev \
libcurl4-openssl-dev \
libhiredis-dev \
unzip \
jq \
sqlite3 \
netcat-openbsd
timeout-minutes: 5
# No `apt-get update` — see .github/scripts/apt-install.sh for why.
run: >
.github/scripts/apt-install.sh
build-essential
libsqlite3-dev
libcurl4-openssl-dev
libhiredis-dev
unzip
jq
sqlite3
netcat-openbsd

- name: Build
run: make -j"$(nproc)"
Expand Down Expand Up @@ -106,13 +107,16 @@ jobs:
uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
unzip \
jq \
sqlite3 \
netcat-openbsd
timeout-minutes: 5
# Every one of these is already on the GitHub runner image; the call
# is kept so the job states its own dependencies rather than relying
# on the image silently continuing to carry them.
run: >
.github/scripts/apt-install.sh
unzip
jq
sqlite3
netcat-openbsd

- name: Run payout regtest test
run: bash tests/test_payout_regtest.sh
Expand Down
173 changes: 173 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# Cut a simplepool release: build the binary for each supported architecture,
# wrap it in a tarball, and publish the lot as a GitHub Release.
#
# The tarball is what `scripts/install.sh --from-release` downloads, so this
# workflow is the thing standing behind the one-line install. It is also the
# only place the published artifacts are produced — a maintainer never uploads
# a hand-built binary, because a hand-built binary has no attested link back
# to a commit.
#
# Trigger:
# git tag v0.2.0 && git push origin v0.2.0
#
# The tag must match VERSION in the Makefile. That is checked, not assumed:
# the version is compiled into the binary and reported by `--version`, so a
# mismatch would ship a release whose own binary disagrees with its name.
#
# workflow_dispatch builds the tarballs and uploads them as workflow
# artifacts without creating a Release — a dry run of the whole path.
name: Release

on:
push:
tags:
- "v*"
workflow_dispatch:

permissions:
contents: read

jobs:
build:
# Same guard as the other workflows: releases are cut from the canonical
# org repo, so a personal fork doesn't spend Actions minutes or publish
# artifacts under its own name.
if: github.repository_owner == 'LayerTwo-Labs'
strategy:
fail-fast: false
matrix:
include:
# 22.04 sets the glibc floor at 2.35, so the binary also runs on
# 24.04 and on Debian 12. Building on the newest runner instead
# would silently exclude every older box.
- runner: ubuntu-22.04
arch: amd64
# Free ARM runners are available to public repositories. If this
# label is ever unavailable, delete this entry — fail-fast is off,
# so the amd64 release still goes out.
- runner: ubuntu-22.04-arm
arch: arm64
runs-on: ${{ matrix.runner }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
# release.sh runs `git archive HEAD` and records the commit, so it
# needs real history rather than a detached blob.
fetch-depth: 0

- name: Install build dependencies
timeout-minutes: 5
# No `apt-get update` — see .github/scripts/apt-install.sh for why.
run: >
.github/scripts/apt-install.sh
build-essential
libsqlite3-dev
libcurl4-openssl-dev
libhiredis-dev

- name: Check the tag matches the Makefile VERSION
if: startsWith(github.ref, 'refs/tags/v')
run: |
tag="${GITHUB_REF_NAME#v}"
makefile_version="$(sed -n 's/^VERSION[[:space:]]*:=[[:space:]]*//p' Makefile | head -1)"
if [ "$tag" != "$makefile_version" ]; then
echo "::error::tag $GITHUB_REF_NAME does not match Makefile VERSION=$makefile_version." \
"The version is compiled into the binary, so releasing this would ship" \
"artifacts whose own --version disagrees with the release name." \
"Bump VERSION in the Makefile (in a PR), then re-tag."
exit 1
fi

- name: Build tarball
run: scripts/release.sh --arch ${{ matrix.arch }} --out dist

- name: Smoke-test the tarball
# A tarball that doesn't unpack into a runnable binary is worse than
# no release at all, because the failure lands on an operator running
# a one-liner on a fresh box.
run: |
set -euo pipefail
work="$(mktemp -d)"
tar -xzf dist/*.tar.gz -C "$work"
root="$(find "$work" -maxdepth 1 -mindepth 1 -type d)"
test -f "$root/RELEASE"
test -f "$root/schema.sql"
test -f "$root/scripts/install.sh"
test -x "$root/scripts/simplepoolctl"
test -f "$root/deploy/systemd/simplepool.service"
test -d "$root/dashboard" && test -d "$root/payout"
# Only meaningful on the native runner; an arm64 binary cannot be
# executed on an amd64 host and vice versa.
"$root/build/simplepool" --version
cat "$root/RELEASE"

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: simplepool-${{ matrix.arch }}
path: dist/*
if-no-files-found: error

publish:
needs: build
# `always()` so a single architecture failing to build still lets the
# other one ship, rather than losing the release entirely.
if: always() && startsWith(github.ref, 'refs/tags/v') && github.repository_owner == 'LayerTwo-Labs'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: staging
pattern: simplepool-*
merge-multiple: true

- name: Collect checksums
run: |
set -euo pipefail
cd staging
ls -la
# At least one architecture must have made it through.
ls *.tar.gz >/dev/null
# One SHA256SUMS covering every tarball, so an operator can verify a
# download with a single file regardless of which arch they took.
cat *.tar.gz.sha256 > SHA256SUMS
rm -f *.tar.gz.sha256
cat SHA256SUMS

- name: Create the release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
{
echo "## Install"
echo
echo '```sh'
echo "curl -fsSL https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}/scripts/install.sh | sudo bash"
echo '```'
echo
echo "The installer downloads the tarball below, verifies it against \`SHA256SUMS\`,"
echo "and interviews you for the rest. See [INSTALL.md](https://github.com/${GITHUB_REPOSITORY}/blob/${GITHUB_REF_NAME}/INSTALL.md)."
echo
echo "## Verify a download by hand"
echo
echo '```sh'
echo "sha256sum -c --ignore-missing SHA256SUMS"
echo '```'
echo
echo "Binaries are built on Ubuntu 22.04 (glibc 2.35), so they also run on"
echo "Ubuntu 24.04 and Debian 12. Each tarball carries a \`RELEASE\` file and a"
echo "\`build/simplepool.build.json\` pinning the binary to this commit by sha256."
} > notes.md
gh release create "$GITHUB_REF_NAME" \
--title "simplepool $version" \
--notes-file notes.md \
staging/*
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@
# another file containing bitcoind RPC credentials into a PUBLIC repo's
# working tree, one `git add -A` away from being committed.
/proxy.conf.bak.*
# Release tarballs built by scripts/release.sh. They contain a full copy of
# the tree plus a binary; nothing here belongs in the repo.
/dist/
# macOS Finder droppings. They appear in any directory the user has opened,
# and a `git add -A` sweeps them into a public repo.
.DS_Store
**/.DS_Store
Loading
Loading