Solo-C: a tiny coding agent in a single C file

main.c is the whole agent: a readline REPL that talks to any OpenAI-compatible
/chat/completions endpoint and exposes exactly one tool, bash(command). Commands
run through the user's shell with stdout and stderr merged, a hard timeout, and
a process-group kill; the output and an exit marker go back to the model until
it answers without calling a tool.

make static-deps builds curl and c-ares from source into static-deps/, so
`make static` can link a fully static binary on distros that get in the way: the
distro libcurl.a drags in libpsl, brotli, zstd and nghttp2, whose static objects
are GCC 14 LTO bytecode a newer linker cannot read, and a minimal system often
ships no libcares.a at all.

Two details worth keeping in mind when editing mkstaticdeps.sh. curl 8.22 takes
--enable-ares=PREFIX rather than the older --with-cares, and it only warns
about options it does not recognise, so the wrong spelling turns off async DNS
in silence instead of failing. And link order is load-bearing: libcares.a has
to come after libcurl.a or ares_* stays undefined.
This commit is contained in:
Luxferre
2026-10-03 09:15:02 +03:00
commit 494bee8b77
5 changed files with 1102 additions and 0 deletions
+135
View File
@@ -0,0 +1,135 @@
#!/bin/sh
# Rebuild static-deps/libcurl.a and static-deps/libcares.a.
#
# Why this exists: the distro libcurl.a cannot be linked statically on Alpine
# because it pulls in libpsl, libbrotli, libzstd and libnghttp2, and those
# static archives ship LTO bytecode produced by GCC 14 while the installed
# compiler is GCC 15. The linker cannot read it:
# bytecode stream in file '.../libpsl.a' generated with LTO version 14.0
# instead of the expected 15.1
# so psl_*, BrotliDecoder* and ZSTD_* simply never resolve. Passing
# -fno-lto does not help: the objects are LTO bytecode, not C.
#
# The fix is a libcurl built without those four libraries. What is left
# (ssl, crypto, z, cares, idn2, unistring) is all LTO-free, so it links
# cleanly into a fully static binary. Cost: no HTTP/2, no brotli/zstd
# Content-Encoding and no public-suffix cookie checks, none of which a
# plain JSON API client needs.
#
# c-ares is built here too, for two reasons. On a minimal system there is often
# no libcares.a at all (Void's c-ares-devel ships the shared library only), so
# `make static` would fail on -lcares; and where an archive does exist it can
# carry the same LTO bytecode problem as the distro libcurl.a. Building our own
# pins the version and guarantees the archive is plain objects.
#
# curl 8.22 takes c-ares as --enable-ares[=PREFIX], not the older
# --with-cares. It reads PREFIX/lib/pkgconfig/libcares.pc, which `make install`
# of c-ares writes, so the prefix built below is enough. Both spellings are
# wrong in one way or another: --with-cares no longer exists, and unknown
# configure options only produce a warning, so a typo here silently disables
# async DNS and you would not notice until the link failed. Same for
# --without-librtmp, which curl 8.22 no longer knows either; it is gone below.
#
# Overridable: CURL_VERSION, CARES_VERSION, OUT (or $1), FORCE=1.
set -e
CURL_VERSION=${CURL_VERSION:-8.22.0}
CARES_VERSION=${CARES_VERSION:-1.34.8}
OUT=${1:-$(pwd)/static-deps}
FORCE=${FORCE:-0}
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT INT TERM
JOBS=$(nproc 2>/dev/null || echo 2)
CA=""
for c in /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-bundle.crt \
/etc/pki/tls/certs/ca-bundle.crt; do
[ -f "$c" ] && { CA=$c; break; }
done
# fetch DEST URL...: first URL that downloads wins.
fetch() {
_dest=$1
shift
for _u in "$@"; do
echo "fetching $_u"
if curl -fsSL -o "$_dest" "$_u"; then
return 0
fi
done
echo "mkstaticdeps: no mirror worked for $_dest" >&2
return 1
}
# An archive is only reused when the stamp beside it still names the version we
# would build. Without this a stale libcurl.a from some earlier build silently
# skips the whole curl step, which is exactly the kind of thing that hides a
# broken configure line.
fresh() {
[ "$FORCE" != 1 ] && [ -f "$1" ] && [ -f "$2" ] &&
[ "$(cat "$2" 2>/dev/null)" = "$3" ]
}
stamp() { echo "$2" > "$1"; }
mkdir -p "$OUT"
# --- c-ares -------------------------------------------------------------
# curl's configure wants the headers and the .pc file, not just the archive, so
# this prefix outlives the run. It lives under static-deps/, which is ignored,
# and it is what makes the skip below safe: without the .pc file a rerun would
# point --enable-ares at a prefix that no longer exists.
CARES_PREFIX=${CARES_PREFIX:-$OUT/cares-prefix}
CARES_STAMP=$OUT/.cares-version
if fresh "$OUT/libcares.a" "$CARES_STAMP" "$CARES_VERSION" &&
[ -f "$CARES_PREFIX/lib/pkgconfig/libcares.pc" ]; then
echo "libcares.a $CARES_VERSION present, skipping (FORCE=1 to rebuild)"
else
fetch "$WORK/cares.tgz" \
"https://c-ares.dev/downloads/c-ares-$CARES_VERSION.tar.gz" \
"https://github.com/c-ares/c-ares/releases/download/v$CARES_VERSION/c-ares-$CARES_VERSION.tar.gz"
echo "extracting c-ares $CARES_VERSION"
tar xzf "$WORK/cares.tgz" -C "$WORK"
cd "$WORK/c-ares-$CARES_VERSION"
# c-ares 1.34 has no --disable-tools; only the test suite is optional.
./configure --prefix="$CARES_PREFIX" \
--enable-static --disable-shared \
--disable-tests \
CFLAGS="-O2 -fno-lto"
make -j"$JOBS"
make install
cp "$CARES_PREFIX/lib/libcares.a" "$OUT/libcares.a"
stamp "$CARES_STAMP" "$CARES_VERSION"
echo "wrote $OUT/libcares.a"
fi
# --- curl ---------------------------------------------------------------
CURL_STAMP=$OUT/.curl-version
if fresh "$OUT/libcurl.a" "$CURL_STAMP" "$CURL_VERSION"; then
echo "libcurl.a $CURL_VERSION present, skipping (FORCE=1 to rebuild)"
else
fetch "$WORK/curl.tar.xz" \
"https://curl.se/download/curl-$CURL_VERSION.tar.xz" \
"https://github.com/curl/curl/releases/download/curl-$CURL_VERSION/curl-$CURL_VERSION.tar.xz"
echo "extracting curl $CURL_VERSION"
tar xf "$WORK/curl.tar.xz" -C "$WORK"
cd "$WORK/curl-$CURL_VERSION"
set -- --disable-shared --enable-static \
--with-ssl \
--enable-ares="$CARES_PREFIX" \
--without-libpsl --without-brotli --without-zstd --without-nghttp2 \
--without-libssh2 \
--disable-ldap --disable-ldaps --disable-rtsp --disable-dict --disable-telnet \
--disable-tftp --disable-pop3 --disable-imap --disable-smtp --disable-gopher \
--disable-mqtt --disable-manual --disable-docs \
CFLAGS="-O2 -fno-lto"
[ -n "$CA" ] && set -- "$@" --with-ca-bundle="$CA"
./configure "$@"
make -j"$JOBS"
mkdir -p "$OUT"
cp lib/.libs/libcurl.a "$OUT/libcurl.a"
stamp "$CURL_STAMP" "$CURL_VERSION"
echo "wrote $OUT/libcurl.a"
fi