Documentation
¶
Overview ¶
Package setup is the first-run wizard: the only thing this server serves until an operator has told it what it is.
Why there is a token ¶
An operator runs this on a remote machine, so the port is reachable by strangers before an administrator account exists. Without a token, whoever loads the page first becomes the administrator. It is printed to the terminal only, never to a log, it is at least 128 bits from crypto/rand, it is compared in constant time, it is single-use, and a fresh one is minted on every restart so that a token left in an old scrollback is already dead.
Why the database decides whether this runs ¶
The rule is not "no data directory means setup mode". A data directory is a cache of a connection string: it can be deleted, and on a container it can live on a volume that does not survive a restart. If its absence reopened the wizard, anyone who could delete it would be handed the administrator account of a database that already has one.
So a single row in the database marks completion, written in the same transaction that creates the first administrator, and the server asks the database at every startup. See github.com/pacenote-sim/server/internal/db.Store.CompleteSetup.
Index ¶
Constants ¶
const ( TokenAttemptsBurst = 5.0 TokenAttemptsPerMinute = 1.0 )
TokenAttemptsBurst and TokenAttemptsPerMinute rate-limit the one endpoint that compares a secret. Five attempts, then one a minute: enough that an operator who mistypes is not locked out, and far too few for guessing to be worth starting.
const CSRFCookie = "pacenote_setup_csrf"
CSRFCookie is the other half of the double-submit check on every form.
const SessionCookie = "pacenote_setup"
SessionCookie carries the wizard session. It is not a credential to anything but the wizard, and it dies with the process.
const SessionTTL = 2 * time.Hour
SessionTTL is how long a half-finished wizard stays open.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Deps ¶
type Deps struct {
// Log receives the wizard's own lines. The token is never one of them.
Log *slog.Logger
// Version is the build, shown in the page header and recorded against the
// completed setup.
Version string
// Token is the one-time setup token, already printed to the terminal.
Token string
// DataDir is where config.json will be written.
DataDir string
// Base is the configuration the server is running with: the listen
// addresses, and a database URL if one came from the environment.
Base config.Config
// Done is signalled once, after the wizard has committed everything, to
// tell the server to switch to normal mode. It must be buffered, so that
// signalling never blocks the request that is still being written.
Done chan<- struct{}
// Now is the clock, injectable for tests.
Now func() time.Time
// OnAttempt is called after every setup token attempt with "accepted",
// "rejected" or "rate_limited". It is how the metrics port sees that
// someone is guessing.
OnAttempt func(outcome string)
// Probe tests a connection string and diagnoses what is wrong with it.
// nil means [github.com/pacenote-sim/server/internal/db.Probe], which is what the server
// uses; it is a field so that a test can drive the wizard without a
// database, and so that the admin panel's "test connection" button can
// reuse the same seam later.
Probe func(ctx context.Context, url string) error
// AlreadySetUp reports whether that database has already been through
// setup. nil means ask the database, which is the rule: the database
// decides, not the filesystem.
AlreadySetUp func(ctx context.Context, url string) (bool, error)
}
Deps is everything the wizard needs from the rest of the server.
type Wizard ¶
type Wizard struct {
// contains filtered or unexported fields
}
Wizard serves the first-run pages.
func New ¶
New builds the wizard. It returns an error only if the embedded templates do not parse, which is a build fault rather than a runtime one.