Files
Felis/cmd/felis/tui_postgres.go

92 lines
2.1 KiB
Go

package main
import (
"context"
"fmt"
"net"
"strings"
"time"
"felis.lolicon.best/internal/store"
)
// checkPostgres proves the configured database is reachable and accepts a
// connection. Host bootstrap provisions PostgreSQL, so in the normal setup flow
// this succeeds immediately; the preflight stage uses it to fail fast otherwise.
func checkPostgres(dbURL string) error {
cfg, err := parseDBURL(dbURL)
if err != nil {
return fmt.Errorf("invalid database URL: %w", err)
}
conn, err := net.DialTimeout("tcp", cfg.addr, 2*time.Second)
if err != nil {
return fmt.Errorf("cannot reach PostgreSQL at %s: %w", cfg.addr, err)
}
conn.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
drv, err := store.Open(ctx, dbURL)
if err != nil {
return fmt.Errorf("connect to PostgreSQL: %w", err)
}
drv.Close()
return nil
}
type dbCfg struct {
addr string
user string
pass string
db string
}
func parseDBURL(url string) (dbCfg, error) {
// Simple parser for postgres://user:pass@host:port/db?options
s := strings.TrimPrefix(url, "postgres://")
s = strings.TrimPrefix(s, "postgresql://")
parts := strings.SplitN(s, "@", 2)
if len(parts) != 2 {
return dbCfg{}, fmt.Errorf("malformed URL")
}
auth := strings.SplitN(parts[0], ":", 2)
rest := strings.SplitN(parts[1], "/", 2)
if len(rest) < 2 {
return dbCfg{}, fmt.Errorf("malformed URL: no database")
}
hostport := rest[0]
dbname := strings.SplitN(rest[1], "?", 2)[0]
if !strings.Contains(hostport, ":") {
hostport += ":5432"
}
return dbCfg{
addr: hostport,
user: auth[0],
pass: func() string {
if len(auth) > 1 {
return auth[1]
}
return ""
}(),
db: dbname,
}, nil
}
func countMigrations(dbURL string) (int, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
drv, err := store.Open(ctx, dbURL)
if err != nil {
return 0, err
}
defer drv.Close()
if err := drv.EnsureVersionTable(ctx); err != nil {
return 0, err
}
applied, err := drv.AppliedVersions(ctx)
if err != nil {
return 0, err
}
return len(applied), nil
}