Documentation
¶
Overview ¶
Package config is the server's configuration: the small file beside the binary, the environment variables that override it, and the organisation settings that live in the database.
The split matters and it is deliberate. The file holds only what a machine needs in order to find its data and open its ports — a connection string and two listen addresses. Everything an operator chose in the wizard, and everything drivers see, lives in the database, because the database is the thing that survives. A data directory can be deleted, or sit on a container volume that does not outlive a restart, and when that happens the server must still come up knowing who it is.
That is also why nothing in this package decides whether setup has run. The presence of a file proves nothing. See internal/db for the row that does.
Index ¶
- Constants
- Variables
- func AutocertDir(dir string) string
- func ClientBinaryIn(dir string) string
- func ClientBuildsDir(dir string) string
- func DefaultDir() string
- func DefaultLimits() wire.Limits
- func EnsureDir(dir string) error
- func PluginsDir(dir string) string
- func ValidateAccent(accent string) error
- func ValidateHost(host string) error
- func ValidateLimits(l wire.Limits) error
- func ValidateRetention(r Retention) error
- func ValidateURL(kind, raw string) error
- type Config
- type Retention
- type Settings
- type TLSMode
Constants ¶
const ( EnvDataDir = "PACENOTE_DATA_DIR" EnvDatabaseURL = "PACENOTE_DATABASE_URL" EnvListen = "PACENOTE_LISTEN" EnvMetricsListen = "PACENOTE_METRICS_LISTEN" EnvSecretKey = "PACENOTE_SECRET_KEY" EnvClientBinary = "PACENOTE_CLIENT_BINARY" )
The environment variables that override the file. They exist for anyone automating a deployment, and EnvDatabaseURL is the one that matters: it is how a container with an ephemeral volume starts without its file.
const ( APIBase = "/api/v1" DiscoveryPath = "/.well-known/sim-telemetry.json" )
APIBase is where version 1 is mounted, and DiscoveryPath the one document a client fetches before anything else. They are constants here rather than in the API package because the discovery document names the first and is served from the second, and two spellings of either would be a client that cannot find the server it was pointed at.
const ( // MinTracePoints is the fewest samples a lap trace may be held to. MinTracePoints = 10 // MaxTracePoints is the most, which is storage on every lap ever driven. MaxTracePoints = 10_000 // MinLapsPerRequest is the fewest laps one upload may carry. MinLapsPerRequest = 1 // MaxLapsPerRequest is the most, which bounds one offline queue drain. MaxLapsPerRequest = 1_000 // MinIntervalMs is the shortest published interval, ten a second. MinIntervalMs = 100 // MaxIntervalMs is the longest, ten minutes. MaxIntervalMs = 600_000 // MinBodyBytes is the smallest body cap that still fits a trace. MinBodyBytes = 64 << 10 // MaxBodyBytes is the largest, which is a memory limit as much as a size. MaxBodyBytes = 64 << 20 )
The bounds on the limits an operator may publish. They are wide — this is a self-hosted server and the operator is the one paying for the traffic — but they are not absent: a zero interval is a client told to send continuously, and a body cap of a gigabyte is a memory limit written in a text field.
const ClientDirName = "client"
ClientDirName is the folder inside the data directory that holds the prebuilt Windows client. It is named in the empty state on the build page, so it is a constant rather than a string typed twice.
const ClientFileName = "pacenote-telemetry.exe"
ClientFileName is what the prebuilt Windows client is called. It is the name the client's own build produces, so an operator copies the file across without renaming it.
const DefaultListen = ":8080"
DefaultListen is the address the public server binds when nothing says otherwise: every interface, port 8080, because 80 needs a privilege an operator should not have to grant to try something out.
const DefaultMetricsListen = "127.0.0.1:9090"
DefaultMetricsListen is the private port. It is loopback and stays loopback: Config.Validate refuses anything else.
const DirMode os.FileMode = 0o700
DirMode is the data directory's permission: the operator, and nobody else. The directory holds a connection string, so a group-readable bit on it is a credential leak on a shared machine.
const DirName = "pacenote-data"
DirName is the data directory created beside the binary on first run.
const FileMode os.FileMode = 0o600
FileMode is the configuration file's permission, for the same reason.
const FileName = "config.json"
FileName is the configuration file inside the data directory.
const KeepTracesForever = 0
KeepTracesForever is the Retention.TraceMonths that deletes nothing. It is zero so that an installation that has never opened the data page keeps everything, which is the only safe thing for a default to do.
const MaxRetentionMonths = 120
MaxRetentionMonths is the longest retention an operator may set. Ten years is past the point where the setting means anything — a team that wants its traces for ever says so with KeepTracesForever rather than with a number nobody will be around to see expire.
Variables ¶
var ErrNotConfigured = errors.New("config: no configuration file")
ErrNotConfigured is returned by Load when the data directory holds no configuration file. It is not a failure — on a first run it is the expected answer — so callers test for it with errors.Is rather than treating it as broken.
Functions ¶
func AutocertDir ¶
AutocertDir is where an automatic certificate and its account key are cached, inside the data directory. Losing it costs a new certificate, not an outage, but Let's Encrypt rate-limits issuance, so it is worth keeping.
func ClientBinaryIn ¶
ClientBinaryIn is where this server looks for the prebuilt client when the configuration does not say: inside the data directory, beside everything else an operator put there.
func ClientBuildsDir ¶
ClientBuildsDir is where the clients an operator builds are kept. They are in the data directory rather than in a temporary one because a download link an operator sent to their team has to keep working after a restart.
func DefaultDir ¶
func DefaultDir() string
DefaultDir is the data directory beside the running binary, overridden by EnvDataDir. Beside the binary rather than in a home directory because the operator moves one file to a server and expects its data to be where they put it.
func DefaultLimits ¶
DefaultLimits are the wire limits served in discovery and enforced by the server, straight from API-V1.md. One set of constants, so the client's intervals and the server's limiter cannot drift apart.
func EnsureDir ¶
EnsureDir creates the data directory at DirMode if it is missing, and tightens the permissions of one that already exists but is readable by anyone else.
func PluginsDir ¶
PluginsDir is where plugins live, inside the data directory: one directory per plugin, each holding a manifest and a binary.
It is under the data directory rather than beside the binary because the data directory is the thing an operator backs up, moves between machines and mounts as a volume, and a plugin that did not travel with it would be a server that came up missing an integration nobody removed.
func ValidateAccent ¶
ValidateAccent checks the accent colour. Empty is allowed and means the client keeps its own; anything else is a CSS hex colour, because that is what the client puts straight into a stylesheet.
func ValidateHost ¶
ValidateHost refuses anything that is not a bare host name.
It is strict on purpose. This name goes into the pairing link a driver opens, into the certificate when the server gets its own, and into every URL the client is told to use — so a scheme, a port or a path pasted in here is a mistake that surfaces later, somewhere else, as something that looks unrelated.
func ValidateLimits ¶
ValidateLimits reports the first limit that is out of range, phrased for the operator who typed it.
These are the numbers discovery publishes and the rate limiter enforces, so they are checked once, here, and both sides read the result. A client that obeys the document must never be refused by the limiter, which is only true while there is one set of numbers rather than two.
func ValidateRetention ¶
ValidateRetention reports what is wrong with a retention setting, phrased for the operator who typed it.
func ValidateURL ¶
ValidateURL checks an absolute URL an operator types — the logo. Empty is allowed and means none; anything else has to be http or https, because it is fetched.
Types ¶
type Config ¶
type Config struct {
// DatabaseURL is a PostgreSQL connection string, in either the URL or the
// keyword/value spelling. It is the one secret in the file, which is why
// the file is 0600 inside a 0700 directory.
DatabaseURL string `json:"database_url"`
// Listen is the public address, "host:port" or ":port".
Listen string `json:"listen"`
// MetricsListen is the private address for metrics and pprof. It must
// resolve to a loopback interface.
MetricsListen string `json:"metrics_listen"`
// SecretKey is the base64 data key that encrypts the operator's own API
// keys before they are stored. It lives here rather than in the database so
// that a database dump holds ciphertext and nothing that opens it. Losing
// it costs the stored API key and nothing else: the server says the key is
// unreadable and asks for it again.
SecretKey string `json:"secret_key,omitempty"`
// ClientBinary is the prebuilt Windows client the build page stamps copies
// of. It is a path rather than a file inside this binary because the
// client is another repository's build artefact, an order of magnitude
// larger than this server, and an operator upgrades one without the other:
// dropping a newer client into the data directory is how they do it.
//
// Empty means [ClientBinaryIn] of the data directory, which is where the
// community edition's zip puts it.
ClientBinary string `json:"client_binary,omitempty"`
}
Config is the machine-local wiring: where the data is and which ports to open. It is the whole contents of config.json.
func Default ¶
func Default() Config
Default is the configuration of a server that has been told nothing.
func Load ¶
Load reads config.json from dir and applies the environment overrides on top.
A missing file is ErrNotConfigured and the environment is still applied, so a caller can tell "no file, but a database URL in the environment" from "no file and nothing to go on" by checking the returned Config.
func (Config) ClientBinaryPath ¶
ClientBinaryPath is the prebuilt client this configuration names, or the data directory's own when it names none.
type Retention ¶
type Retention struct {
// TraceMonths is how many months of traces to keep, counted from the lap's
// own start. [KeepTracesForever] keeps them all.
TraceMonths int `json:"trace_months"`
}
Retention is how long this server keeps the compressed trace of a lap.
It applies to traces and not to laps. A lap's number, its time and the stint it belongs to are the driver's record and are never deleted here; the trace is the shape of the lap, it is the bulk of the data, and it is the part an operator runs out of disk for.
type Settings ¶
type Settings struct {
// Organisation is what drivers see when their client connects. It is
// wire.Discovery.Name.
Organisation string `json:"organisation"`
// PublicHost is the host name drivers' clients reach, with no scheme and
// no port. Pairing links are built from it, and it is the name on the
// certificate when TLSMode is [TLSAuto].
PublicHost string `json:"public_host"`
// TLSMode is how that host is served.
TLSMode TLSMode `json:"tls_mode"`
// ShortName, Logo and Accent are the rest of the white-label branding a
// client wears once it is paired. ShortName empty means the initials of
// Organisation; Logo is an absolute URL; Accent is a CSS hex colour, and
// empty means the client keeps its own.
ShortName string `json:"short_name,omitempty"`
Logo string `json:"logo,omitempty"`
Accent string `json:"accent,omitempty"`
// Limits are served in discovery and enforced by the server.
Limits wire.Limits `json:"limits"`
// Retention is how long lap traces are kept. It is a setting rather than a
// constant because what a team can afford to store is the operator's
// answer and not this server's.
Retention Retention `json:"retention"`
}
Settings are the organisation's own configuration. They live in the database rather than in the file beside the binary, because they are what makes this deployment itself: delete the data directory and these survive.
func DefaultSettings ¶
DefaultSettings are the settings of an organisation that has said nothing but its name, the host drivers reach it on, and how that host is served. Every other field has an answer that is right for most installations, and the wizard asks for none of them.
func (Settings) BaseURL ¶
BaseURL is the address everything a driver is sent to is built from: the pairing link, the privacy notice, the admin panel printed at startup.
func (Settings) Discovery ¶
Discovery is the document a client fetches before anything else.
The features are passed in rather than read from here because what an installation has depends on what is running rather than on what is stored — a plugin answering a request kind, not a key somebody pasted — and this package knows nothing about plugins.
type TLSMode ¶
type TLSMode string
TLSMode is how the server is reached from the internet. The operator chooses once, in the wizard, and can change it later in the admin panel.
const ( // TLSProxy is plain HTTP on a port, with a reverse proxy in front doing // the certificate. The operator already has nginx, Caddy or a load // balancer and wants this to stay out of the way. TLSProxy TLSMode = "proxy" // TLSAuto is an automatic Let's Encrypt certificate for [Settings.PublicHost]. // It is what lets a bare binary on a fresh server talk to a real client, // which needs HTTPS, and it needs ports 80 and 443. TLSAuto TLSMode = "auto" )
The two ways a server is reached.