feat: add auto db migration in binary

This commit is contained in:
Enot (ded) Skelly committed 2026-06-11 15:39:07 -07:00
1 parent 880536905f
commit c3bf6d7e0d
5 files changed
+85 -36

No files matched your search

+3 -6
View File
@@ -1,5 +1,5 @@
# Copyright 2026 Beacon Contributors
# SPDX-License-Identifier: agpl
# SPDX-License-Identifier: AGPL-3.0-or-later
FROM golang:1.26-alpine AS builder
WORKDIR /app
@@ -9,10 +9,7 @@ COPY . .
RUN go build -o beacon ./cmd/beacon
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata postgresql-client
RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app
COPY --from=builder /app/beacon .
COPY db/migrations/ ./migrations/
COPY .build/docker-entrypoint.sh .
RUN chmod +x docker-entrypoint.sh
ENTRYPOINT ["./docker-entrypoint.sh"]
CMD ["./beacon"]
-30
View File
@@ -1,30 +0,0 @@
#!/bin/sh
# Copyright 2026 Beacon Contributors
# SPDX-License-Identifier: agpl
set -e
if [ -n "$POSTGRES_DSN" ]; then
echo "Running database migrations..."
psql "$POSTGRES_DSN" -c "CREATE TABLE IF NOT EXISTS schema_migrations (
filename TEXT PRIMARY KEY,
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);"
for f in /app/migrations/*.sql; do
name=$(basename "$f")
already=$(psql "$POSTGRES_DSN" -tAc "SELECT 1 FROM schema_migrations WHERE filename = '$name'")
if [ "$already" != "1" ]; then
echo " Applying $name..."
psql "$POSTGRES_DSN" -f "$f"
psql "$POSTGRES_DSN" -c "INSERT INTO schema_migrations (filename) VALUES ('$name');"
else
echo " Skipping $name (already applied)"
fi
done
echo "Migrations complete."
fi
exec ./beacon "$@"
+4
View File
@@ -114,6 +114,10 @@ func main() {
}
defer pool.Close()
if err := db.RunMigrations(ctx, pool); err != nil {
log.Fatalf("migrations failed: %v", err)
}
store := db.New(pool)
// ── Redis cache layer ────────────────────────────────────────────────────
+78
View File
@@ -0,0 +1,78 @@
// Copyright 2026 Beacon Contributors
// SPDX-License-Identifier: AGPL-3.0-or-later
package db
import (
"context"
"embed"
"fmt"
"io/fs"
"sort"
"strings"
"github.com/jackc/pgx/v5/pgxpool"
)
//go:embed migrations/*.sql
var migrationFiles embed.FS
func RunMigrations(ctx context.Context, pool *pgxpool.Pool) error {
_, err := pool.Exec(ctx, `
CREATE TABLE IF NOT EXISTS schema_migrations (
filename TEXT PRIMARY KEY,
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
`)
if err != nil {
return fmt.Errorf("failed to create schema_migrations: %w", err)
}
entries, err := fs.ReadDir(migrationFiles, "migrations")
if err != nil {
return fmt.Errorf("failed to read migrations: %w", err)
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].Name() < entries[j].Name()
})
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".sql") {
continue
}
var already bool
err := pool.QueryRow(
ctx,
"SELECT EXISTS(SELECT 1 FROM schema_migrations WHERE filename = $1)",
entry.Name(),
).Scan(&already)
if err != nil {
return fmt.Errorf("failed to check migration %s: %w", entry.Name(), err)
}
if already {
continue
}
sql, err := migrationFiles.ReadFile("migrations/" + entry.Name())
if err != nil {
return fmt.Errorf("failed to read migration %s: %w", entry.Name(), err)
}
if _, err := pool.Exec(ctx, string(sql)); err != nil {
return fmt.Errorf("failed to apply migration %s: %w", entry.Name(), err)
}
if _, err := pool.Exec(
ctx,
"INSERT INTO schema_migrations (filename) VALUES ($1)",
entry.Name(),
); err != nil {
return fmt.Errorf("failed to record migration %s: %w", entry.Name(), err)
}
fmt.Printf("applied migration: %s\n", entry.Name())
}
return nil
}
File renamed without changes.