Popplio is the API backend for Omniplex (Previously: Infinity Bot List). It
also hosts the Arcadia staff panel API and staff Discord bot, ported into
this repository as the arcadia/ package, and their combined background
tasks and Discord bots run in the same process as the public API.
Licensed under the AGPL-3.0. See LICENSE.
- Go 1.23 or later
- PostgreSQL (connection string set in
config.yaml) - Redis (used for caching)
- A Discord bot application (for the public API's own bot) and, separately, a second Discord bot application for Arcadia's staff bot, since it runs its own gateway connection under its own identity
No platform-specific build tooling is required; Popplio is a pure Go module
with no CGO dependencies (CGO_ENABLED=0 is used in production builds).
Popplio is configured via config.yaml at the repository root, which is
gitignored since it holds real credentials.
config.yaml.sample documents every available key and is regenerated
automatically from the config schema (config/config.go) at the start of
every startup, before config.yaml itself is read — so it always reflects
the current set of fields, defaults, and comments, and stays in sync on its
own whenever the schema changes. On a bare checkout with no config.yaml
yet, running go run . will still (re)write config.yaml.sample before it
fails on the missing config.yaml, so it's always safe to run first just to
get an up-to-date sample.
Copy config.yaml.sample to config.yaml and fill in the blanks. Fields
without a default in the sample (tokens, client secrets, API keys) are
required and Popplio will refuse to start without them (validator tags
enforce this at startup).
Popplio runs as a single deployment, there is no build-time
staging/beta/dev split. Every config key takes one plain value; there's
nothing to keep in sync across environment variants. For local development,
just point config.yaml at your own Discord bot application(s)
(discord_auth.token, arcadia.token) and your own database.
The one thing that still varies per caller rather than per deployment is
the Bug Hunter-only sign-in restriction: it checks the calling frontend's
OAuth redirect_uri (or, for already-authenticated requests, the Origin
header) against the configured production frontend (sites.frontend), so a
non-production frontend pointed at the same shared backend can still be
restricted to Bug Hunters without needing a separate deployment.
go build -o popplio .
./popplio
or during development:
go run .
Popplio listens on the port configured in meta.port.
On Linux and macOS it uses tableflip
for zero-downtime restarts on SIGHUP; on other platforms (including
Windows) it falls back to a plain http.ListenAndServe with a startup
warning, since tableflip's socket handoff is not supported there. Windows
is fine for local development but is not a production target.
Schema changes are versioned goose
migrations under db/migrations/, applied via cmd/migrate:
go run ./cmd/migrate status # see what's applied / pending
go run ./cmd/migrate up # apply every pending migration
go run ./cmd/migrate create add_some_column
or make migrate / make migrate-status. This connects using the same
config.yaml DSN as the rest of Popplio, and applying migrations is a
deliberate manual step — never wired into a deploy, so nothing runs
silently as a side effect of restarting the service. See
db/migrations/README.md.
Everything the module reads or writes goes through
sqlc-generated code (db/queries/*.sql → db/), with
a small number of documented exceptions for queries sqlc's static analysis
can't model (dynamic table/column dispatch, template-built search SQL, and
similar) — each one has a comment at the call site explaining why.
make tests
Runs go test ./... with coverage output to coverage.out.
This is illustrative, not exhaustive — read the actual directory before assuming a package exists.
Live OpenAPI docs are served by the running instance at /docs
(production: https://api.omniplex.gg/docs).
Always fetch Discord user data through dovewing.GetUser, not a raw
Discord API call, it transparently handles gateway cache, Redis, and
in-memory caching, and every other part of the codebase assumes user data
went through it.
