Help us shape the future of Go! Take the 2026 Go Developer Survey

config

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 16, 2026 License: GPL-3.0 Imports: 10 Imported by: 0

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

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
const DefaultMetricsListen = "127.0.0.1:9090"

DefaultMetricsListen is the private port. It is loopback and stays loopback: Config.Validate refuses anything else.

View Source
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.

View Source
const DirName = "pacenote-data"

DirName is the data directory created beside the binary on first run.

View Source
const FileMode os.FileMode = 0o600

FileMode is the configuration file's permission, for the same reason.

View Source
const FileName = "config.json"

FileName is the configuration file inside the data directory.

View Source
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.

View Source
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

View Source
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

func AutocertDir(dir string) string

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

func ClientBinaryIn(dir string) string

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

func ClientBuildsDir(dir string) string

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

func DefaultLimits() wire.Limits

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

func EnsureDir(dir string) error

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

func PluginsDir(dir string) string

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

func ValidateAccent(accent string) error

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

func ValidateHost(host string) error

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

func ValidateLimits(l wire.Limits) error

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

func ValidateRetention(r Retention) error

ValidateRetention reports what is wrong with a retention setting, phrased for the operator who typed it.

func ValidateURL

func ValidateURL(kind, raw string) error

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

func Load(dir string) (Config, error)

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

func (c Config) ClientBinaryPath(dir string) string

ClientBinaryPath is the prebuilt client this configuration names, or the data directory's own when it names none.

func (Config) Save

func (c Config) Save(dir string) error

Save writes the configuration into dir, creating the directory at DirMode if it is not there. The write is to a temporary file in the same directory followed by a rename, so a crash halfway through leaves the old file intact rather than half of the new one.

func (Config) Validate

func (c Config) Validate() error

Validate reports the first thing wrong with the configuration, phrased for the operator who has to fix it.

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.

func (Retention) Cutoff

func (r Retention) Cutoff(now time.Time) (time.Time, bool)

Cutoff is the instant a trace has to be older than to be deleted. The second result is false when nothing is deleted, which the caller must check before using the first: a zero-month setting would otherwise read as a cutoff of now and delete everything.

func (Retention) Keeps

func (r Retention) Keeps() bool

Keeps reports whether this setting deletes anything at all.

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"`
	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

func DefaultSettings(organisation, host string, mode TLSMode) Settings

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

func (s Settings) BaseURL() string

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

func (s Settings) Discovery(features []wire.Feature, minClient string) wire.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.

func (Settings) Scheme

func (s Settings) Scheme() string

Scheme is how a driver's client reaches this server: https everywhere except a development run on localhost, which has no certificate and does not need one.

func (Settings) Validate

func (s Settings) Validate() error

Validate reports the first thing wrong with these settings, phrased for the operator who has to fix it.

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.

func (TLSMode) Valid

func (m TLSMode) Valid() bool

Valid reports whether m is one of the two modes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL