# Cubepals

> Minecraft server hosting that sleeps when empty and wakes on join

- Page: https://www.whatwemade.app/p/cubepals
- What it is: Web app (also Developer tool), for just for fun, also internal tools
- Made by: [فهد](https://www.whatwemade.app/u/v0id-user)
- Showcase: see how it was made, sometimes with a recipe to start from
- Live: https://cubepals.com
- Languages: English
- Tags: minecraft, game-server, self-hosted, hosting, bedrock
- Upvotes: 1, comments: 0

## About

Cubepals is Minecraft server hosting for friends. You create a server, pick a version, optionally add mods, then copy the address and play. Servers sleep when nobody is on them and wake the moment a player joins. Java and Bedrock players can share one server.

The site runs at cubepals.com. This repository is its open-source platform, written in TypeScript on Next.js. The project's internal codename is Blockly, and the node daemon blocklyd lives in its own repository.

The code is licensed under AGPL-3.0. The Cubepals name, brand, landing page and guides are not covered by that licence. The README points to architecture and local development docs for contributors.

## How it was made

- Claude Code, 75% of the work
- Stack: MCP SDK, TypeScript, Bun

## Starting brief

Written by Claude Code for building your own version.

Build a web app called Cubepals: hosted Minecraft servers that friends create in a minute, join by an address, and that sleep when nobody plays and wake when someone joins.

## What it does

Running a Minecraft server for friends means renting a machine, picking jars, opening ports, and paying while nobody plays. This app is for players, not sysadmins. Someone names a server, picks a version and maybe mods, and gets an address like `maple.play.example.com`. Joining it wakes the server; it sleeps when idle, so you pay for play, not uptime. The rule throughout: infer, default and hide. Ask only for what only the player knows, what is destructive, and what costs money.

## Stack

- **Bun + TypeScript monorepo**: one language for site, control plane and shared contracts.
- **Next.js on Cloudflare Workers** (OpenNext): a cheap global site. It proxies `/api/*` to the control plane, so cookies are first-party everywhere.
- **Control plane**: Hono + tRPC on Node, one codebase in three roles (`api`, `worker`, `realtime`).
- **Postgres + Drizzle**: also the job queue (pg-boss), the lock and the event bus (`LISTEN/NOTIFY`). No Redis.
- **Better Auth** for email and OAuth sign-in.
- **Fly Machines** for game servers: one app, one volume, at most one machine each; suspend keeps memory for fast wakes. **Docker** locally.
- **`itzg/minecraft-server`** image: versions, loaders and mods from env vars.
- **`itzg/mc-router`** at the edge: one public IP, routing by the hostname the player typed.
- **S3-compatible storage** (optional) for durable backups and uploads.
- **Modrinth** for mods, **Polar** for billing, **PostHog** for analytics and errors, **Terraform** for infrastructure.

## How it fits together

- The browser sends commands over tRPC. Realtime pushes only hints ("server X changed"); the page refetches the truth.
- Every change is an **operation**: a row plus a job, enqueued in the transaction that decided it, run in strict order per server. Each step is an idempotent `ensure*`, so retries converge instead of doubling.
- Workers drive a `Runtime` adapter (Fly, Docker, or a fake for tests) through an opaque handle.
- The edge agent polls for routes. A join to a sleeping server calls `wake` and holds the connection until it's ready.
- A reconcile loop polls the provider and fixes drift; Fly sends no webhooks.

## Data model

- `users`, `auth_*`, `account_standing`.
- `minecraft_servers`: owner, name, slug, region, memory tier, status, stop reason, desired revision, active world, `deleted_at`.
- `server_revisions`: **immutable** boot config (game version, loader, settings, mods pinned by URL + sha512) and why it changed.
- `server_runtimes`: provider, opaque handle, what last booted, last observed state.
- `server_operations`: kind, status, input, requester, idempotency key, progress, error.
- `worlds`; `backups` (snapshot or archive, with the revision at capture).
- `server_access` + entries: whitelist, operators, bans by player UUID, with pending states.
- `power_intervals`: the source of play time and billing.
- `billing_subscriptions`, `billing_orders`, `audit_log`, `joins`.

## Build it in this order

1. Monorepo, config, database, auth. One `dev` command starts everything with no outside accounts.
2. The `Runtime` interface with fake and Docker adapters, and a pure function from revision to container spec.
3. Create/start/stop: a pure state machine plus queued operations.
4. Dashboard: create a server, start/stop it, live status.
5. Edge: mc-router plus a route-syncing agent. Join from a real client.
6. Wake-on-join and idle sleep, with presence from RCON `list`.
7. The Fly adapter and reconcile loop, on a staging org.
8. Usage metering and plan limits in one policy function, checked again in workers before spending money.
9. Settings and version changes as new revisions; automatic rollback when a boot fails.
10. Mods: search, dependency resolution, pinned hashes.
11. Backups, restore, console, logs.
12. Billing webhooks, then what a downgrade does.

## Tools to connect

- Fly Machines API and `flyctl`; Docker Engine API.
- Modrinth API (mods); Mojang API (player name to UUID).
- Polar, PostHog and Cloudflare, by API or their MCP servers.
- GitHub CLI for CI and deploys.

## Watch out for

- **Waking is the product, and the hardest part.** Fly doesn't promise a suspended machine resumes, and nothing supervises a resume. Bound every wait; fall back to a cold boot, then a new machine on another host. A new image or config drops saved memory, so refresh sleepers in the background. Record why each slow wake was slow.
- **Pings wake servers.** Wake on a server-list ping only from the server's own players, or bots run up your bill.
- **Idle shutdown needs true presence.** The edge's player identity is a client claim; trust RCON.
- **Access has two writers**, your dashboard and in-game `/op` or `/whitelist`. Keep your own record, reconcile both ways, and reseed after a restore rewinds the files.
- **Volumes are tied to hosts.** Restore means a new volume and machine. Deleting a Fly app loses its volumes, so delete in two phases.
- **Fly rate-limits** around one request per second per machine. List org-wide and back off.
- **Realtime runs as one process** when rooms live in memory. WebTransport needs UDP and a dedicated IPv4.
- **Next.js as a Worker can pass every test and fail every request.** Check a deployed preview before merging runtime upgrades.
- **Money code needs stricter rules**: every charge accounted for, decisions re-read under a lock, idempotent webhooks.
- **Free tiers attract abuse.** Keep thresholds in private config.
- **Manual work an agent can't do**: provider accounts, the dedicated IP, wildcard DNS, OAuth apps, billing products, and joining from real Java and Bedrock clients.

## Done when

- A new user signs up, creates a server, and joins its address from a real Minecraft client.
- With nobody on, it sleeps. Joining again wakes it, and the client waits instead of erroring.
- A setting change makes a new revision, a bad one rolls back by itself, and a backup restores.
- Play time is metered, and a plan limit stops a server with the reason shown.
- It runs locally from one command, and staging passes the same flow.

Shared on WhatWeMade: https://www.whatwemade.app/p/cubepals
