flyemoji 60b0ec97ac feat(invite): let a player bring a friend to the server they're on
/invite <player> posts a chat card to the invitee with a green [Accept] and a
red [Deny] button, and Accept walks them to the server the inviter is standing
on. It is a UX wrapper over `/felis go` and nothing more: the accept runs the
same doGo path on the ACCEPTING player's own verified uuid, so a stored invite
carries a server name and never an identity to act as, and the prompt needs no
unguessable token.

Why it can be this simple: an invite can only name the server its sender is
currently on, so the target is running by construction, and a running felis
server already admits any linked player through <name>.<root-domain> on the
link check alone (WaitingRouter.onServerPreConnect) — no wake, no
autostartPolicy consultation. Hence enqueueFromInvite: a READY backend is
joined directly, and only the not-ready case falls through to the policy-gated
wake path unchanged. Routing an accept through wakeAndWaitLinked would have
asked the API to wake a server that needs no waking, and autostartPolicy
defaults to ownerOnly, so the API would answer 403 and the green button would
do nothing for exactly the people you would invite.

It is not consequence-free, and the inviter is told so at send time rather
than in a comment only we read. Landing on a felis server records the player
in its allowlist (onServerConnected -> join-event -> RecordJoin); on an
autostartPolicy=allowlist server that row is what lets them come back and
START the thing later. The same row they would earn by walking in unaided —
the invite shortened the walk, it did not widen the door — but it outlives the
invite, so accessNotice says so. The wording follows the policy: only under
allowlist does it claim they will be able to start the server themselves,
because under the ownerOnly default (and the empty string the API reports for
an unset field) that row grants no waking and the claim would be a lie.

InviteBook also holds a 30s per-sender cooldown, because the one capability
/invite genuinely adds is "make a chat card appear on any online player", and
unrated that is a way to follow someone around their own chat log. It is
charged in put() rather than at the top of the command, so an invite refused
for an offline name or a player already on the server costs the sender
nothing, and the gate sits after every other validation for the same reason.
The stamp is global per sender on purpose: a per-(sender, invitee) key would
wave through one player papering the whole proxy, which is the thing being
limited.

The card lives in InviteCard as a pure function so the buttons — the whole
point of the feature — can be asserted without a live proxy, and the button
clicks are pinned to the server the card named, so a stale card cannot answer
a newer invite (checked with peek before the invite is spent, so refusing a
superseded card leaves the live one answerable).

Verified: production gradle 8.14 + JDK 21 build; jar carries the plugin classes
and no test classes; InviteBookTest (36 checks) and InviteCardTest (48 checks);
and a live Velocity 3.5.1 that loads the jar, registers /invite <player> and
answers /invite accept <server>, with a malformed subcommand as a negative
control.
2026-07-22 14:32:51 +09:00
2026-07-20 18:53:25 +09:00
2026-07-20 18:53:25 +09:00
2026-07-12 01:42:07 +08:00
2026-07-12 04:37:11 +08:00

Felis

A Kubernetes-driven Minecraft server hosting platform — one command to deploy, automatic lifecycle, backup, and security.
一款 Kubernetes 驱动的 Minecraft 服务器托管平台,一行命令部署,自动管理生命周期与安全。

简体中文 | English

Table of Contents

Features

  • Wake on Join: Servers start automatically when a player connects, and stop when idle — like hibernate for your server.
  • Web Dashboard: Monitor server status, online players, and resource usage from your browser, with backup and restore management.
  • Auto Backup & Restore: Scheduled world backups with one-click rollback from any backup point.
  • World Reaper: Worlds idle for more than 15 days are automatically backed up and removed to free disk space.
  • Multi-core Support: Compatible with Paper, Fabric, Forge, and NeoForge, federated behind a Velocity proxy.
  • Modpack Submission: Players submit custom modpacks; admin approval triggers automatic build and deployment.
  • Passkey Login: Passwordless authentication via fingerprint, face recognition, or hardware security keys.
  • Zero Trust Security: Panel traffic protected by Cloudflare Access; the internal API is never exposed to the internet.

Getting Started

On a prepared Linux host, run:

curl -fsSL https://raw.githubusercontent.com/MliroLirrorsIngenuity/Felis/main/deploy/bootstrap.sh | sudo bash

The script installs K3s, deploys the control plane, and launches a setup wizard. Once done, open your browser at the configured domain.

Build from Source

Felis is built with Go and Node.js:

# Backend (Go 1.26+)
go build -o felis ./cmd/felis

# Frontend (Node.js 22+)
cd panel
npm ci
npm run build

# Docker image
docker build -t felis:custom .

License

The source code is released under the MIT License.

License Notes

  1. Attribution: Any distribution of this project or derivative works must include the original copyright notice and license statement.
  2. Disclaimer: This project is provided "as is", without warranty of any kind.

Acknowledgements

Languages
Go 62.7%
TypeScript 22.5%
Shell 7.4%
Java 7.1%
Dockerfile 0.2%
Other 0.1%