Skip to content

mobile-app

Starts the tournament management server (Preact UI + REST/SSE backend). Used on the day of the tournament for drawing pools, recording scores, and pushing real-time updates to spectator screens.

bracket-creator mobile-app [flags]

Refer to the Tournament app guide for a full walkthrough of the UI.

Flags

Flag Short Default Description
--folder -f . Folder containing tournament.md and competitions/. Created at startup if it does not exist.
--port -p 8080 (or $PORT) Port to listen on
--bind -b localhost (or $BIND_ADDRESS) Address to bind to. Use 0.0.0.0 to reach the server from other devices on the LAN.
--lock-password (none) unset (or $LOCK_PASSWORD=true) Switch to locked mode. Requires TOURNAMENT_PASSWORD_HASH. Refer to Authentication.

Environment variables

Variable Used by Description
PORT --port default Initial value for --port if the flag isn't set.
BIND_ADDRESS --bind default Initial value for --bind if the flag isn't set.
LOCK_PASSWORD --lock-password default true enables locked mode without passing the flag.
TOURNAMENT_PASSWORD_HASH locked mode auth Bcrypt hash of the admin password. Required when --lock-password is set. Generate with hash-password.
TOURNAMENT_ADMIN_PASSWORD_HASH nothing Ignored. When it is set, the server logs a line at startup saying so. The admin password above covers every action. Refer to Upgrading from an earlier release.

Authentication

The server has two authentication modes for the admin console; viewer routes are always public.

File mode (default)

The admin password is stored plaintext in tournament-data/tournament.md and compared by exact-string match. Set the password during the in-app Create tournament flow, or edit tournament.md directly.

  • POST /api/tournament/reset needs no password but answers only a request from the machine running the server (a loopback address) that is addressed to a loopback name: localhost, 127.0.0.1 or [::1]. Any other request, from another machine or addressed by another name, gets 403 with the code reset_local_only. If you've forgotten the current password, browse to http://localhost:<port>/reset on the machine running the server to set a new one. In a Docker container run with -p, a browser on the host reaches the server through the container network, not loopback, so the reset is refused there too; edit tournament.md in the mounted data folder instead.
  • GET /api/auth-config returns {"mode": "file", "resetEnabled": true}.

Locked mode (--lock-password)

The on-disk password is ignored. Authentication compares the X-Tournament-Password header against a bcrypt hash from the TOURNAMENT_PASSWORD_HASH environment variable.

Generate the hash, piping the secret in rather than typing it (refer to hash-password for the input rules):

printf '%s' "$MY_ADMIN_SECRET" | bracket-creator hash-password

Start the server with the hash:

TOURNAMENT_PASSWORD_HASH='$2a$10$...' \
  bracket-creator mobile-app --lock-password -f ./tournament-data
  • POST /api/tournament/reset returns 404. The /reset SPA route still serves the embedded page, but renders an "operator-disabled" message instead of the form. The AuthModal also hides the "Forgot password?" link.
  • GET /api/auth-config returns {"mode": "locked", "resetEnabled": false}.
  • The server refuses to start if the env var is empty or malformed (fail-closed; no silent fallback to file mode).
  • Rotation requires restarting with a new hash. The hash is read once at startup.

Recommended for any deployment reachable over the internet. The plaintext-in-file pattern of file mode is intended for trusted-network use only.

Operational caveats

  • POST /api/tournament/reset is limited to the machine running the server, judged on the connection's peer address and on the request's Host, which must be a loopback name. X-Forwarded-For and similar headers are not consulted. The Host rule means a web page whose domain was rebound to 127.0.0.1 in a browser on the server machine is refused. Behind a reverse proxy on the same machine, every request arrives from the proxy's loopback address, so the reset is reachable by whoever the proxy exposes it to; block that path at the proxy or run with --lock-password (which 404s POST /api/tournament/reset).
  • Locked mode has no bcrypt brute-force protection. The server runs a full bcrypt comparison (bcrypt.DefaultCost approx 50-100 ms) on every X-Tournament-Password header, but does not throttle repeated failed attempts. For internet-exposed locked-mode deployments, add a reverse proxy that rate-limits authenticated routes (for example, nginx's limit_req).
  • Mode switching preserves the stored password. Switching from file mode to locked mode does NOT erase tournament.md's password field; auth stops consulting it. A later switch back to file mode resurrects the original password. This is a deliberate rollback feature, but anyone with filesystem access can still read the value. To fully retire a file-mode credential before going locked, POST /api/tournament/reset it to a one-time throwaway first.

Competition configuration

A competition's pool and knockout settings are stored in its competitions/<id>/config.md YAML front matter, and are exposed by the API in JSON. Key names differ between the two:

config.md key JSON API key Values Default Description
extra_qualifiers extraQualifiers "", "larger-pools", "fill-bracket" "" (standard) How many finishers each pool sends to the knockout. Only meaningful for a Mixed competition with a minimum-players-per-pool size; requires pool_winners: 1 (poolWinners: 1 in JSON) for either non-default value. Refer to How many qualify from each pool. When updating a competition through the API, leaving the JSON key out keeps whatever is already stored; send the key with an empty string to set a competition back to standard.

Examples

Local LAN, file mode, default port:

bracket-creator mobile-app -f ./tournament-data

Bind to all interfaces on a custom port:

bracket-creator mobile-app -f ./tournament-data -b 0.0.0.0 -p 8082

Locked mode for a public deployment, with the hash generated as shown under Locked mode:

HASH=$(printf '%s' "$MY_ADMIN_SECRET" | bracket-creator hash-password)
TOURNAMENT_PASSWORD_HASH="$HASH" \
  bracket-creator mobile-app --lock-password -f /var/lib/bracket-creator -b 0.0.0.0