{"id":30027747,"url":"https://github.com/foxssake/nohub","last_synced_at":"2025-08-06T13:43:43.549Z","repository":{"id":307898181,"uuid":"987277220","full_name":"foxssake/nohub","owner":"foxssake","description":"Self-hosted lobby and server list manager","archived":false,"fork":false,"pushed_at":"2025-08-03T13:24:54.000Z","size":26,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-03T14:30:37.621Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/foxssake.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-20T20:52:17.000Z","updated_at":"2025-08-03T13:24:54.000Z","dependencies_parsed_at":"2025-08-03T14:32:31.414Z","dependency_job_id":null,"html_url":"https://github.com/foxssake/nohub","commit_stats":null,"previous_names":["foxssake/mobhub","foxssake/nohub"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/foxssake/nohub","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxssake%2Fnohub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxssake%2Fnohub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxssake%2Fnohub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxssake%2Fnohub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/foxssake","download_url":"https://codeload.github.com/foxssake/nohub/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/foxssake%2Fnohub/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269092021,"owners_count":24358554,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-06T02:00:09.910Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-08-06T13:43:38.679Z","updated_at":"2025-08-06T13:43:42.676Z","avatar_url":"https://github.com/foxssake.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nohub\n\nA lobby manager for multiplayer games.\n\n## Proposals\n\nThis section contains proposals on what *nohub* should be, and how to\nimplement it.\n\n### Vision\n\n*Nohub* should be a simple to manage, self-hosted solution for managing player\nlobbies and server lists for multiplayer games.\n\n* Should be easy to integrate with Godot, but not exclusive to it\n  * This includes other game engines, but also games built from scratch\n* Should be easy to self-host\n  * Easy to setup on a variety of setups, from a single VPS to large Kubernetes\n    clusters\n* Should be game-agnostic\n  * Does not make assumptions about the game if not necessary\n  * Does not prescribe what the user's game should be like\n* Should be focused\n  * Includes only the features needed to bring players together in lobbies\n\n### Use cases\n\nSome common usages for lobby services and how they relate to *nohub*:\n\n- Creating a lobby\n  - Client connects to the service\n  - Client requests a lobby, sending in initial data\n  - Service creates a lobby and binds it to the client\n  - Client receives lobby ID\n- Listing lobbies\n  - Client connects to the service\n  - Client requests the list of lobbies\n  - Service streams the lobbies in response\n    - ❓Do we send only lobby IDs, or some data too? And if so, what data? Does\n      the client specify that?\n  - List is updated by streaming changes\n  - Clients can terminate the updates by submitting a request\n- Joining a lobby\n  - Client submits a join request with the lobby's ID\n  - Service responds with a connection string\n  - Client parses the connection string and connects\n  - Examples:\n    - `enet://98.109.115.132:16384`\n    - `noray://tomfol.io:8890/k394RbdoV-tvJorXyOIUE`\n  - The address string is completely up to the game, though an URI-like syntax\n    is recommended\n- Displaying lobby name, player count, etc.\n  - Lobbies only contain the necessary data by default ( id, visibility, is\n    locked, etc. )\n  - Owning clients can set custom data\n  - Custom data is stored in a key-value map\n  - Keys can be arbitrary, though we'll provide some standards for common stuff\n  - Value is also arbitrary\n  - Configurable limits apply ( e.g. max key length, max value length, max\n    entry count, etc. )\n- Managing a lobby\n  - Lobby can only be managed by the client who created it\n  - This is bound to the socket connection\n  - Creating client can submit requests that update lobby data\n  - Creating client can submit requests that manipulate the lobby ( e.g.\n    delete, lock, make private, etc. )\n  - Requests must include the lobby being managed ( e.g. by ID )\n  - Requests trying to modify someone else's lobby are rejected\n- Making a lobby hidden\n  - Hidden lobbies can be joined by ID, but are not listed\n  - Owning client can either create the lobby as hidden, or submit a request to\n    hide the lobby\n  - Same for making the lobby visible again\n- Making a lobby private\n  - Private lobbies are hidden, and usually only invited players can join\n  - Owning client makes the lobby hidden\n  - Whenever someone tries to connect, the game runs its own authentication\n    - e.g. hosting game process distributes a special join key over a\n      background service\n  - This case is low-prio until there's some social service we can integrate\n    with\n- Locking a lobby\n  - Locked lobbies are visible but can't be joined\n  - For example the game is about to start after a countdown\n  - During the countdown, we don't want to delete the lobby, but also don't\n    want anyone to join\n  - Owning client can submit a request to lock / unlock lobby\n  - ❓ Is this really that common / necessary?\n\n### Features\n\n- Track lobbies\n  - Define relevant interfaces ( e.g. Lobby )\n  - Keep a list of lobbies in memory\n  - Implement CRUD operations\n  - No public API to implement at this point\n- Create lobby flow\n  - Create lobby with submitted custom data\n  - Bind lobby ownership to creating client\n  - Respond with lobby ID\n- Lobby update operations\n  - Set custom data keys - support updating multiple values at once\n  - Set flags - is locked, is hidden\n  - Delete lobby\n- Lobby list\n  - Expect a request\n  - Reply with stream of current lobbies\n  - ❓ Client submits list of custom data keys, response includes custom data\n    per lobby\n- Querying lobbies\n  - Client submits a request, with lobby ID and optionally custom data keys\n    needed\n  - Service responds with base lobby data + custom values\n  - Service responds with error if lobby doesn't exist\n  - ❓ Should clients be able to query hidden lobbies if they have the ID\n    already? \n- Lobby join\n  - Reject if locked\n  - Check if lobby ID valid\n  - Respond with connection string\n- Lobby list subscription\n  - Clients receive list updates in real time\n  - Lives in a separate module\n  - Lobby module emits events - lobby created, deleted, hidden, showed, locked,\n    unlocked\n  - Client submits request for updates\n  - Respond with stream\n  - Client can submit request to stop updates\n  - ❗ May overlap with existing list functionality\n  - Optional: Buffer updates for some time / count before sending them\n    - We could even consolidate the list\n    - e.g. if a lobby is created and destroyed in the same batch, send 0 events\n      instead of 2\n\nPossibilities for the future:\n\n- Store lobbies in DB\n  - Motivation: keep the list of lobbies even if the service restarts\n  - Idea: build a separate module that reads the list on startup, updates DB on\n    lobby module events\n- Ring layout\n  - Motivation: a single instance may not be enough, run multiple and\n    distribute the load\n  - Problem: if an instance creates a lobby, the rest of the instances need to\n    know\n  - Idea: configure a list of other hosts, where we broadcast our lobby events\n    - This allows for arbitrary layouts, e.g. ring, star, p2p, etc.\n  - ❗ Make sure to not overwrite each other's changes when using a shared DB\n  - ❗ Make sure to give each event an ID, so the same event isn't sent back\n    and forth forever between two instances\n\n### Authentication\n\nTODO\n\n### High level architecture\n\nSimilar to noray, the service will be structured as a thin outer shell ( config\nparsing, app entry point ), wrapping multiple modules. Each module provides\ncertain functionality by hooking into the app's lifecycle. Modules may depend\non each other.\n\nTODO: Some mermaid diagrams of the various components\n\n### Data model\n\n```mermaid\nclassDiagram\n    class Lobby {\n        +string id\n        +bool isLocked\n        +bool isVisible\n        +map~string, string~ customData\n\n        -string connectionString\n    }\n```\n\nTODO: Some mermaid diagrams of what data is stored ( lobbies, participants, etc. )\n\n### Tech stack\n\n- TypeScript\n- Bun\n- [Trimsock]\n- [pino] for logging\n- [Prometheus] for metrics\n\n\n[Trimsock]: https://github.com/foxssake/trimsock\n[pino]: https://getpino.io/\n[Prometheus]: https://prometheus.io/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoxssake%2Fnohub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffoxssake%2Fnohub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoxssake%2Fnohub/lists"}