an osu! server in Zig, built for Stable and lazer.
i wanted a server i could understand, change and run myself. that became the backend, a pinned lazer client, the website, multiplayer, a beatmap mirror and all the less exciting work needed to keep them running.
Stable and zigcho!lazer use the same local kai account. roles, restrictions, friends and presence belong to that account. an official osu! account is separate; this is an unofficial project and has no affiliation with ppy.
| repo | what belongs in it |
|---|---|
| zigcho | the Zig server, website, PostgreSQL storage, scoring, chat, multiplayer, mirror and release tooling. start here for backend work. |
| zigcho!lazer | the patch against a pinned official osu! commit, client checks and Windows, macOS, Linux, Android and unsigned iOS packaging. |
| stable-conformance | the external Stable protocol harness, packet and route inventory, and stateful comparisons with bancho.py. |
| .github | this profile and the shared issue, contribution, support and security defaults. |
| private anticheat | the server-side detection module, evidence checks and calibration. access and use need explicit permission. |
new services and tools made specifically for zigcho go in this org. unrelated projects stay on my account.
Stable has its Bancho login, chat, friends, spectating, multiplayer, tournaments and ScoreV2. lazer has its own API and realtime paths for solo scores, profiles, maps, chat, rooms, playlists and ranked play.
the website brings profiles, weighted top plays, first places, recents, pins, replay downloads, medals, maps, teams and rooms together. account settings and staff controls use the same account state as the clients. the kai bot handles player and staff commands in game.
both clients keep their own plays. the website can show combined stats or separate Stable, lazer and ScoreV2 views. combined stats use lazer's legacy score value alongside Stable scores, with the highest-PP result per map for weighted bests. vanilla, Relax and Autopilot stay in their own lanes. raw lazer score values still belong on the lazer play itself.
flowchart TB
players["Stable / zigcho!lazer / browser"]
edge["Cloudflare<br/>public DNS + HTTPS + caching"]
ingress["Layerline<br/>private ingress"]
origin["private origin<br/>Zigcho HTTP listener"]
boundary["trusted proxy + request limits"]
router["host + method + path router"]
players --> edge --> ingress --> origin --> boundary --> router
Layerline carries traffic from the public edge to the origin. the origin is the actual server, not another name for Cloudflare. its address stays private.
| request | where it goes |
|---|---|
osu.kai.ovh and Bancho hosts |
Stable login, packets and legacy /web/*.php routes. |
api.kai.ovh |
OAuth, lazer API responses and the state behind the client's screens. |
kai.ovh |
the website, profiles, account settings and staff tools. |
| asset, avatar and beatmap hosts | media, covers, previews, map packages and replay downloads. |
| chat, spectator and multiplayer traffic | the matching Stable or lazer transport, backed by shared sessions and room state. |
the route then reaches its domain code: accounts, maps, scores, chat, rooms or moderation. PostgreSQL is the production source of truth. SQLite remains for fixtures and offline tools.
flowchart TB
services["Zigcho domain services"]
db["PostgreSQL<br/>accounts + sessions + scores + game state"]
objects["S3-compatible storage<br/>sets + replays + avatars + backups"]
services --> db
services --> objects
map resolution can use the configured upstream metadata and beatmap sources when a set is missing. the mirror stores whole sets, and beatmaps.kai.ovh provides downloads and storage status.
the client-specific parser validates the submission before it reaches scoring. the local Rust PP bridge selects the pinned calculator for that score's ruleset and mods. the versioned anticheat boundary passes bounded evidence to the private module when enabled; enforcement and staff review remain owned by the server.
accepted plays are persisted with their stats and achievement changes. a failed play can add the totals it is meant to add without becoming a leaderboard best. stored replay data can move to object storage, and failed replays are not downloadable. eligible scores reach the in-game announcement channel and Discord score webhook.
profiles, leaderboards and results screens read that stored result. the clients still have different wire formats, so each gets the response its own screen expects.
the server is pinned to Zig 0.16.0 with a Rust PP bridge. the server README has the build commands and domain folder layout. the client README covers its upstream pin, patch and portable packages.
GitHub runners build the release artifacts. a server release gets an immutable directory, a PostgreSQL backup, a restore check and a rollback target before activation. client packages record both the Zigcho revision and the official osu! revision they were built from.
the changelog reads the Markdown release history, so changing the notes doesn't need another client build. the performance reports keep measured results and their limits together; a short load run is not an hour-long capacity claim.
a green route or build is useful evidence, but i still want the player to reach the map, submit the score and finish the room. report the client version and the actual broken flow when something doesn't work.
the public server includes the versioned integration ABI. the detection module itself is private, separately licensed and not included with public server source. repository or binary access alone does not grant permission to use it.
the aim is evidence staff can review, with a clear difference between suspicious play and incomplete data. private rules and calibration stay out of public issues and release notes. account and hardware-match appeals go to kai.ovh/appeal.
keep a change about one real problem. use the shared contribution guide, and send security reports through private vulnerability reporting. don't post passwords, tokens or raw hardware identifiers in issues.
original public Zigcho work uses the Zigcho Public Use License. it allows public servers, production use, modifications and free redistribution with credit, a link back and clear changes. sale and paid access need written permission. older copies released under MIT keep those MIT terms.
the private anticheat has its own permission-only licence. upstream osu! code keeps its MIT licence, and other libraries, artwork, beatmaps and music keep their own terms. the public licence does not grant rights to those assets or the osu!/ppy branding.