- Add `felis setup` TUI for initial Owner provisioning and optional Cloudflare edge - Refactor breakGlass to share console TUI model (runConsoleTUI) with setup mode - Session auth respects configured [auth].admin_hostname; fallback to op.console.<root> - Protect linked Yggdrasil admins from Mojang-priority reclaim (spec §B3) - cfsetup: idempotent Access app/policy creation, better 401/403 errors, GET + lookup - Bootstrap: auto-install cloudflared, symlink /etc/felis/felis.toml - Add sequence diagrams for ping-to-join, claim, and link flows
6.0 KiB
6.0 KiB
Felis Sequence Diagrams
This file carries the spec section 28 sequence-diagram deliverables that are not covered by the OpenAPI artifact.
Section 28 #9: Ping To Join To Wake To Ready To Teleport
sequenceDiagram
autonumber
actor Player
participant Velocity as Velocity proxy
participant Registry as Velocity server registry
participant API as felis-api internal face
participant Cluster as MinecraftServer CRD/status
participant Operator as felis operator
participant Backend as Minecraft backend
Player->>Velocity: server-list ping for subdomain.root-domain
Velocity->>Registry: read cached lifecycle view
Registry-->>Velocity: phase-aware MOTD
Velocity-->>Player: ping response (read-only, no wake)
Player->>Velocity: join subdomain.root-domain
Velocity->>Registry: resolve host to server
Registry-->>Velocity: ServerView(name, ready=false)
alt backend already ready and registered
Velocity-->>Player: initial server = backend
Player->>Backend: connect
else backend not ready and lobby configured
Velocity-->>Player: initial server = lobby
Velocity->>API: POST /internal/servers/{name}/wake {mc_uuid}
API->>Cluster: GetServer(name)
API->>API: authorize autostartPolicy, cooldown, running cap
API->>Cluster: SetDesiredState(name, Running)
API-->>Velocity: 202 phase/ready
Velocity->>Velocity: enqueue waiter
Operator->>Cluster: reconcile DesiredState=Running
Operator->>Backend: start pod/service
Backend-->>Operator: RCON-ready / lifecycle ready
Operator-->>Cluster: status.ready=true
loop every waiting tick
Velocity->>API: GET /internal/servers/{name}/status
API->>Cluster: GetServer(name)
API-->>Velocity: ready flag
end
Velocity->>Registry: lookup registered backend
Velocity-->>Player: "ready - moving you in"
Velocity->>Player: Connect request to backend
Player->>Backend: connect
Velocity->>API: POST /internal/servers/{name}/join-event {mc_uuid}
API->>API: RecordJoin; refresh activity and allowlist UUID
API-->>Velocity: 204
else backend not ready and no lobby configured
Velocity-->>Player: disconnect with reconnect-later message
Velocity->>API: POST /internal/servers/{name}/wake {mc_uuid}
API->>Cluster: SetDesiredState(name, Running) if authorized
API-->>Velocity: 202 or branchable error
end
Section 28 #11: Claim Transaction
sequenceDiagram
autonumber
actor Player
participant Panel as Web panel
participant API as felis-api external face
participant Repo as Repo / Postgres
participant Audit as Audit log
Player->>Panel: click Claim on ownerless server
Panel->>API: POST /api/v1/servers/{name}/claim
API->>API: validate server name and principal
API->>Repo: IsLinked(user_id)
alt user has no verified account link
Repo-->>API: false
API-->>Panel: 412 not_linked
else linked
Repo-->>API: true
API->>Repo: QuotaAvailable(user_id)
alt quota exhausted
Repo-->>API: false
API-->>Panel: 403 quota_exceeded
else quota available
Repo-->>API: true
API->>Repo: ClaimServer(name, user_id)
Note over Repo: SELECT EXISTS(server); then atomic UPDATE servers SET owner_id=$2, claimed_at=now() WHERE name=$1 AND owner_id IS NULL AND deleted_at IS NULL
alt server missing
Repo-->>API: ErrNotFound
API-->>Panel: 404 not_found
else zero rows affected
Repo-->>API: claimed=false
API-->>Panel: 409 already_claimed
else one row affected
Repo-->>API: claimed=true
API->>Audit: external claim audit
API-->>Panel: 200 {"claimed":true}
end
end
end
Section 28 #12: Account Binding /link Flow
sequenceDiagram
autonumber
actor Player
participant Game as Minecraft server or Velocity
participant LinkClient as Felis LinkClient
participant APIInternal as felis-api internal face
participant Repo as Repo / Postgres
participant Panel as Web panel
participant APIExternal as felis-api external face
Player->>Game: /link
Game->>Game: read verified online-mode UUID and auth_source
Game->>LinkClient: requestCode(mc_uuid)
LinkClient->>APIInternal: POST /api/v1/internal/account/link/code {mc_uuid, auth_source}
APIInternal->>APIInternal: validate UUID and auth_source; generate 8-symbol code
APIInternal->>Repo: CreateLinkCode(code, mc_uuid, auth_source, expires_at)
Repo-->>APIInternal: inserted account_link_codes row
APIInternal-->>LinkClient: 201 {code, expires_at}
LinkClient-->>Game: LinkCode
Game-->>Player: show one-time code in chat
Player->>Panel: open Account link flow
Panel->>APIExternal: POST /api/v1/account/link/start
APIExternal->>Repo: IsLinked(user_id)
Repo-->>APIExternal: linked status
APIExternal-->>Panel: status and "run /link" instructions
Player->>Panel: submit code
Panel->>APIExternal: POST /api/v1/account/link/verify {code}
APIExternal->>APIExternal: trim and uppercase code
APIExternal->>Repo: VerifyLinkCode(user_id, code, now)
Repo->>Repo: SELECT non-expired code
alt missing or expired code
Repo-->>APIExternal: ErrLinkCodeInvalid
APIExternal-->>Panel: 400 invalid_code
else UUID linked to another user
Repo-->>APIExternal: ErrConflict
APIExternal-->>Panel: 409 already_linked
else valid code
Repo->>Repo: INSERT account_links(user_id, mc_uuid, auth_source) ON CONFLICT (user_id, mc_uuid) DO UPDATE auth_source
Repo->>Repo: DELETE account_link_codes WHERE code=$1
Repo-->>APIExternal: mc_uuid, auth_source
APIExternal->>Repo: Audit account.link
APIExternal-->>Panel: 200 {linked:true, mc_uuid, auth_source}
end