Files
unkin-agent 42307f285f
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
Adopt golib/pg for migrations and pool construction
encapi's schema was a cumulative IF NOT EXISTS blob re-executed inline on every
start: it grows forever, records nothing, and cannot express a change that is
not a fresh CREATE. golib owns that mechanism now, so encapi keeps the SQL and
drops the runner.

- Move the DDL verbatim into migrations/0001_init.sql, embedded via
  migrations.FS. It stays IF NOT EXISTS-guarded, so the first start against the
  live database re-runs it as a no-op and only lands the schema_migrations row.
- Build the pool with pg.NewMigrated under the advisory lock named
  encapi-migrations, and delete the inline migrate(). database.New now takes a
  context and a logger; main.go hands it the signal context so a start blocked
  on the migration lock still dies on SIGTERM.
- Render the DSN with pg.DSN. The env var contract is untouched — the fields are
  still resolved by internal/config, because encapi defaults DBUSER and DBNAME
  to "encapi" where pg.DSNFromEnv treats both as required.
- Guard the set: embedded files must match migrations/, every CREATE must be
  idempotent, the derived lock key is pinned, and a container test proves the
  adoption path over a database that predates schema_migrations.
- Plumb GOPRIVATE=git.unkin.net for the first cross-repo Go dependency:
  exported by the Makefile, set in the Dockerfile and the woodpecker Go steps,
  documented in the README.
2026-09-02 00:17:04 +10:00

66 lines
1.8 KiB
Go

// Package config loads encapi server configuration from the environment.
package config
import (
"fmt"
"os"
"strconv"
"git.unkin.net/unkin/golib/pg"
)
// Config is the fully-resolved server configuration.
type Config struct {
ListenAddr string
DBHost string
DBPort int
DBUser string
DBPass string
DBName string
DBSSL string
// WriteToken guards all mutating endpoints. Reads are always open.
// When empty, writes are refused entirely (fail-closed).
WriteToken string
// DistroAPIURL, when set, points encapi at an external kickstart/distro
// API that resolves per-host provisioning params (epel, os release, ...).
// Left empty, no distro params are injected into ENC output.
DistroAPIURL string
}
// DatabaseDSN renders a libpq/pgx connection string. The fields come from this
// package rather than golib's pg.DSNFromEnv because encapi defaults DBUSER and
// DBNAME to "encapi", where DSNFromEnv treats both as required.
func (c *Config) DatabaseDSN() string {
return pg.DSN(c.DBHost, c.DBPort, c.DBUser, c.DBPass, c.DBName, c.DBSSL)
}
// Load reads configuration from the environment, applying defaults.
func Load() (*Config, error) {
dbPort, err := strconv.Atoi(getenv("DBPORT", "5432"))
if err != nil {
return nil, fmt.Errorf("invalid DBPORT: %w", err)
}
return &Config{
ListenAddr: getenv("LISTEN_ADDR", ":8000"),
DBHost: getenv("DBHOST", "localhost"),
DBPort: dbPort,
DBUser: getenv("DBUSER", "encapi"),
DBPass: getenv("DBPASS", "encapi"),
DBName: getenv("DBNAME", "encapi"),
DBSSL: getenv("DBSSL", "disable"),
WriteToken: os.Getenv("ENCAPI_WRITE_TOKEN"),
DistroAPIURL: os.Getenv("ENCAPI_DISTRO_API_URL"),
}, nil
}
func getenv(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}