| muslkit: musl static libraries from Alpine, without Alpine 5feb050 nandi 15h ago | 1 | # muslkit |
| 2 | |
| 3 | musl-linked static libraries from Alpine, without Alpine. |
| 4 | |
| 5 | Alpine builds everything against musl and ships `*-static` packages for most of |
| 6 | it. An `.apk` is just a tarball, and an `APKINDEX` is just a text file — so you |
| 7 | don't need apk, a chroot, a container or a distro to get `libcrypto.a` built |
| 8 | for musl. muslkit fetches the index, resolves a dependency closure, downloads |
| 9 | the packages and unpacks them into a sysroot directory you own. |
| 10 | |
| 11 | Nothing is installed system-wide. Nothing needs root. |
| 12 | |
| 13 | ```bash |
| 14 | muslkit add openssl-libs-static |
| 15 | nim c -d:ssl $(muslkit nimflags) --cc:clang --clang.exe:./zigcc app.nim |
| 16 | ``` |
| 17 | |
| 18 | ## Why |
| 19 | |
| 20 | Getting a fully static Nim (or C, or Zig) binary with TLS on a glibc distro |
| 21 | means finding musl-built archives, and the usual answers are "install Alpine", |
| 22 | "run Docker" or "use Nix". Alpine's mirrors already serve exactly the files — |
| 23 | this just takes them. |
| 24 | |
| 25 | ## Usage |
| 26 | |
| 27 | ``` |
| 28 | muslkit add <pkg>... Download packages and unpack into the sysroot |
| 29 | muslkit list Show what the sysroot holds |
| 30 | muslkit libs Show the static libraries in the sysroot |
| 31 | muslkit search <text> Search the index (names and descriptions) |
| 32 | muslkit show <pkg> Index record for one package |
| 33 | muslkit nimflags [-l lib] Print nim flags for a static build |
| 34 | muslkit ccflags [-l lib] Print cc/clang flags |
| 35 | muslkit nimcfg [-o file] Write a nim.cfg fragment |
| 36 | muslkit zigcc [-o file] Write a `zig cc -target …-musl` wrapper script |
| 37 | muslkit env Shell exports (PKG_CONFIG_*, MUSLKIT_ROOT) |
| 38 | muslkit path Print the sysroot path |
| 39 | muslkit clean Remove the sysroot (cache is kept) |
| 40 | ``` |
| 41 | |
| 42 | Options: `-r/--root`, `-b/--branch` (default `v3.21`, `edge` for rolling), |
| 43 | `-a/--arch`, `-m/--mirror`, `--repo main,community`, `-l/--lib`, `--cc`, |
| 44 | `--no-deps`, `--refresh`, `-q/--quiet`. |
| 45 | |
| 46 | The sysroot defaults to `$XDG_DATA_HOME/muslkit/sysroot` and honors |
| 47 | `MUSLKIT_ROOT`. Downloads are cached under `$XDG_CACHE_HOME/muslkit`, so a |
| 48 | second `add` is offline and the index is re-fetched only once a day. |
| 49 | |
| 50 | ## A full static build, start to finish |
| 51 | |
| 52 | ```bash |
| 53 | muslkit add openssl-libs-static zlib-static |
| 54 | muslkit zigcc # writes ./zigcc (needs zig on PATH) |
| 55 | nim c -d:release -d:ssl \ |
| 56 | --cc:clang --clang.exe:./zigcc --clang.linkerexe:./zigcc \ |
| 57 | $(muslkit nimflags) --passL:-s \ |
| 58 | -o:app src/app.nim |
| 59 | ``` |
| 60 | |
| 61 | `muslkit nimflags` emits more than include and library paths, because two |
| 62 | things bite every static Nim build with TLS: |
| 63 | |
| 64 | - **`-d:ssl` makes Nim `dlopen` libssl at runtime.** A static binary cannot, |
| 65 | and dies at startup with `could not load: libcrypto.so(...)` — even on code |
| 66 | paths that never touch the network. The fix is `--dynlibOverride:ssl |
| 67 | --dynlibOverride:crypto` plus the archives on the link line. |
| 68 | - **OpenSSL 3 removed `SSL_get_peer_certificate`,** which Nim's wrapper still |
| 69 | references, so the link fails on one undefined symbol. The fix is |
| 70 | `-DSSL_get_peer_certificate=SSL_get1_peer_certificate`. |
| 71 | |
| 72 | Both are emitted automatically when `libssl.a` and `libcrypto.a` are present in |
| 73 | the sysroot. A static binary carries no CA trust store, so set `SSL_CERT_FILE` |
| 74 | to a bundle on the host that runs it. |
| 75 | |
| 76 | Cross-compiling is the same command with `--arch`; `muslkit add -a aarch64 |
| 77 | openssl-libs-static` and `muslkit zigcc -a aarch64` line up. |
| 78 | |
| 79 | ## Trust |
| 80 | |
| 81 | Packages come over HTTPS from the mirror and are unpacked as-is. muslkit checks |
| 82 | the size recorded in the index but does **not** verify Alpine's RSA signatures — |
| 83 | apk's checksum field covers the package's control segment, not the file, so a |
| 84 | real check means implementing apk's signature format. Treat a sysroot as build |
| 85 | input, not as a trust root. If that matters for your use, pin a mirror you run. |
| 86 | |
| 87 | ## Install |
| 88 | |
| 89 | ```bash |
| 90 | nimble install |
| 91 | ``` |
| 92 | |
| 93 | or build in place with `nim c -d:ssl -o:muslkit src/muslkit.nim`. Requires |
| 94 | `tar` on PATH (an `.apk` is concatenated gzip streams, which GNU tar reads) and, |
| 95 | for the `zigcc` helper, `zig`. |
| 96 | |
| 97 | ## Library |
| 98 | |
| 99 | ```nim |
| 100 | import muslkit |
| 101 | |
| 102 | let remote = initRemote(branch = "edge") |
| 103 | let idx = remote.fetchIndex() |
| 104 | for pkg in idx.resolve(["openssl-libs-static"]): |
| 105 | echo pkg.name, " ", pkg.version, " ", remote.fetchPackage(pkg) |
| 106 | ``` |
| 107 | |
| 108 | Modules: `muslkit/index` (APKINDEX parsing, provides and dependency |
| 109 | resolution), `muslkit/repo` (mirror, cache, download, unpack), |
| 110 | `muslkit/sysroot` (manifest, static-lib discovery), `muslkit/flags` (nim/cc/ |
| 111 | pkg-config flag emission). |
| 112 | |
| 113 | ## Tests |
| 114 | |
| 115 | ```bash |
| 116 | nimble test |
| 117 | ``` |
| 118 | |
| 119 | The suite is offline — index parsing, resolution through `so:`/`pkg:` provides, |
| 120 | manifest round-trips and flag emission all run against fixtures. |