Skip to content

Let in approved players only, and close the server for maintenance to all but a list, under another name in the server list, in contract 6 - #60

Merged
ImBaphomettt merged 11 commits into
mainfrom
allowlist-maintenance
Nov 11, 2026
Merged

ImBaphomettt merged 11 commits into
mainfrom
allowlist-maintenance

Conversation

@ImBaphomettt

@ImBaphomettt ImBaphomettt commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

One commit, then six merges of main: #56; #57, after which ALLOWLIST_MANAGE moved from bit 26 to bit 27; #59; #61, after which it moved to bit 29; #58, after which it moved to bit 32 and contract 6 follows the kick's 5; #62, which took bit 32 and migration 0019 first, after which it moved to bit 33 and the migrations to 0020 and 0021. In between, 500603f opened the topics kept in a watch through one helper, so that open() in live/topics.rs stayed within clippy's 100 lines; #61 brought a helper of its own for the same, followed, which the merge keeps in its place. Last, 1dc1b89 writes every message the owner writes for players in the editor #58 brought, filled by one helper, 0cc30bc stops the bans' refusal message from calling itself, with the kick's, all the text players read, and 2ff56fb gives Maintenance a row of its own in the status card on phones, where it no longer fits beside Restart, Stop and the kick's menu. Who gets in is now decided at connection by one service: a ban first, then maintenance mode, then the allowlist. The allowlist lets in only the players the staff approved, and a refused player reads the owner's message with the ID of their request. Maintenance mode closes the server to all but a list of players, holds through restarts of FXServer and of Observer, and gives the server another name in FiveM's server list meanwhile. The bridge's contract goes to 6.

What changed

Admission (021f3a1)

  • observer-app (admission): one service answers each player the bridge holds at playerConnecting: a ban first, then maintenance mode, then the allowlist. The bans' task no longer answers connecting; it still checks players again once they joined. In bans.rs, only that path changed (Seen and refusal are now pub(crate)).
  • A database that cannot tell lets the player in, as with bans: a failing disk never locks everyone out.
  • Live topic server.admission: { allowlist: { enabled, waiting, approved }, maintenance: { on, since, by, passes } }, for DASHBOARD_VIEW, PLAYERS_VIEW or SERVER_CONTROL.

Allowlist

  • observer-domain (allowlist): the settings (on or off, and the refusal message, with {id} and {name}), request IDs such as 7KQ-2XP from the bans' alphabet, the search. Migration 0020_allowlist.sql: approvals, and requests, one per licence, the 2,000 tried last kept.
  • A refused player makes a request, or counts one more try on theirs under the same ID; a denied player who comes back makes a new request, under a new ID. A player who shows no licence is refused and makes none. A licence can be approved ahead of a first visit, with a note.
  • observer-web: GET /servers/default/allowlist/requests and GET …/approved (PLAYERS_VIEW); POST …/requests/{code}/approve, DELETE …/requests/{code}, POST …/requests/deny, POST …/approved and DELETE …/approved/{id} (ALLOWLIST_MANAGE, permission bit 33, sensitive, implies PLAYERS_VIEW); GET and PUT …/allowlist/settings (SETTINGS_VIEW, SETTINGS_MANAGE with the password again). Audit actions allowlist_request_approved, allowlist_request_denied, allowlist_requests_denied, allowlist_licence_approved, allowlist_approval_removed, allowlist_settings_changed. Error codes allowlist_request_not_found, approval_not_found, already_approved, invalid_approval, invalid_allowlist_settings.
  • Panel: an Allowlist page under Players (the requests to approve or deny, the approved players, one search over both, Approve a licence), the count of waiting requests on its rail entry while the allowlist is on, and Settings → Allowlist.

Maintenance mode

  • observer-domain (maintenance): the message, with {name}. Whether it is on, since when and by whom live in settings, so it holds through restarts of FXServer and of Observer. Migration 0021_maintenance.sql: the players who still get in, 200 at most, by licence.
  • A player on the list gets in whether the allowlist has them or not; a ban still refuses them.
  • observer-web: GET and PUT /servers/default/maintenance (SERVER_CONTROL: the message players will read, and the switch); GET and PUT …/maintenance/settings, GET and POST …/maintenance/passes, DELETE …/maintenance/passes/{id} (SETTINGS_VIEW, SETTINGS_MANAGE with the password again). Audit actions maintenance_started, maintenance_ended, maintenance_pass_added, maintenance_pass_removed, maintenance_settings_changed. Error codes pass_not_found, pass_exists, invalid_pass, too_many_passes, invalid_maintenance_settings.
  • Panel: Maintenance beside Restart and Stop; a dialog that shows what a refused player will read, how many still get in and the name the server will take in the list; a banner under the status card on every page while it is on (since when, by whom, how many still get in, End maintenance); Settings → Maintenance mode: the message, the list (typing a name lists the players Observer knows, those online first; a pasted licence takes a note) and the name in the server list.

The server's name in FiveM's server list

  • observer-fxserver: BridgeAction::ServerName. While maintenance is on, the bridge sets sv_hostname, and sv_projectName when the server has one, to the name the owner chose, keeps what they held in three server convars of its own (observer_name_kept, observer_name_hostname, observer_name_project), and gives both back when it ends. The name travels hex-encoded in observer_cmd, as every bridge action: no text the owner typed reaches FXServer's console. After a start during maintenance, it is sent again once the server is online, the cfg having given the server its own names.
  • observer-fxserver (supervisor): /info.json is read once a run; the name the bridge gave for maintenance is not taken for the server's own name in the panel.
  • 40 characters at most, what the list keeps of a name.

Contract 6 (resource/)

  • observer:playerRefused says what refused the player, in cause: ban, maintenance or allowlist. ban, reason, endsAt and by come with a ban only, request with the allowlist.
  • New event observer:maintenance (on, at, by), raised at each switch and again at each start of FXServer while maintenance is on.
  • CONTRACT.md, observer_example and the README follow.

Shared pieces of the panel

  • settings/MessageEditor.tsx, which Kick a player or everyone, and raise announcements and direct messages for the server's resources, in contract 5 #58 brought for the bans' and the kick's messages, now writes the allowlist's and maintenance's too (1dc1b89). It takes what goes between the placeholders and the preview, the allowlist's warning when its message lacks {id}, and shows the preview with components/MessagePreview.tsx, which the maintenance dialog shows too. This branch's own field for the same, MessageField, is gone.
  • lib/fill.ts fills every owner message, in place of the bans' fillMessage and the kick's fillKickMessage.
  • Notice takes an icon.

Notes for review

Verified

  • cargo xtask check in one run on 2ff56fb: fmt, icons, clippy with and without test-catalog, every test, bindings, then the panel (format, lint, typecheck, 950 tests, build). It also passed in one run on the merge 7e61e7a and on 0cc30bc, before Keep ban templates for the ban dialog, and edit an active ban under a permission of its own #62 (930 panel tests), and the panel's checks passed on the merge b7b8380 alone.
  • cargo xtask e2e's flows on 7e61e7a and on 0cc30bc: 12 passed each time, Kick a player or everyone, and raise announcements and direct messages for the server's resources, in contract 5 #58's kick flow among them, the 2 Linux ones skipped; 2ff56fb changes nothing above 760 px, the flows' width being 1440. Run with the Chromium 1248 already on this host instead of the one Playwright 1.65 downloads.
  • By hand on 0cc30bc, in the lab at 1440 px: Settings → Bans and kicks and Allowlist in the dark theme, Maintenance mode in the light one. Each message editor inserts its placeholders at the caret and fills its preview, and the allowlist's warning for a message without {id} shows between the two. No error in the console.
  • By hand on 7e61e7a, in a lab set up afresh, its migrations 0019 to 0021 applied in order: Settings → Bans and kicks with two ban templates, at 1440 px in the dark theme and at 375 px in the light one, which showed the status card overflowing. With 2ff56fb's panel, its buttons measured within it at 375 px (317 px of 317), and on one row at 1440.
  • On the earlier merges, timing tests this branch does not touch failed while other sessions loaded the host, and passed when run again: a_start_is_followed_through_the_commands_of_its_cfg (a resource measured at 1798 ms against its 1800; its binary passed twice on main alone, then twice on this branch once the load fell), a_restart_never_waits_past_its_limit_and_the_dump_still_ends (3.9 s against its 3), markup_that_never_closes_is_read_quickly (7.5 s against its 5), and a_check_that_reaches_nothing_says_so_rather_than_fail, which expects nothing on port 41977 while a release server answered there. On 2ff56fb, one panel test failed in one of three runs of npm test while other sessions loaded the host, its output not kept; the next two runs passed 950 of 950, and so did cargo xtask check's.
  • Against the fake FXServer, in crates/fake-fxserver/tests/runtime.rs: a player refused by the allowlist gets in once approved; maintenance refuses all but its list and is raised again at the next start; the server carries its maintenance name in /info.json and /dynamic.json, again after a restart, and gets its own back; a banned player is refused at connection through the admission, and Kick a player or everyone, and raise announcements and direct messages for the server's resources, in contract 5 #58's kicks, announcements, messages and txAdmin events pass beside it.
  • By hand, on 021f3a1, an Observer with the fake FXServer, in both themes at 1440 and 375 px: refusals with the request ID, the same ID on each try, entry after approval; maintenance turned on and off from the panel, the banner, the list, the audit entries; the names during maintenance, after a restart and after it ends; the rail keeping the server's own name.
  • The bridge's Lua parses (lua-language-server --check) after the merge with Kick a player or everyone, and raise announcements and direct messages for the server's resources, in contract 5 #58, which runs none of it.
  • GitHub Actions refuses the jobs at the moment (the Panel job fails without running a step, the others are skipped, as on main): everything above ran locally.

Not verified

  • The bridge's Lua on a real FXServer: deferrals.done(message) for the three refusals, observer:maintenance, SetConvar and SetConvarServerInfo on the names.
  • What FiveM's server list shows today, and how soon: its UI has not been public since 2026-05-28, and how fast the list takes a heartbeat is Cfx.re's.
  • Linux: no platform code changed, and the two Linux e2e flows were skipped.

…nce to all but a list, under another name in the server list
# Conflicts:
#	crates/fake-fxserver/tests/runtime.rs
# Conflicts:
#	crates/observer-domain/src/audit.rs
#	crates/observer-domain/src/live.rs
#	crates/observer-domain/src/permissions.rs
#	crates/observer-web/src/error.rs
#	crates/observer-web/src/live/topics.rs
#	web/src/api/generated/AuditAction.ts
#	web/src/api/generated/ErrorCode.ts
#	web/src/api/generated/Permission.ts
#	web/src/api/generated/Topic.ts
#	web/src/api/generated/TopicEvent.ts
#	web/src/api/generated/TopicSnapshot.ts
#	web/src/i18n/en.ts
#	web/src/live/messages.ts
# Conflicts:
#	crates/fake-fxserver/tests/runtime.rs
#	crates/observer-domain/src/audit.rs
#	crates/observer-domain/src/live.rs
#	crates/observer-domain/src/permissions.rs
#	crates/observer-fxserver/src/bridge.rs
#	crates/observer-web/src/lib.rs
#	crates/observer-web/src/live/topics.rs
#	crates/observer-web/src/tests.rs
#	crates/observer/src/run.rs
#	resource/observer/server.lua
#	web/src/api/generated/AuditAction.ts
#	web/src/api/generated/ErrorCode.ts
#	web/src/api/generated/Permission.ts
#	web/src/api/generated/Topic.ts
#	web/src/api/generated/TopicEvent.ts
#	web/src/api/generated/TopicSnapshot.ts
#	web/src/i18n/en.ts
#	web/src/live/messages.ts
# Conflicts:
#	README.md
#	crates/fake-fxserver/src/main.rs
#	crates/fake-fxserver/tests/runtime.rs
#	crates/observer-app/src/bans.rs
#	crates/observer-domain/src/audit.rs
#	crates/observer-domain/src/bans.rs
#	crates/observer-domain/src/error.rs
#	crates/observer-domain/src/permissions.rs
#	crates/observer-fxserver/src/bridge.rs
#	crates/observer-fxserver/src/stdin.rs
#	crates/observer-web/src/api.rs
#	crates/observer-web/src/error.rs
#	crates/observer-web/src/lib.rs
#	crates/observer-web/src/tests.rs
#	crates/observer/src/run.rs
#	resource/CONTRACT.md
#	resource/observer/server.lua
#	resource/observer_example/server.lua
#	web/src/api/generated/AuditAction.ts
#	web/src/api/generated/ErrorCode.ts
#	web/src/api/generated/Permission.ts
#	web/src/i18n/en.ts
#	web/src/live/messages.ts
#	web/src/settings/Bans.tsx
#	web/src/settings/catalog.ts
# Conflicts:
#	crates/observer-domain/src/audit.rs
#	crates/observer-domain/src/permissions.rs
#	crates/observer-store/src/migrations.rs
#	web/src/api/generated/AuditAction.ts
#	web/src/api/generated/Permission.ts
#	web/src/audit/details.ts
#	web/src/i18n/en.ts
#	web/src/settings/Bans.tsx
@ImBaphomettt
ImBaphomettt merged commit 70c9f6c into main Nov 11, 2026
2 of 3 checks passed
@ImBaphomettt
ImBaphomettt deleted the allowlist-maintenance branch November 11, 2026 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant