Adopt golib/pg for migrations and pool construction
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful

artifactapi's schema was a ~180-line inline DDL blob re-executed on every
start, growing an ALTER TABLE ... IF NOT EXISTS line per change with nothing
recording what had run. golib/pg already owns that mechanic for the estate, so
move the SQL into a versioned, embedded set and let the library apply it.

- Depend on git.unkin.net/unkin/golib v0.1.0.
- Move the DDL verbatim to migrations/0001_init.sql, embedded via the new
  migrations package, and build the pool with pg.NewMigrated (LockName
  "artifactapi-migrations"). The runner adds a cluster-wide advisory lock the
  old blob never took, so replicas starting together queue instead of racing
  each other through the DDL.
- The live database has the schema but no schema_migrations, so its first start
  on this build re-runs 0001. Every statement is IF NOT EXISTS-guarded, so that
  run is a no-op landing only the tracking row; a container-backed test drops
  the row from a migrated database and asserts exactly that, and a static guard
  keeps future migrations additive and idempotent.
- Keep config.DatabaseDSN as the DSN source rather than pg.DSNFromEnv: the
  variable names match, but golib has no default user or database name, and
  artifactapi documents and ships DBUSER/DBNAME defaults of "artifacts". The
  deployed env var contract is unchanged.
- Guard the embedded set against migrations/ and pin the derived advisory key,
  so neither can drift unnoticed.
- 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.
This commit is contained in:
2026-09-02 00:18:46 +10:00
parent 734195e54e
commit 82bb5708c8
11 changed files with 524 additions and 303 deletions
+39
View File
@@ -321,6 +321,28 @@ S3/MinIO ─── content-addressable blob storage (blobs/sha256/{hash})
S3 client supports MinIO, Ceph RGW, and AWS S3 (via minio-go).
### Schema migrations
The SQL schema lives in `migrations/` as numbered `.sql` files, embedded into
the binary and applied at startup before the server listens, so there is no
mirrored copy of the schema in the deployment to drift out of sync.
The runner is [`golib/pg`](https://git.unkin.net/unkin/golib)'s `pg.NewMigrated`
— artifactapi owns the SQL, the shared library owns the mechanics.
Each start takes `pg_advisory_lock` on a fixed key (FNV-1a/64 of the lock name
`artifactapi-migrations`), creates `schema_migrations` (`version`, `applied_at`)
if missing, and applies every embedded file whose filename is not yet recorded —
in lexical (version) order, each file's SQL and its tracking row in one
transaction — then unlocks. Replicas starting together queue on the lock and
then find nothing to do.
A file absent from `schema_migrations` is re-run even when the database already
has the schema, which is how a database migrated by the old untracked inline DDL
picks `0001_init.sql` up: the statements are `IF NOT EXISTS`-guarded, so the
re-run is a no-op that only lands the tracking row. New migrations must stay
additive and idempotent for the same reason; a test enforces it.
## Environment Variables
| Variable | Default | Description |
@@ -331,6 +353,7 @@ S3 client supports MinIO, Ceph RGW, and AWS S3 (via minio-go).
| `DBUSER` | `artifacts` | PostgreSQL user |
| `DBPASS` | | PostgreSQL password |
| `DBNAME` | `artifacts` | PostgreSQL database |
| `DBSSL` | `disable` | PostgreSQL `sslmode` |
| `REDIS_URL` | `redis://localhost:6379` | Redis URL |
| `MINIO_ENDPOINT` | `localhost:9000` | S3 endpoint |
| `MINIO_ACCESS_KEY` | | S3 access key |
@@ -358,6 +381,22 @@ make lint # golangci-lint + go vet
make fmt # gofmt + goimports
```
### `GOPRIVATE`
artifactapi depends on `git.unkin.net/unkin/golib`, which is served by Gitea and
is unknown to `proxy.golang.org` / `sum.golang.org`. Module resolution therefore
needs:
```
export GOPRIVATE=git.unkin.net
```
The `Makefile` exports it for every target, and the `Dockerfile` and the
woodpecker Go steps set it themselves, so `make build|test|lint` and CI work on
a clean checkout. Only bare `go` commands run outside `make` need it in your
shell — set it there (or in your shell profile) rather than with `go env -w`,
which is machine state this repo cannot carry.
### TUI
```bash