{"id":49235621,"url":"https://github.com/haydenbleasel/ghost","last_synced_at":"2026-04-24T15:33:26.460Z","repository":{"id":284892848,"uuid":"945562810","full_name":"haydenbleasel/ghost","owner":"haydenbleasel","description":"Simple, reliable dedicated game servers.","archived":false,"fork":false,"pushed_at":"2026-04-19T02:21:34.000Z","size":2563,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-19T04:24:15.790Z","etag":null,"topics":["dedicated-server","gaming","minecraft","palworld","valheim"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/haydenbleasel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"license.md","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-09T18:00:08.000Z","updated_at":"2026-04-19T02:21:44.000Z","dependencies_parsed_at":"2025-04-24T02:38:35.589Z","dependency_job_id":"ad783bf6-170d-4c7d-9c16-ccf4ec2f3b94","html_url":"https://github.com/haydenbleasel/ghost","commit_stats":null,"previous_names":["haydenbleasel/ultrabeam","haydenbleasel/ghost"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/haydenbleasel/ghost","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haydenbleasel%2Fghost","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haydenbleasel%2Fghost/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haydenbleasel%2Fghost/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haydenbleasel%2Fghost/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/haydenbleasel","download_url":"https://codeload.github.com/haydenbleasel/ghost/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haydenbleasel%2Fghost/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32228995,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T13:21:15.438Z","status":"ssl_error","status_checked_at":"2026-04-24T13:21:15.005Z","response_time":64,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["dedicated-server","gaming","minecraft","palworld","valheim"],"created_at":"2026-04-24T15:33:25.192Z","updated_at":"2026-04-24T15:33:26.445Z","avatar_url":"https://github.com/haydenbleasel.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ghost\n\nOpen-source control plane for dedicated game servers. Next.js on Vercel, game VMs on Hetzner Cloud, coordinated via a tiny `ghost-agent` that runs on each VM.\n\nMVP supports Minecraft. Clean provisioning activity logs, live console streaming, start/stop/restart/delete — no SSH, no Kubernetes, no Pterodactyl.\n\n## Architecture\n\n```\nBrowser ──SSE──▶ Next.js (Vercel) ──long-poll──▶ ghost-agent (Hetzner VM)\n                     │                                 │\n                     ├── Prisma → Neon Postgres        └── Docker → game container\n                     ├── Upstash Redis (event seq)\n                     └── Workflow SDK (durable steps)\n```\n\n- **Vercel Workflow SDK** (`workflow`) runs the durable provisioning + teardown workflows. Steps emit structured activity events that the UI streams via SSE.\n- **Hetzner VMs** are created from a prebaked snapshot (Docker + `ghost-agent` preinstalled). `cloud-init` only writes a per-server bootstrap token.\n- **Agent protocol** — Ed25519-signed requests, long-poll for commands, batched event/log POSTs. The agent never accepts inbound connections.\n- **Auth** — Better Auth (email + password to start) on the same Postgres.\n\n## Layout\n\n```\napp/                  Next.js App Router — UI, API, Better Auth\nlib/                  server-side libs (db, redis, hetzner, agent helpers, workflows)\nprotocol/             Zod schemas + signing canonicalization shared with the agent\nagent/                Bun-built TypeScript agent (compiled to a Linux binary)\nprisma/               schema + migrations\nscripts/              gold-image build script, systemd unit, cloud-init example\ngames/                per-game compose generators (Minecraft only enabled in MVP)\n```\n\n## Environment variables\n\n```bash\n# Postgres (Neon)\nDATABASE_URL=\nDIRECT_URL=               # pooled vs direct for Prisma migrate\n\n# Vercel KV / Upstash Redis (monotonic event sequence + nonce dedupe)\nKV_REST_API_URL=\nKV_REST_API_TOKEN=\n\n# Hetzner Cloud\nHETZNER_TOKEN=            # read/write token from Hetzner console\nHETZNER_IMAGE_ID=         # snapshot id produced by scripts/build-image.sh\nHETZNER_LOCATION=nbg1\nHETZNER_SERVER_TYPE=cx23\n\n# Secrets (32+ char random strings)\nBOOTSTRAP_JWT_SECRET=\nBETTER_AUTH_SECRET=\n\n# URLs\nBETTER_AUTH_URL=https://your-app.vercel.app\nNEXT_PUBLIC_APP_URL=https://your-app.vercel.app\n\n# Optional\nNEXT_PUBLIC_SENTRY_DSN=\nSENTRY_ORG=\nSENTRY_PROJECT=\n```\n\n## One-time setup\n\n1. **Neon** — create a Postgres database, set `DATABASE_URL`.\n2. **Vercel KV / Upstash** — create a Redis database, set `KV_REST_API_URL` + `KV_REST_API_TOKEN`.\n3. **Prisma** — `pnpm migrate` (runs `prisma format \u0026\u0026 prisma generate \u0026\u0026 prisma db push`).\n4. **Build the gold image** — see [Building the gold image](#building-the-gold-image) below.\n5. **Deploy** the Next.js app to Vercel with the env vars above.\n\n## Building the gold image\n\nOne-time ~10 min process on Hetzner. Produces a snapshot with Ubuntu + Docker + `ghost-agent` + the game's Docker image pre-pulled, so first provision is instant.\n\n### Prereqs\n\n```bash\nbrew install hcloud jq\nhcloud ssh-key create --name laptop --public-key-from-file ~/.ssh/id_ed25519.pub\n```\n\nIn `.env.local`:\n\n```bash\nHETZNER_TOKEN=            # required\nHETZNER_SSH_KEY=laptop    # optional, default laptop (hcloud ssh-key name)\n```\n\n### Build\n\n```bash\npnpm snapshot\n```\n\nCompiles the agent, creates a throwaway builder VM, runs `scripts/build-image.sh` on it, snapshots it, deletes the VM, and writes the new `HETZNER_IMAGE_ID` back to `.env.local`. A `trap` deletes the builder even if the run fails. Takes ~5 min.\n\nThe previous snapshot (if any) is **not** auto-deleted — the script prints the old ID and the `hcloud image delete` command.\n\n### Adding more games later\n\nAdd `docker pull \u003cimage\u003e` lines to `scripts/build-image.sh`, then rerun `pnpm snapshot`.\n\n## Lifecycle\n\n- **Create** — `POST /api/servers { name, game: 'minecraft' }` runs the `provisionServer` workflow: mint bootstrap JWT → Hetzner create → await boot → await agent enroll → push `UPDATE_CONFIG` compose → await `healthy` → mark `ready`.\n- **Start/Stop/Restart** — `POST /api/servers/:id/commands` enqueues a command. The agent picks it up within ~1s via long-poll, executes `docker compose up/stop/restart`, and acks.\n- **Delete** — `DELETE /api/servers/:id` flips `desiredState=deleted` and starts the `teardownServer` workflow: send DELETE to agent → delete Hetzner server → mark deleted.\n- **Activity stream** — `GET /api/servers/:id/activity/stream` (SSE). Cursor via `?cursor=\u003cseq\u003e`, auto-close at 270s so client can reconnect cleanly.\n- **Logs stream** — `GET /api/servers/:id/logs/stream` (SSE). Ring-buffered in Postgres (prune via cron).\n\n## Agent protocol\n\n- `POST /api/agent/enroll` — exchanges a one-shot bootstrap JWT (minted by the workflow, written to `/etc/ghost/bootstrap.json` by cloud-init) for a persistent Ed25519 public key registration.\n- All subsequent agent calls carry `X-Ghost-{Agent,Ts,Nonce,Sig}` headers. Sig is `ed25519(method || path || ts || nonce || body)`. Timestamp skew tolerance: 60s. Nonce TTL: 5 min.\n- `GET /api/agent/commands?wait=25` — long-poll, up to 25s DB polling interval ~750ms. Returns `{commands: []}` or 204.\n- `POST /api/agent/commands/:id/ack`, `POST /api/agent/events`, `POST /api/agent/heartbeat`, `POST /api/agent/rotate-key`.\n\n## Scripts\n\n- `pnpm dev` — Next dev server with turbopack\n- `pnpm build` — prisma generate + next build\n- `pnpm db:push` / `db:migrate` / `db:studio`\n- `pnpm agent:dev` — run agent with Bun for local testing\n- `pnpm agent:build` — cross-compile Linux binary to `dist/ghost-agent`\n\n## License\n\nMIT — see `license.md`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaydenbleasel%2Fghost","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhaydenbleasel%2Fghost","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaydenbleasel%2Fghost/lists"}