3 Commits

Author SHA1 Message Date
Hermes Agent 6959684d63 fix: trim busybox to archive-focused config
busybox static musl build / build-and-release (push) Successful in 38s
2026-05-08 00:18:12 +00:00
Hermes Agent 9d69535ad9 fix: add linux headers for busybox build
busybox static musl build / build-and-release (push) Failing after 21s
iperf3 static musl build / build-and-release (push) Successful in 20s
2026-05-08 00:10:10 +00:00
Hermes Agent 92f2c636be feat: add more static musl tools
busybox static musl build / build-and-release (push) Failing after 31s
doggo static musl build / build-and-release (push) Successful in 19s
gdu static musl build / build-and-release (push) Successful in 37s
iperf3 static musl build / build-and-release (push) Successful in 20s
speedtest-go static musl build / build-and-release (push) Successful in 32s
2026-05-08 00:04:34 +00:00
14 changed files with 500 additions and 46 deletions
+68
View File
@@ -0,0 +1,68 @@
name: busybox static musl build
on:
push:
branches:
- main
paths:
- .gitea/workflows/busybox.yml
- scripts/build_busybox.sh
- scripts/publish_release.py
- versions/busybox.version
workflow_dispatch:
permissions:
contents: write
jobs:
build-and-release:
runs-on: alpine-latest
steps:
- name: Bootstrap workspace
shell: sh
env:
REPO_URL: ${{ gitea.server_url }}
REPO_NAME: ${{ gitea.repository }}
GIT_SHA: ${{ gitea.sha }}
run: |
apk add --no-cache git
workdir="$(pwd)"
tmpdir="$(mktemp -d)"
git clone --depth 1 "$REPO_URL/$REPO_NAME.git" "$tmpdir/repo"
git -C "$tmpdir/repo" fetch --depth 1 origin "$GIT_SHA"
git -C "$tmpdir/repo" checkout "$GIT_SHA"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
cp -a "$tmpdir/repo"/. "$workdir"/
- name: Read version
id: meta
shell: sh
working-directory: ${{ gitea.workspace }}
run: |
version="$(tr -d ' \n\r' < versions/busybox.version)"
artifact="busybox-${version}-linux-amd64-musl-static"
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "artifact=${artifact}" >> "$GITHUB_OUTPUT"
- name: Build static busybox
shell: sh
working-directory: ${{ gitea.workspace }}
run: |
chmod +x scripts/build_busybox.sh
./scripts/build_busybox.sh "${{ steps.meta.outputs.version }}" "$PWD/dist"
- name: Publish release assets
shell: sh
env:
GITEA_TOKEN: ${{ gitea.token }}
GITEA_API_URL: ${{ gitea.api_url }}
REPO_OWNER: ${{ gitea.repository_owner }}
REPO_NAME: static-musl-builds
TOOL: busybox
VERSION: ${{ steps.meta.outputs.version }}
TARGET_SHA: ${{ gitea.sha }}
working-directory: ${{ gitea.workspace }}
run: |
apk add --no-cache python3
python3 scripts/publish_release.py "dist/${{ steps.meta.outputs.artifact }}" "dist/${{ steps.meta.outputs.artifact }}.tar.gz" "dist/${{ steps.meta.outputs.artifact }}.sha256"
+68
View File
@@ -0,0 +1,68 @@
name: doggo static musl build
on:
push:
branches:
- main
paths:
- .gitea/workflows/doggo.yml
- scripts/build_doggo.sh
- scripts/publish_release.py
- versions/doggo.version
workflow_dispatch:
permissions:
contents: write
jobs:
build-and-release:
runs-on: alpine-latest
steps:
- name: Bootstrap workspace
shell: sh
env:
REPO_URL: ${{ gitea.server_url }}
REPO_NAME: ${{ gitea.repository }}
GIT_SHA: ${{ gitea.sha }}
run: |
apk add --no-cache git
workdir="$(pwd)"
tmpdir="$(mktemp -d)"
git clone --depth 1 "$REPO_URL/$REPO_NAME.git" "$tmpdir/repo"
git -C "$tmpdir/repo" fetch --depth 1 origin "$GIT_SHA"
git -C "$tmpdir/repo" checkout "$GIT_SHA"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
cp -a "$tmpdir/repo"/. "$workdir"/
- name: Read version
id: meta
shell: sh
working-directory: ${{ gitea.workspace }}
run: |
version="$(tr -d ' \n\r' < versions/doggo.version)"
artifact="doggo-${version}-linux-amd64-musl-static"
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "artifact=${artifact}" >> "$GITHUB_OUTPUT"
- name: Build static doggo
shell: sh
working-directory: ${{ gitea.workspace }}
run: |
chmod +x scripts/build_doggo.sh
./scripts/build_doggo.sh "${{ steps.meta.outputs.version }}" "$PWD/dist"
- name: Publish release assets
shell: sh
env:
GITEA_TOKEN: ${{ gitea.token }}
GITEA_API_URL: ${{ gitea.api_url }}
REPO_OWNER: ${{ gitea.repository_owner }}
REPO_NAME: static-musl-builds
TOOL: doggo
VERSION: ${{ steps.meta.outputs.version }}
TARGET_SHA: ${{ gitea.sha }}
working-directory: ${{ gitea.workspace }}
run: |
apk add --no-cache python3
python3 scripts/publish_release.py "dist/${{ steps.meta.outputs.artifact }}" "dist/${{ steps.meta.outputs.artifact }}.tar.gz" "dist/${{ steps.meta.outputs.artifact }}.sha256"
+1
View File
@@ -7,6 +7,7 @@ on:
paths:
- .gitea/workflows/gdu.yml
- scripts/build_gdu.sh
- scripts/publish_release.py
- versions/gdu.version
workflow_dispatch:
+2 -1
View File
@@ -6,7 +6,8 @@ on:
- main
paths:
- .gitea/workflows/iperf3.yml
- scripts/**
- scripts/build_iperf3.sh
- scripts/publish_release.py
- versions/iperf3.version
- README.md
workflow_dispatch:
+68
View File
@@ -0,0 +1,68 @@
name: speedtest-go static musl build
on:
push:
branches:
- main
paths:
- .gitea/workflows/speedtest-go.yml
- scripts/build_speedtest-go.sh
- scripts/publish_release.py
- versions/speedtest-go.version
workflow_dispatch:
permissions:
contents: write
jobs:
build-and-release:
runs-on: alpine-latest
steps:
- name: Bootstrap workspace
shell: sh
env:
REPO_URL: ${{ gitea.server_url }}
REPO_NAME: ${{ gitea.repository }}
GIT_SHA: ${{ gitea.sha }}
run: |
apk add --no-cache git
workdir="$(pwd)"
tmpdir="$(mktemp -d)"
git clone --depth 1 "$REPO_URL/$REPO_NAME.git" "$tmpdir/repo"
git -C "$tmpdir/repo" fetch --depth 1 origin "$GIT_SHA"
git -C "$tmpdir/repo" checkout "$GIT_SHA"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
cp -a "$tmpdir/repo"/. "$workdir"/
- name: Read version
id: meta
shell: sh
working-directory: ${{ gitea.workspace }}
run: |
version="$(tr -d ' \n\r' < versions/speedtest-go.version)"
artifact="speedtest-go-${version}-linux-amd64-musl-static"
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "artifact=${artifact}" >> "$GITHUB_OUTPUT"
- name: Build static speedtest-go
shell: sh
working-directory: ${{ gitea.workspace }}
run: |
chmod +x scripts/build_speedtest-go.sh
./scripts/build_speedtest-go.sh "${{ steps.meta.outputs.version }}" "$PWD/dist"
- name: Publish release assets
shell: sh
env:
GITEA_TOKEN: ${{ gitea.token }}
GITEA_API_URL: ${{ gitea.api_url }}
REPO_OWNER: ${{ gitea.repository_owner }}
REPO_NAME: static-musl-builds
TOOL: speedtest-go
VERSION: ${{ steps.meta.outputs.version }}
TARGET_SHA: ${{ gitea.sha }}
working-directory: ${{ gitea.workspace }}
run: |
apk add --no-cache python3
python3 scripts/publish_release.py "dist/${{ steps.meta.outputs.artifact }}" "dist/${{ steps.meta.outputs.artifact }}.tar.gz" "dist/${{ steps.meta.outputs.artifact }}.sha256"
+11 -45
View File
@@ -2,50 +2,16 @@
Automated static `musl` builds for portable Linux binaries.
Current target:
- `iperf3` (x86_64 / amd64)
## Tools
- `iperf3`
- `gdu`
- `speedtest-go`
- `doggo`
- `busybox`
## What this repo does
Each release publishes:
- the raw binary
- a `.tar.gz`
- a `.sha256`
- Tracks a pinned upstream version in `versions/`
- Builds a fully static `musl` binary with Gitea Actions
- Publishes versioned release assets to Gitea Releases
- Is designed to grow with more binaries later
## Current output
For each `iperf3` release build, the workflow publishes:
- `iperf3-<version>-linux-amd64-musl-static`
- `iperf3-<version>-linux-amd64-musl-static.tar.gz`
- `iperf3-<version>-linux-amd64-musl-static.sha256`
## Repo layout
- `versions/iperf3.version` — tracked upstream version
- `scripts/build_iperf3.sh` — static build script
- `scripts/sync_iperf3_version.py` — checks upstream and updates pinned version
- `scripts/publish_release.py` — idempotent release asset publisher for Gitea
- `.gitea/workflows/iperf3.yml` — CI pipeline
## How updates happen
1. `scripts/sync_iperf3_version.py` checks the latest upstream iperf3 release.
2. If a newer version exists, it updates `versions/iperf3.version`.
3. A commit is pushed.
4. Gitea Actions builds the static binary and uploads release assets.
## Manual local update example
```bash
python3 scripts/sync_iperf3_version.py --update-file
if ! git diff --quiet -- versions/iperf3.version; then
git add versions/iperf3.version
git commit -m "chore(iperf3): bump to $(cat versions/iperf3.version)"
git push
fi
```
## Notes
- The current workflow targets `amd64` first because that is the most broadly useful Linux target.
- The structure is intentionally generic so more binaries can be added later without reworking the repo.
Versions are pinned in `versions/*.version` and refreshed by cron. Matching Gitea Actions workflows build and publish updated releases automatically.
+84
View File
@@ -0,0 +1,84 @@
#!/bin/sh
set -eu
VERSION="${1:?usage: build_busybox.sh <version> [output_dir]}"
OUTPUT_DIR="${2:-$PWD/dist}"
PREFIX_NAME="busybox-${VERSION}-linux-amd64-musl-static"
mkdir -p "$OUTPUT_DIR"
apk add --no-cache \
build-base \
bzip2 \
curl \
file \
linux-headers \
tar \
xz
SRCDIR="${TMPDIR:-/tmp}/busybox-src-${VERSION}"
rm -rf "$SRCDIR"
mkdir -p "$SRCDIR"
curl -fsSL "https://busybox.net/downloads/busybox-${VERSION}.tar.bz2" \
| tar -xj -C "$SRCDIR" --strip-components=1
cd "$SRCDIR"
make allnoconfig >/dev/null
set_kconfig() {
key="$1"
value="$2"
if grep -q "^${key}=" .config; then
sed -i "s|^${key}=.*|${key}=${value}|" .config
elif grep -q "^# ${key} is not set$" .config; then
sed -i "s|^# ${key} is not set$|${key}=${value}|" .config
else
echo "${key}=${value}" >> .config
fi
}
set_kconfig CONFIG_STATIC y
set_kconfig CONFIG_LONG_OPTS y
set_kconfig CONFIG_FEATURE_VERBOSE_USAGE y
set_kconfig CONFIG_FEATURE_COMPRESS_USAGE y
set_kconfig CONFIG_FEATURE_SEAMLESS_GZ y
set_kconfig CONFIG_FEATURE_SEAMLESS_BZ2 y
set_kconfig CONFIG_FEATURE_SEAMLESS_XZ y
set_kconfig CONFIG_GUNZIP y
set_kconfig CONFIG_ZCAT y
set_kconfig CONFIG_BUNZIP2 y
set_kconfig CONFIG_BZCAT y
set_kconfig CONFIG_UNLZMA y
set_kconfig CONFIG_LZCAT y
set_kconfig CONFIG_UNXZ y
set_kconfig CONFIG_XZ y
set_kconfig CONFIG_XZCAT y
set_kconfig CONFIG_GZIP y
set_kconfig CONFIG_TAR y
set_kconfig CONFIG_FEATURE_TAR_CREATE y
set_kconfig CONFIG_FEATURE_TAR_FROM y
set_kconfig CONFIG_FEATURE_TAR_GNU_EXTENSIONS y
set_kconfig CONFIG_FEATURE_TAR_AUTODETECT y
set_kconfig CONFIG_UNZIP y
yes '' | make oldconfig >/dev/null
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)" busybox
cp busybox "$OUTPUT_DIR/$PREFIX_NAME"
strip "$OUTPUT_DIR/$PREFIX_NAME" 2>/dev/null || true
(
cd "$OUTPUT_DIR"
tar -czf "${PREFIX_NAME}.tar.gz" "$PREFIX_NAME"
sha256sum "$PREFIX_NAME" "${PREFIX_NAME}.tar.gz" > "${PREFIX_NAME}.sha256"
)
file "$OUTPUT_DIR/$PREFIX_NAME"
ldd "$OUTPUT_DIR/$PREFIX_NAME" || true
if file "$OUTPUT_DIR/$PREFIX_NAME" | grep -qi 'dynamically linked'; then
echo "error: output binary is still dynamically linked" >&2
exit 1
fi
echo "build OK: $OUTPUT_DIR/$PREFIX_NAME"
+38
View File
@@ -0,0 +1,38 @@
#!/bin/sh
set -eu
VERSION="${1:?usage: build_doggo.sh <version> [output_dir]}"
OUTPUT_DIR="${2:-$PWD/dist}"
VERSION="${VERSION#v}"
PREFIX_NAME="doggo-${VERSION}-linux-amd64-musl-static"
mkdir -p "$OUTPUT_DIR"
apk add --no-cache curl file go git tar xz
SRCDIR="${TMPDIR:-/tmp}/doggo-src-${VERSION}"
rm -rf "$SRCDIR"
mkdir -p "$SRCDIR"
curl -fsSL "https://github.com/mr-karan/doggo/archive/refs/tags/v${VERSION}.tar.gz" | tar -xz -C "$SRCDIR" --strip-components=1
cd "$SRCDIR"
CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -trimpath -ldflags="-s -w -extldflags=-static" -o "$OUTPUT_DIR/$PREFIX_NAME" ./cmd/doggo
strip "$OUTPUT_DIR/$PREFIX_NAME" 2>/dev/null || true
(
cd "$OUTPUT_DIR"
tar -czf "${PREFIX_NAME}.tar.gz" "$PREFIX_NAME"
sha256sum "$PREFIX_NAME" "${PREFIX_NAME}.tar.gz" > "${PREFIX_NAME}.sha256"
)
file "$OUTPUT_DIR/$PREFIX_NAME"
ldd "$OUTPUT_DIR/$PREFIX_NAME" || true
if file "$OUTPUT_DIR/$PREFIX_NAME" | grep -qi 'dynamically linked'; then
echo "error: output binary is still dynamically linked" >&2
exit 1
fi
echo "build OK: $OUTPUT_DIR/$PREFIX_NAME"
+38
View File
@@ -0,0 +1,38 @@
#!/bin/sh
set -eu
VERSION="${1:?usage: build_speedtest-go.sh <version> [output_dir]}"
OUTPUT_DIR="${2:-$PWD/dist}"
VERSION="${VERSION#v}"
PREFIX_NAME="speedtest-go-${VERSION}-linux-amd64-musl-static"
mkdir -p "$OUTPUT_DIR"
apk add --no-cache curl file go git tar xz
SRCDIR="${TMPDIR:-/tmp}/speedtest-go-src-${VERSION}"
rm -rf "$SRCDIR"
mkdir -p "$SRCDIR"
curl -fsSL "https://github.com/showwin/speedtest-go/archive/refs/tags/v${VERSION}.tar.gz" | tar -xz -C "$SRCDIR" --strip-components=1
cd "$SRCDIR"
CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -trimpath -ldflags="-s -w -extldflags=-static" -o "$OUTPUT_DIR/$PREFIX_NAME" .
strip "$OUTPUT_DIR/$PREFIX_NAME" 2>/dev/null || true
(
cd "$OUTPUT_DIR"
tar -czf "${PREFIX_NAME}.tar.gz" "$PREFIX_NAME"
sha256sum "$PREFIX_NAME" "${PREFIX_NAME}.tar.gz" > "${PREFIX_NAME}.sha256"
)
file "$OUTPUT_DIR/$PREFIX_NAME"
ldd "$OUTPUT_DIR/$PREFIX_NAME" || true
if file "$OUTPUT_DIR/$PREFIX_NAME" | grep -qi 'dynamically linked'; then
echo "error: output binary is still dynamically linked" >&2
exit 1
fi
echo "build OK: $OUTPUT_DIR/$PREFIX_NAME"
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
import argparse
import re
import sys
import urllib.request
from pathlib import Path
DOWNLOADS_URL = "https://busybox.net/downloads/"
VERSION_RE = re.compile(r"busybox-([0-9]+(?:\.[0-9]+)+)\.tar\.bz2")
def version_key(version: str):
return tuple(int(part) for part in version.split("."))
def fetch_latest_version() -> str:
req = urllib.request.Request(
DOWNLOADS_URL,
headers={"User-Agent": "static-musl-builds-updater"},
)
with urllib.request.urlopen(req, timeout=30) as resp:
html = resp.read().decode("utf-8", "replace")
versions = sorted(set(VERSION_RE.findall(html)), key=version_key)
if not versions:
raise RuntimeError("No BusyBox versions found on downloads page")
return versions[-1]
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--version-file", required=True, type=Path)
parser.add_argument("--update-file", action="store_true")
args = parser.parse_args()
latest = fetch_latest_version()
current = args.version_file.read_text(encoding="utf-8").strip() if args.version_file.exists() else ""
print(f"current={current or '<missing>'}")
print(f"latest={latest}")
if current == latest:
print("status=up-to-date")
return 0
print("status=update-available")
if args.update_file:
args.version_file.parent.mkdir(parents=True, exist_ok=True)
args.version_file.write_text(latest + "\n", encoding="utf-8")
print(f"updated_file={args.version_file}")
return 0
if __name__ == "__main__":
sys.exit(main())
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
import argparse
import json
import re
import sys
import urllib.request
from pathlib import Path
VERSION_RE = re.compile(r"[0-9]+(?:\.[0-9]+)*")
def fetch_latest_tag(repo: str) -> str:
url = f"https://api.github.com/repos/{repo}/releases/latest"
req = urllib.request.Request(
url,
headers={
"Accept": "application/vnd.github+json",
"User-Agent": "static-musl-builds-updater",
},
)
with urllib.request.urlopen(req, timeout=30) as resp:
data = json.load(resp)
return data["tag_name"].strip()
def normalize_version(tag: str, strip_prefix: str) -> str:
version = tag
if strip_prefix and version.startswith(strip_prefix):
version = version[len(strip_prefix):]
version = version.strip()
if not VERSION_RE.fullmatch(version):
raise ValueError(f"Unexpected version format: {tag!r} -> {version!r}")
return version
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--repo", required=True, help="owner/repo on GitHub")
parser.add_argument("--version-file", required=True, type=Path)
parser.add_argument("--strip-prefix", default="v")
parser.add_argument("--update-file", action="store_true")
args = parser.parse_args()
latest_tag = fetch_latest_tag(args.repo)
latest = normalize_version(latest_tag, args.strip_prefix)
current = args.version_file.read_text(encoding="utf-8").strip() if args.version_file.exists() else ""
print(f"repo={args.repo}")
print(f"current={current or '<missing>'}")
print(f"latest={latest}")
if current == latest:
print("status=up-to-date")
return 0
print("status=update-available")
if args.update_file:
args.version_file.parent.mkdir(parents=True, exist_ok=True)
args.version_file.write_text(latest + "\n", encoding="utf-8")
print(f"updated_file={args.version_file}")
return 0
if __name__ == "__main__":
sys.exit(main())
+1
View File
@@ -0,0 +1 @@
1.37.0
+1
View File
@@ -0,0 +1 @@
1.1.5
+1
View File
@@ -0,0 +1 @@
1.7.10