Packaging a thread-pool web server derivation, devShell, flake check
Beat A built the book's server. Beat B gives it a store path, notices it reads two files at runtime, and installs them too.
<!-- hal:authoritative:yaml -->
Beat A built the book's server. Beat B gives it a store path, notices it reads two files at runtime, and installs them too.
§I - Frame
Duha Beat B, Nix packaging session 07. Beat A (Rust session 24) walked TRPL Ch.21: a request-line server, a four-thread pool, and a Drop that closes the channel and joins. This shell packages that crate, named hello as in the book.
Session 06 packaged a print-and-exit binary. This one is different in one way that matters to Nix: handle_connection calls fs::read_to_string("hello.html") at request time. The HTML is runtime data, not source the compiler consumes. A derivation that ships only $out/bin/hello builds green and serves nothing.
Asr owns the Pills cursor and the Rust-roads rows (#16 crate-as-derivation, #19 flake devShell/checks). Those Done cells stay unmarked.
Three moves land by the end:
- The Crate Derivation:
buildRustPackageforhello, with apostInstallthat carries the two HTML files. - The Dev Shell:
devShells.defaultas the place you runcargo runandcurl. - The Flake Check: a
checksentry that builds the package and runs its tests, and what it does not prove.
Done-criteria: Can sketch a buildRustPackage for the web server that installs its HTML, open a Rust devShell, and name one flake checks entry that proves the build.
§II - The Crate Derivation
The rust-tops corpus nix/cargo-tops.nix is still the skeleton: pname, version, src = lib.cleanSource ../., cargoLock.lockFile, doCheck = true, and a meta.mainProgram. Read-only teaching from the repo; no build cadence attaches to it.
For the web server, keep that skeleton and add one phase:
{ lib, rustPlatform }:
rustPlatform.buildRustPackage {
pname = "hello";
version = "0.1.0";
src = lib.cleanSource ./.;
cargoLock.lockFile = ./Cargo.lock;
doCheck = true;
postInstall = ''
mkdir -p $out/share/hello
cp hello.html 404.html $out/share/hello/
'';
meta.mainProgram = "hello";
}
cargoLock.lockFile pins the crate graph. The book's server is std only, so the lock lists one package and the builder fetches nothing. doCheck runs cargo test in the sandbox. Beat A's two unit tests drain eight jobs through Drop and assert new(0) panics. Neither opens a socket, which keeps the check honest inside a build sandbox.
postInstall runs after the binary lands in $out/bin. It copies the pages to $out/share/hello. The binary still opens hello.html relative to its working directory, so you run it from that folder: cd $(nix build --print-out-paths)/share/hello && ../../bin/hello. A later version would take the directory as an argument. Changing the Rust is Beat A's call, not this shell's.
Do not copy buildAndTestSubdir from the corpus. This crate is a single package, not a workspace member.
§III - The Dev Shell
The release derivation is not where you edit. devShells.default puts the toolchain on PATH without making it a runtime input of hello:
devShells = forAllSystems (
system:
let
pkgs = pkgsFor system;
in
{
default = pkgs.mkShell {
packages = [
pkgs.rustc
pkgs.cargo
pkgs.clippy
pkgs.rustfmt
pkgs.curl
];
};
}
);
That is the rust-tops flake.nix pattern with one addition, curl, because this crate is checked by hand with requests. Enter with nix develop, then cargo run from the crate root, where hello.html lives. In a second terminal, curl -i http://127.0.0.1:7878/ should show HTTP/1.1 200 OK and a Content-Length. /nope should show 404 NOT FOUND. After two connections the Listing 21-25 main returns and the pool prints its shutdown lines.
The shell gives you a pinned compiler and a client. The thread pool, the channel, and the Drop order live in Rust. The shell is the edit path, not $out.
§IV - The Flake Check
packages ships, checks proves. The corpus flake points its check at its own package:
checks = forAllSystems (
system:
{
hello = self.packages.${system}.hello;
}
);
nix flake check builds that attribute, which compiles hello, runs cargo test because doCheck is on, and runs postInstall. A missing 404.html would fail the cp and fail the check. That is the one runtime-file mistake this check catches.
It does not start the server. A tighter check would be a runCommand that launches hello from $out/share/hello, sends two requests, and asserts 200 then 404. Loopback inside a build sandbox varies by platform, so treat that as a separate, deliberate check, not a default.
This lesson does not claim a flake check ran. nix is on the lab Mac. No throwaway nix flake check was executed for this fire, so there is no exit code to report.
§V - Proof and close
- Copy the derivation and say what
postInstalladds thatbuildRustPackagewould not install on its own. - Say why the two unit tests are safe under
doCheckand a socket test might not be. - Open the
devShell, name the twocurlcalls, and the status line each should return. - Add
checks.helloand name the one runtime-file mistake it would catch. - Name the Asr NIX-SYLLABUS rows that teach these moves later (#16, #19) and confirm this bundle did not mark them.
Done-criteria: Can sketch a buildRustPackage for the web server that installs its HTML, open a Rust devShell, and name one flake checks entry that proves the build.
This is the last Beat B on a TRPL row. Beat B ends with the dual-fire shape on 2026-10-10.