nandi/nimstaticpublic Fork 0
799d4ce
Commits
Clone
git clone https://git.rickub.com/nandi/nimstatic.git
git clone ssh://git@rickub.com/nandi/nimstatic.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

ci: check staticness by reading the ELF, not by ldd

The runner image has no ldd, and `ldd X | grep -q` fails silently when it is
missing — the pipe eats the error and grep simply finds nothing, so a good
build died with no message. Reading the program headers for PT_INTERP needs
nothing but python3, and is what 'static' actually means.

The upload step also falls back to the id-based asset path when the API sends
no upload_url.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-09-19T18:06:33-07:00 Browse files
799d4ce parent: a977b7e
modified .rickub/workflows/release.yml +12 -3
@@ -105,9 +105,11 @@ jobs:
105105 VERSION="${TAG#v}"
106106 NAME="nimstatic-${VERSION}-x86_64-linux"
107107 ./nimstatic-host src/nimstatic.nim -o "$NAME" -- -d:ssl --passL:-s
108- # `file` is not on the runner image; ldd answers the only question
109- # that matters, and running it proves the thing actually starts.
110- ldd "$NAME" 2>&1 | grep -q "not a dynamic executable"
108+ # Neither file(1) nor ldd is on the runner image, and `ldd … | grep`
109+ # fails silently when ldd is missing — the pipe eats the error and
110+ # grep just finds nothing. Read the ELF program headers instead: a
111+ # PT_INTERP entry (type 3) is what makes a binary dynamic.
112+ python3 -c "import struct,sys;d=open(sys.argv[1],'rb').read();assert d[:4]==b'\x7fELF';off=struct.unpack_from('<Q',d,0x20)[0];esz=struct.unpack_from('<H',d,0x36)[0];n=struct.unpack_from('<H',d,0x38)[0];t=[struct.unpack_from('<I',d,off+i*esz)[0] for i in range(n)];sys.exit('dynamic: has PT_INTERP' if 3 in t else 0)" "$NAME"
111113 ./"$NAME" --help > /dev/null
112114 xz -9e -k "$NAME"
113115 sha256sum "$NAME" "$NAME.xz" > SHA256SUMS
@@ -134,7 +136,14 @@ jobs:
134136 -d "{\"tag_name\":\"$TAG\",\"name\":\"nimstatic $TAG\"}" > release.json
135137 fi
136138
139+ # Prefer the upload_url the API advertises; fall back to the
140+ # id-based path when this forge does not send one.
137141 upload_url=$(field upload_url < release.json | cut -d'{' -f1)
142+ if [ -z "$upload_url" ]; then
143+ release_id=$(field id < release.json)
144+ [ -n "$release_id" ] || { echo "no release id in:"; cat release.json; exit 1; }
145+ upload_url="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/$release_id/assets"
146+ fi
138147 echo "uploading to $upload_url"
139148
140149 for asset in "$NAME" "$NAME.xz" SHA256SUMS; do
@@ -105,9 +105,11 @@ jobs:
105 VERSION="${TAG#v}"105 VERSION="${TAG#v}"
106 NAME="nimstatic-${VERSION}-x86_64-linux"106 NAME="nimstatic-${VERSION}-x86_64-linux"
107 ./nimstatic-host src/nimstatic.nim -o "$NAME" -- -d:ssl --passL:-s107 ./nimstatic-host src/nimstatic.nim -o "$NAME" -- -d:ssl --passL:-s
108- # `file` is not on the runner image; ldd answers the only question108+ # Neither file(1) nor ldd is on the runner image, and `ldd … | grep`
109- # that matters, and running it proves the thing actually starts.109+ # fails silently when ldd is missing — the pipe eats the error and
110- ldd "$NAME" 2>&1 | grep -q "not a dynamic executable"110+ # grep just finds nothing. Read the ELF program headers instead: a
111+ # PT_INTERP entry (type 3) is what makes a binary dynamic.
112+ python3 -c "import struct,sys;d=open(sys.argv[1],'rb').read();assert d[:4]==b'\x7fELF';off=struct.unpack_from('<Q',d,0x20)[0];esz=struct.unpack_from('<H',d,0x36)[0];n=struct.unpack_from('<H',d,0x38)[0];t=[struct.unpack_from('<I',d,off+i*esz)[0] for i in range(n)];sys.exit('dynamic: has PT_INTERP' if 3 in t else 0)" "$NAME"
111 ./"$NAME" --help > /dev/null113 ./"$NAME" --help > /dev/null
112 xz -9e -k "$NAME"114 xz -9e -k "$NAME"
113 sha256sum "$NAME" "$NAME.xz" > SHA256SUMS115 sha256sum "$NAME" "$NAME.xz" > SHA256SUMS
@@ -134,7 +136,14 @@ jobs:
134 -d "{\"tag_name\":\"$TAG\",\"name\":\"nimstatic $TAG\"}" > release.json136 -d "{\"tag_name\":\"$TAG\",\"name\":\"nimstatic $TAG\"}" > release.json
135 fi137 fi
136 138
139+ # Prefer the upload_url the API advertises; fall back to the
140+ # id-based path when this forge does not send one.
137 upload_url=$(field upload_url < release.json | cut -d'{' -f1)141 upload_url=$(field upload_url < release.json | cut -d'{' -f1)
142+ if [ -z "$upload_url" ]; then
143+ release_id=$(field id < release.json)
144+ [ -n "$release_id" ] || { echo "no release id in:"; cat release.json; exit 1; }
145+ upload_url="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/$release_id/assets"
146+ fi
138 echo "uploading to $upload_url"147 echo "uploading to $upload_url"
139 148
140 for asset in "$NAME" "$NAME.xz" SHA256SUMS; do149 for asset in "$NAME" "$NAME.xz" SHA256SUMS; do
modified nimstatic.nimble +1 -1
@@ -1,4 +1,4 @@
1-version = "0.1.4"
1+version = "0.1.5"
22 author = "nandi"
33 description = "Fully static Nim binaries, dependencies and all — detects what you link and fetches musl archives from Alpine"
44 license = "MIT"
@@ -1,4 +1,4 @@
1-version = "0.1.4"1+version = "0.1.5"
2 author = "nandi"2 author = "nandi"
3 description = "Fully static Nim binaries, dependencies and all — detects what you link and fetches musl archives from Alpine"3 description = "Fully static Nim binaries, dependencies and all — detects what you link and fetches musl archives from Alpine"
4 license = "MIT"4 license = "MIT"