···3636```bash
3737cd server
3838mkdir -p db
3939-goose -dir migrations sqlite3 db/game.db up
3939+goose -dir migrations -allow-missing sqlite3 db/game.db up
4040```
41414242### Importing a Snapshot
···65656666### Run
67676868+The server is split into two binaries: a gRPC game server and an HTTP asset CDN. Both must be running for the client to work.
6969+7070+**Start the CDN** (serves asset bundles, list.bin, master data, web pages):
7171+7272+```bash
7373+cd server
7474+go run ./cmd/octo-cdn \
7575+ --listen 0.0.0.0:8080 \
7676+ --public-addr 10.0.2.2:8080
7777+```
7878+7979+**Start the game server** (gRPC, points the client at the CDN):
8080+6881```bash
6982cd server
7083go run ./cmd/lunar-tear \
7171- --host 10.0.2.2 \
7272- --http-port 8080 \
7373- --grpc-port 8003
8484+ --listen 0.0.0.0:8003 \
8585+ --public-addr 10.0.2.2:8003 \
8686+ --octo-url http://10.0.2.2:8080
7487```
75887676-The default gRPC port is 443, which requires `sudo` (privileged port). Use `--grpc-port` with a high port to avoid this. If you do need port 443, either use `sudo` or grant the binary the capability on Linux:
8989+The default listen address is `0.0.0.0:443`, which requires `sudo` (privileged port). Use `--listen` with a high port to avoid this. If you do need port 443, either use `sudo` or grant the binary the capability on Linux:
77907891```bash
7992go build -o lunar-tear ./cmd/lunar-tear
8093sudo setcap cap_net_bind_service=+ep ./lunar-tear
8181-./lunar-tear --host 10.0.2.2 --http-port 8080
9494+./lunar-tear --public-addr 10.0.2.2:443 --octo-url http://10.0.2.2:8080
8295```
83969797+The CDN can run on a completely separate machine — just set `--octo-url` on the game server and `--public-addr` on the CDN to the externally-reachable address.
9898+9999+### Run All Services At Once
100100+101101+Instead of starting each service individually, use the dev runner to launch all three (auth, CDN, game server) with a single command. No Docker required — works on macOS, Linux, and Windows.
102102+103103+```bash
104104+cd server
105105+make dev
106106+```
107107+108108+Or directly:
109109+110110+```bash
111111+cd server
112112+go run ./cmd/dev
113113+```
114114+115115+Each service's output is prefixed with a colored label (`[auth]`, `[cdn]`, `[grpc]`). Press Ctrl+C to shut everything down.
116116+117117+Override defaults with namespaced flags:
118118+119119+```bash
120120+go run ./cmd/dev --grpc.listen 0.0.0.0:9000 --grpc.public-addr 10.0.2.2:9000 --cdn.public-addr 192.168.1.50:8080
121121+```
122122+123123+Or via `make`:
124124+125125+```bash
126126+make dev ARGS="--grpc.listen 0.0.0.0:9000 --grpc.public-addr 10.0.2.2:9000"
127127+```
128128+129129+| Flag | Default | Description |
130130+| --------------------- | ------------------ | ---------------------------------------- |
131131+| `--auth.listen` | `0.0.0.0:3000` | auth-server listen address |
132132+| `--auth.db` | `db/auth.db` | auth-server SQLite database path |
133133+| `--cdn.listen` | `0.0.0.0:8080` | octo-cdn local bind address |
134134+| `--cdn.public-addr` | `10.0.2.2:8080` | octo-cdn externally-reachable addr |
135135+| `--grpc.listen` | `0.0.0.0:8003` | lunar-tear gRPC listen address |
136136+| `--grpc.public-addr` | `10.0.2.2:8003` | lunar-tear externally-reachable addr |
137137+| `--grpc.octo-url` | `http://10.0.2.2:8080` | Octo CDN base URL passed to lunar-tear |
138138+| `--grpc.auth-url` | `http://localhost:3000` | auth server base URL passed to lunar-tear |
139139+| `--no-color` | `false` | disable colored output |
140140+84141### Ports
851428686-| Protocol | Port | Notes |
8787-| -------- | ---- | ----------------------------------------------------------- |
8888-| gRPC | 443 | default; configurable with `--grpc-port` (requires patched client) |
8989-| HTTP | 8080 | Octo asset API + game web pages (`--http-port` flag) |
143143+| Protocol | Port | Binary | Notes |
144144+| -------- | ---- | ------------- | ----------------------------------------------------------- |
145145+| gRPC | 443 | `lunar-tear` | default; configurable with `--listen` (requires patched client) |
146146+| HTTP | 8080 | `octo-cdn` | Octo asset API + game web pages |
147147+148148+### Game Server Flags (`lunar-tear`)
149149+150150+| Flag | Default | Description |
151151+| --------------- | ----------------- | ---------------------------------------------------- |
152152+| `--listen` | `0.0.0.0:443` | gRPC listen address (host:port) |
153153+| `--public-addr` | `127.0.0.1:443` | externally-reachable host:port advertised to clients |
154154+| `--octo-url` | *(required)* | CDN base URL the client uses for assets (e.g. `http://10.0.2.2:8080`) |
155155+| `--db` | `db/game.db` | SQLite database path |
156156+| `--auth-url` | *(empty)* | Auth server base URL (e.g. `http://localhost:3000`) |
901579191-### Flags
158158+### CDN Flags (`octo-cdn`)
921599393-| Flag | Default | Description |
9494-| ------------- | ------------ | ---------------------------------------------------- |
9595-| `--host` | `127.0.0.1` | hostname/IP given to the client |
9696-| `--http-port` | `8080` | HTTP/Octo server port |
9797-| `--grpc-port` | `443` | gRPC server port (client must be patched to match) |
9898-| `--db` | `db/game.db` | SQLite database path |
160160+| Flag | Default | Description |
161161+| --------------- | ----------------- | -------------------------------------------------------- |
162162+| `--listen` | `0.0.0.0:8080` | local bind address |
163163+| `--public-addr` | `127.0.0.1:8080` | externally-reachable address (used in list.bin rewriting) |
164164+| `--assets-dir` | `.` | root directory containing the `assets/` tree |
99165100166### Docker
101167102102-Migrations run automatically on container start.
168168+Three services are available via Docker Compose: the game server (`lunar-tear`), the CDN (`octo-cdn`), and the auth server (`auth-server`). Migrations run automatically on game server start.
103169104170```bash
105171cd server
106172docker compose up -d
107173```
108174109109-The `db/` directory is mounted as a volume so the database persists across restarts. Make sure `assets/` is populated before starting.
175175+The `db/` directory is mounted as a volume so both `game.db` and `auth.db` persist across restarts. Make sure `assets/` is populated before starting.
176176+177177+Each service has its own image and can be deployed independently:
178178+179179+| Service | Image | Default Port | Notes |
180180+| -------- | --------------------------- | ------------ | ------------------------------ |
181181+| `server` | `kretts/lunar-tear:latest` | 8003 | gRPC game server |
182182+| `cdn` | `kretts/octo-cdn:latest` | 8080 | HTTP asset CDN |
183183+| `auth` | `kretts/auth-server:latest` | 3000 | Account registration and login |
184184+185185+The game server is configured via environment variables in the compose file: `LUNAR_LISTEN` (bind address), `LUNAR_PUBLIC_ADDR` (client-facing address), `LUNAR_OCTO_URL`, and `LUNAR_AUTH_URL`. Auth is optional — if `LUNAR_AUTH_URL` is unset the game server starts without it.
110186111187### Makefile Targets
112188···115191| Target | Description |
116192| -------------- | ------------------------------------------------------- |
117193| `make proto` | Regenerate protobuf stubs |
118118-| `make build` | Build the server binary |
194194+| `make build` | Build the game server binary |
195195+| `make build-cdn` | Build the CDN binary |
196196+| `make build-auth` | Build the auth server binary |
119197| `make build-import` | Build the import-snapshot tool |
198198+| `make build-claim-account` | Build the claim-account tool |
199199+| `make dev` | Run all three services with one command |
120200| `make migrate` | Run goose migrations on `db/game.db` |
121201| `make import` | Import a snapshot (`SNAPSHOT=... UUID=...` required) |
202202+203203+## Claim Account
204204+205205+Transfers an existing game account to the most recently connected client. Looks up a player by their in-game name, assigns the new client's UUID to that account, and deletes the empty account the new client created.
206206+207207+Useful when a new client connects and creates a throwaway account, but you want it to load an existing account instead.
208208+209209+```bash
210210+cd server
211211+go run ./cmd/claim-account --name "PlayerName" --db db/game.db
212212+```
213213+214214+| Flag | Default | Description |
215215+| -------- | ------------ | ---------------------------------------------------- |
216216+| `--name` | *(required)* | In-game player name to claim |
217217+| `--db` | `db/game.db` | SQLite database path |
218218+219219+## Auth Server
220220+221221+A separate HTTP server that handles player account registration and login. The patched client's Facebook login button is redirected to this server, which presents a username/password form. Tokens issued here are validated by the game server to link or recover accounts.
222222+223223+### Run
224224+225225+```bash
226226+cd server
227227+go run ./cmd/auth-server \
228228+ --listen 0.0.0.0:3000 \
229229+ --db db/auth.db
230230+```
231231+232232+The `--secret` flag accepts a hex-encoded HMAC key. If omitted, a random key is generated on startup and printed to the console — pass it back on the next restart to keep existing tokens valid.
233233+234234+### Flags
235235+236236+| Flag | Default | Description |
237237+| ---------- | --------------- | -------------------------------------------- |
238238+| `--listen` | `0.0.0.0:3000` | HTTP listen address (host:port) |
239239+| `--db` | `db/auth.db` | SQLite database path for auth users |
240240+| `--secret` | *(generated)* | Hex-encoded HMAC secret for token signing |
122241123242## ⚠️ Legal Disclaimer
124243
···11+-- +goose Up
22+ALTER TABLE users ADD COLUMN facebook_id INTEGER;
33+CREATE UNIQUE INDEX idx_users_facebook_id ON users(facebook_id) WHERE facebook_id IS NOT NULL;
44+55+-- +goose Down
66+DROP INDEX IF EXISTS idx_users_facebook_id;
77+ALTER TABLE users DROP COLUMN facebook_id;
88+