The control panel that actually understands Hytale. Built by BitaceS · AGPL-3.0 · free, forever
TalePanel is a full-stack server management platform built exclusively for Hytale. Worlds, mods and players are first-class objects, not files in a generic file manager — and it scales from one box to a multi-node cluster.
It is currently in public beta (v0.9.0-beta). It runs against real Hytale servers. The list below is the honest split between what ships today and what is still roadmap — no marketing bullets that the code does not hold.
Shipped and working against real Hytale servers:
- Servers — create, start, stop, restart, kill, resource limits, per-server member roles.
- Live console + logs — command input and log tail. Implemented as short-interval polling (logs ~1s, metrics ~5s, status 2–15s), not a socket. It feels live; it is honest about how.
- Multi-node — enroll additional daemon hosts with a one-shot token, servers get scheduled onto nodes.
- Worlds — list, create, switch, delete worlds as real objects.
- Files — browse, edit, upload, download inside the server directory.
- Mods (upload-based) — upload a mod file, it is installed, enabled/disabled and tracked per server. Mod files are detected by filename + SHA-256; where a metadata manifest exists (
plugin.yml,fabric.mod.json) it is parsed. A Hytale-native manifest format does not exist publicly yet — when it does, TalePanel will read it. - Backups — create/restore/download a Zip snapshot of a server. The archive is stored on the same node as the server. No off-site copy yet.
- Players — list, kick, ban, whitelist.
- Auth & security — JWT + refresh, TOTP 2FA (AES-256-GCM at rest), RBAC (owner > admin > moderator > user), audit log, rate limiting, no seed account, no default password.
- Install — one script for panel or daemon, automatic Let's Encrypt TLS, works without a domain via
sslip.io.
Not there yet (roadmap, tracked publicly):
- WebSocket / SSE streaming — replacing the polling loops in console, logs and metrics.
- Off-site backups (S3 / object storage) — MinIO is in the compose file, but no backup is uploaded to it today. Backups live on the node.
- CurseForge mod browser — the code exists but is experimental and off by default: it needs
CURSEFORGE_API_KEYandCURSEFORGE_GAME_ID, and Hytale has no CurseForge game ID yet. Until it does, use the upload-based mod installer, which is fully supported. - Test coverage — thin (a handful of Go tests, no Rust tests). Beta means beta.
- mTLS between panel and daemon, process isolation per server — see
SECURITY.md.
TalePanel is AGPL-3.0 self-hosted. Pick one server for the panel (1 CPU, 2 GB RAM is fine) and one or more servers for the daemon (the hosts that actually run Hytale).
sudo bash <(curl -fsSL https://raw.githubusercontent.com/BitaceS/talepanel/main/scripts/install.sh)A menu lets you pick: Panel, Daemon, Both (same host, dev/home),
Upgrade, or Uninstall. For unattended installs pass --mode panel
or --mode daemon plus the relevant flags — see bash install.sh --help.
No domain? Leave the domain prompt blank (or pass --ip-only) and the
installer auto-builds an sslip.io hostname from your server's public IP.
Let's Encrypt still issues a real TLS cert, so the panel opens over proper
HTTPS — no browser warnings, no self-signed workarounds.
The full operator reference (hardware sizing, DNS, firewall, supported distros, troubleshooting) lives in INSTALL.md.
Installs Docker if missing, clones into /opt/talepanel, generates every
secret via openssl rand -hex 32, creates the admin account you choose,
and starts the stack behind Caddy with automatic Let's Encrypt TLS.
In the panel, go to Nodes → Add Node to get a one-shot enrollment token
(15-minute TTL, single-use). Then run install.sh --mode daemon on the
daemon host with --panel-url and --enrollment-token. The node appears
as online within ~30 seconds.
Offline / air-gapped install: drop
HytaleServer.jarandAssets.zipinto/srv/taledaemon/hytale-bin/on the daemon host. Servers provision in milliseconds via hardlink instead of pulling from the Hytale CDN, which is IPv4-only.
talepanel/
├── apps/
│ └── web/ Nuxt 3 web panel
├── services/
│ ├── api/ Go backend API
│ └── daemon/ Rust node daemon (TaleDaemon)
├── infra/
│ └── docker/ Docker/Compose files
├── services/api/migrations/ PostgreSQL migrations
├── docker-compose.yml
├── .env.example
└── README.md
| Tool | Version | Check |
|---|---|---|
| Docker + Docker Compose | latest | docker -v |
| Node.js | 20+ | node -v |
| Go | 1.22+ | go version |
| Rust + Cargo | 1.79+ | rustc --version |
git clone https://github.com/BitaceS/talepanel.git
cd talepanel
cp .env.example .envEdit .env — at minimum set:
JWT_SECRET=<generate: openssl rand -hex 32>
JWT_REFRESH_SECRET=<generate: openssl rand -hex 32>docker compose up -d postgres redis minio minio-initWait ~10 seconds for Postgres to initialize (runs migrations automatically).
cd services/api
go mod download
go run cmd/server/main.goAPI is now at http://localhost:8080.
Health check: curl http://localhost:8080/api/v1/health
No seed account is shipped. Create the first owner once, using the API container's bundled CLI (requires step 2's Postgres to be up):
docker compose run --rm api tale-cli admin create \
--email you@example.com --username you \
--password 'Correct-Horse-Battery-4!' --non-interactiveThe password must be at least 12 characters with one digit and one non-alphanumeric symbol.
cd apps/web
npm install
npm run devWeb panel: http://localhost:3000
cd services/daemon
cp config.example.toml config.toml
# Edit config.toml: set api_url, node_id, node_token
cargo run# Build and run everything
docker compose up --build
# Just infrastructure + API
docker compose up -d postgres redis minio minio-init api
# Watch API logs
docker compose logs -f apiBase URL: http://localhost:8080/api/v1
# Register
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","username":"myuser","password":"securepass123"}'
# Login (use the owner you bootstrapped via tale-cli — see "Creating the first admin" below)
curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{"email":"you@example.com","password":"your-password"}'
# Use returned access_token in subsequent requests:
curl http://localhost:8080/api/v1/auth/me \
-H "Authorization: Bearer <access_token>"| Method | Route | Description |
|---|---|---|
| POST | /auth/login | Login |
| POST | /auth/register | Register |
| POST | /auth/refresh | Refresh access token |
| POST | /auth/logout | Logout + revoke session |
| GET | /auth/me | Current user |
| GET | /servers | List servers |
| POST | /servers | Create server |
| GET | /servers/:id | Get server |
| POST | /servers/:id/start | Start server |
| POST | /servers/:id/stop | Stop server |
| POST | /servers/:id/restart | Restart server |
| POST | /servers/:id/kill | Kill process |
| GET | /nodes | List nodes (admin) |
| POST | /admin/nodes/enroll | Create one-shot daemon enrollment token (admin) |
| POST | /nodes/enroll | Redeem enrollment token (daemon) |
| GET | /health | Health check |
| GET | /health/ready | Readiness check |
See .env.example for the full list with descriptions.
Required secrets (never commit these):
JWT_SECRET— JWT signing key, min 32 chars, generate withopenssl rand -hex 32JWT_REFRESH_SECRET— Different secret for refresh tokensPOSTGRES_PASSWORD— Database passwordREDIS_PASSWORD— Redis password
| Service | Port | Notes |
|---|---|---|
| API (Go) | 8080 | REST API (the panel polls it; no WebSocket/SSE yet) |
| Web Panel | 3000 | Nuxt 3 dev server |
| PostgreSQL | 5432 | Database |
| Redis | 6379 | Cache + queue |
| MinIO S3 API | 9000 | Object storage — shipped in compose, not yet used by backups |
| MinIO Console | 9001 | MinIO web UI |
| TaleDaemon | 8444 | Node daemon local API |
Adding a daemon host uses a one-shot enrollment token — the daemon self-registers, so you never copy a permanent token by hand.
-
In the panel, open Nodes → Add Node, fill in the name and capacity, and click Create Enrollment Token. The panel shows the token (15-min TTL, single-use) and a ready-to-paste install command.
-
On the daemon host, run the install command from the modal:
sudo bash <(curl -fsSL https://raw.githubusercontent.com/BitaceS/talepanel/main/scripts/install.sh) --mode daemon \ --panel-url https://panel.example.com \ --enrollment-token '<token-from-panel>'
-
The installer redeems the token via
POST /nodes/enroll, receives the node UUID + permanent node token, writes/etc/taledaemon/config.toml, and starts the daemon as a systemd service. Within ~30 seconds the node flips toonlinein the panel.
For fully unattended provisioning the same token can be passed to install.sh --mode daemon — see bash install.sh --help.
No seed account ships with the database. Create the owner after the
first docker compose up:
docker compose run --rm api tale-cli admin create \
--email you@example.com \
--username you \
--password 'Correct-Horse-Battery-4!' \
--non-interactiveOwner/admin passwords must be at least 12 characters, with at least one
digit and one non-alphanumeric symbol. The install.sh script does this
automatically during a fresh panel install.
| Layer | Technology |
|---|---|
| Web Panel | Nuxt 3, Vue 3, TypeScript, TailwindCSS, Pinia |
| API Backend | Go, Gin, pgx/v5, go-redis, golang-jwt |
| Node Daemon | Rust, tokio, axum, reqwest, serde |
| Database | PostgreSQL 16 |
| Cache / Queue | Redis 7 |
| Object Storage | MinIO (S3-compatible) — infrastructure only; backups are node-local today |
# API tests
cd services/api && go test ./...
# Daemon tests
cd services/daemon && cargo test
# Web lint/typecheck
cd apps/web && npm run lint && npm run typecheckCoverage is thin at v0.9 — a few Go test files, no Rust tests yet. Tests are being back-filled per module. PRs that add regression tests for code they touch are the most welcome kind of PR.
- Go:
gofmt+golangci-lint - Rust:
cargo fmt+cargo clippy - TypeScript/Vue: ESLint + Prettier
| Item | Status |
|---|---|
| Auth, RBAC, 2FA, audit log | Shipped |
| Servers, console + logs (polling), files, worlds, players | Shipped |
| Multi-node with enrollment tokens | Shipped |
| Upload-based mod installer | Shipped |
| Node-local backups (create / restore / download) | Shipped |
| WebSocket/SSE streaming instead of polling | Next |
| Off-site backups (S3-compatible object storage) | Next |
| mTLS panel ↔ daemon, per-server process isolation | Next |
| Meaningful test coverage (Go + Rust) | Ongoing |
| CurseForge mod browser | Experimental, disabled — blocked on Hytale having a CurseForge game ID |
| Server templates, webhooks, alert channels | Planned |
Contributions are welcome. Please read CONTRIBUTING.md for workflow and coding-style notes, and be aware that opening a pull request constitutes acceptance of the Contributor License Agreement — the CLA-Assistant bot records your acceptance automatically on your first PR.
Security issues: do not open a public issue — see SECURITY.md.
TalePanel is AGPL-3.0. Free for everyone — hobbyists, communities and hosting providers alike. There is no paid tier, no "enterprise edition", no license you have to buy to run it as a service.
Copyright © 2025–2026 BitaceS (Lukas Diengdoh).
TalePanel — Built for the Hytale community.