Repository navigation
Roadmap
Where CaseDropper is headed. Spoiler: it already arrived, burned the map, rebuilt the entire thing in Zig, embedded a scripting language, and is now sitting in a FROM scratch container wondering what to conquer next.
- Serve static files
- Case-insensitively
- Using the .NET runtime like it's 2019 and we don't know any better
-
Dictionary<string, string>that grew forever like an unattended Kubernetes cluster -
FileSystemWatcherthat occasionally noticed files existed - Container image that included an entire operating system because .NET said so
This version worked. We are not grateful. It carried a garbage collector and a runtime and it served files at the speed of "managed code." It was the mass-produced furniture of static file servers. Functional, soulless, and full of hidden dependencies you didn't ask for.
- Rewrite the entire project in Zig 0.15.2 because we saw the light
- Zero external dependencies. Not "few." Zero. The number between -1 and 1.
-
PathMapusingstd.StringHashMap— a hash map that doesn't need a garbage collector to survive -
std.Thread.RwLockfor concurrent reads — reader-writer locks, like Zig intended -
std.Thread.Poolsized to CPU count — threads managed by people who understand threads - Linux inotify for file watching — direct kernel API, no wrapper, no abstraction, no nonsense
- 1-second debounce on rebuilds — the map is torn down and reconstructed, not incrementally patched like a coward
- Full rebuild on changes — deletions actually work now, unlike the old C# version that hoarded paths like emotional baggage
- Single static binary — no runtime, no shared libraries, no questions asked
-
FROM scratchcontainer — nothing in it except the binary and the void - Cross-compilation to 9 architectures from a single host, because Zig doesn't need QEMU to acknowledge that ARM exists
The project is spiritually complete. The binary is pure. The container is empty. Everything below is scope creep dressed up as ambition, except the scope creep turned out to be kind of incredible.
- Directory browsing behind
ENABLE_DIRECTORY_BROWSINGenv var — for the nostalgic IIS admin who misses seeing file listings in a table - Multi-arch Docker builds:
amd64,arm64,armv7,i386,riscv64,ppc64le,s390x,mips64le,loong64— nine architectures, one build host, zero excuses - Hot reload via inotify — drop files in, the map rebuilds. No restart. Zig watches. Zig knows.
- CI/CD with GitHub Actions — the binary builds itself in the cloud, tests itself, and publishes itself to GHCR. We just watch.
All of this is done. Shipped. In production. Running on scratch containers across nine CPU architectures. The roadmap for v1.1 is a victory lap.
- Embedded Lua 5.4.7 scripting engine — we put a scripting language in a static file server and we'd do it again
- Vendored 32 Lua C source files into
deps/lua/src/— zero external dependencies, compiled by Zig, cross-compiled to all 9 architectures -
LUA_SCRIPTenv var — point it at a Lua script, the server generates URL aliases at index time - Lua
rewrite(path)function called during index build — not per-request, because we have taste - Aliases stored in the same
StringHashMap— the hash map doesn't know they came from Lua and it doesn't need to know - musl libc linkage — Lua needs libc, musl keeps it static, the binary remains a single file, the container remains
FROM scratch, purity is nuanced - URL percent-decoding in
normalizePath— browsers percent-encode Unicode, the server decodes it, the hash map resolves it, the file is served - Example Lua scripts — shipped in
scripts/, discoverable by the curious, regrettable by the cautious -
smallcaps.lua— Latin → Unicode small caps, because after Cyrillic homoglyphs, small caps felt restrained - Hot reload with Lua — filesystem changes trigger a rebuild that re-runs the Lua script on every path, regenerating all aliases from scratch
- First-write-wins collision handling — real file paths always beat Lua aliases, Lua aliases don't overwrite each other, the hash map is fair
The project has transcended its original purpose. What started as "serve files case-insensitively" is now "serve files case-insensitively via a hash map populated by an embedded Lua scripting engine running character substitution functions decoded from obfuscated JavaScript, accessible via Cyrillic URLs that get percent-decoded by a Zig HTTP server running in an empty Docker container on nine CPU architectures." This is not scope creep. This is scope achievement.
- Health check endpoint (return 200 if the hash map exists, which it always does, because Zig doesn't lose hash maps)
- Metrics endpoint (count how many times someone requested
favicon.ICOinstead offavicon.ico— the only metric that matters. Also count Lua alias hits vs direct path hits, because we want to know if anyone actually uses Cyrillic URLs, and we're afraid of the answer.) - Graceful shutdown (currently it just stops, which is arguably the most graceful thing a process can do)
- Custom 404 page (serve a file when a file isn't found — the irony is not lost on us. Bonus: serve the 404 page via its Lua-rewritten alias.)
- Lua script hot reload (currently the script is loaded once at startup; changing it requires a restart. We could watch the script file too, but then we'd be watching the watcher, and that's the kind of recursive architecture decision that ends careers.)
None of these are started. None of these are planned. They exist here so the roadmap has a future section, which makes the project look alive. The project is alive. It just doesn't need features. It needs to serve files case-insensitively with optional Lua-generated URL aliases, and it does that already, in Zig, perfectly.
- Multiple Lua scripts (chain them — Cyrillic-ify the smallcaps of the original path, creating URLs in tiny Ukrainian. No one asked for this. No one should.)
- Web UI dashboard (to monitor the static file server that has four configuration variables, one hash map, and a Lua engine that runs once at startup. The dashboard would need more infrastructure than the server.)
- WASM support (compile CaseDropper to WebAssembly and serve files from inside a browser serving files to itself — we've gone too far but Zig could actually do this, and the Lua engine would probably survive the transition)
- AI-powered case prediction (predict what casing the user meant to type — it's always lowercase, the model has 100% accuracy, we don't need AI for this but we do need a bullet point that mentions AI for SEO)
- LuaJIT integration (replace PUC Lua with LuaJIT for faster index builds. The index build takes milliseconds. JIT compilation takes longer than the thing it's compiling. This is a solution searching for a problem and not finding one.)
- Rewrite in C (just kidding. Zig or nothing. Andrew Kelley didn't forge a language so we could go back to header files and undefined behavior.)
CaseDropper will continue to serve files case-insensitively, with optional Lua-generated URL aliases in Cyrillic, small caps, or whatever other alphabet someone writes a rewrite() function for, until:
- The sun engulfs the earth
- The maintainer discovers that nginx can do this with two lines of config (it can't do the Lua part — well, OpenResty can, but it does it per-request, like an animal)
- Zig reaches 1.0 and we have to update the build.zig (this will happen first)
- Someone writes a Lua script that generates aliases in Wingdings and we have to seriously reconsider what we've built
Whichever comes first. In the meantime, the binary sits in its empty container, statically linked, watching the filesystem with inotify, lowercasing paths, running Lua scripts at index time, decoding percent-encoded Cyrillic URLs, serving files. No garbage collector. No runtime. No regrets. Just Zig, Lua, and a hash map that knows too much.