Commit Graph
14 Commits
Author SHA1 Message Date
Lemon-miaow 22d1e834ad fix(felis): 启动失败的服可在面板重试或停止,玩家入服时直接告知启动失败 2026-09-26 22:08:29 +08:00
Lemon-miaow c1025274e1 fix(velocity): 等待队列跟随服的启动进度,自动重试期间一直等并每分钟报进度,放弃或被停止时说明原因 2026-09-26 21:41:01 +08:00
Lemon-miaow df8b09788c fix(velocity): 目标服拒绝玩家时带原因提示并直接放行到大厅,不再在 login 里循环 2026-09-26 21:28:19 +08:00
Lemon-miaow 4afabc390d fix(velocity): 菜单加入和 /felis go 先查实时状态,运行中的服直接进入,内部 wake 对已运行服不再做启动权限校验 2026-09-26 21:21:59 +08:00
Lemon-miaow 3843082153 fix(velocity): 停服的 fallback 名不再注册成后端,唤醒就绪时按轮询到的地址立即注册再传送 2026-09-26 21:18:08 +08:00
Lemon-miaow 5099b2501f feat(velocity): legacy forwarding 列表跟随 CR 的 forwarding=legacy 标签实时更新 (#15) 2026-09-26 08:09:49 +08:00
Lemon-miaow c15eec6c2d test(velocity): 路由核心类接真 velocity-api 自测,修正子域名改名后旧域名仍路由 2026-09-26 00:42:05 +08:00
Lemon-miaow b65c2c00b3 feat(plugins): 插件侧计数器与周期健康日志,Limbo/刷新失败按故障周期升级日志 2026-09-26 00:33:04 +08:00
Lemon-miaow fcf5c305ea feat(velocity): felis-api 调用改走有界线程池、定时任务防重叠,超时可配置,幂等 GET 带抖动重试一次 2026-09-26 00:03:19 +08:00
Lemon-miaow 0fb43232b5 fix(velocity): 保存最近一次服务器列表,控制面不可用时重启代理仍能路由登录 2026-09-25 22:58:53 +08:00
Lemon-miaow d93c1b6913 feat(api): op-login 游戏内审批先展示目标账号、邮箱与发起来源,须输入账户名确认,velocity 显示审批卡片 2026-09-25 17:02:43 +08:00
Lemon-miaow 4648175773 fix(velocity): felis:control 按来源服务器限权并关闭 bungeecord 通道 2026-09-24 13:39:38 +08:00
Lemon-miaow c59b38775b ci(plugins): run the plugin self-tests and build the shipped plugin jars
The three framework-free test mains under plugins/*/test were never run by
anything — not CI, not the plugin builds — and the velocity/paper/limbo jars
were only ever compiled by deploy/bootstrap.sh on a live host. CI gains a
'plugins' job (JDK 21 plus the Gradle 8.14 the plugin Dockerfiles pin) running
plugins/test.sh: the three mains (InviteCardTest's jars fetched from Maven
Central, pinned and digest-checked) and the three production builds.

The first real run surfaced and fixed two untested assumptions: InviteCardTest's
documented javac line omitted examination-api (adventure-api's Component
signatures reference Examinable, so javac needs it too), and limbo's '+' version
default cannot resolve — LOOHP's repository serves no maven-metadata — so the
script resolves the current release off the Limbo CI artifact name (the same
source bootstrap reads) and plugins/README.md stops advertising a bare
'gradle -p plugins/limbo build' that can never work.

Verified in gradle:8.14-jdk21 on the VM: mains OK (32/36/48 checks);
velocity/paper/limbo BUILD SUCCESSFUL.
2026-09-24 00:19:56 +08:00
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