recoverDB checks for migration conflicts before the server starts. It connects to postgres on every run (embedded postgres starts fast enough that caching is unnecessary) and compares the tracking table against files on disk. Conflicts: - Tracked file missing from disk → needs --db-rollback or --db
(ctx context.Context, logger slog.Logger, cfg *devConfig)
| 45 | // - Tracked file content differs from disk → needs --db-continue or --db-reset. |
| 46 | // - New files on disk not tracked → normal forward migration, server handles it. |
| 47 | func recoverDB(ctx context.Context, logger slog.Logger, cfg *devConfig) error { |
| 48 | pgURL := os.Getenv("CODER_PG_CONNECTION_URL") |
| 49 | isBuiltinPG := pgURL == "" |
| 50 | |
| 51 | if isBuiltinPG { |
| 52 | pgDir := filepath.Join(cfg.configDir, "postgres") |
| 53 | if _, err := os.Stat(filepath.Join(pgDir, "data")); err != nil { |
| 54 | return nil // Fresh install. |
| 55 | } |
| 56 | if cfg.dbReset { |
| 57 | logger.Warn(ctx, "wiping built-in database (--db-reset)") |
| 58 | if err := os.RemoveAll(pgDir); err != nil { |
| 59 | return xerrors.Errorf("remove postgres directory: %w", err) |
| 60 | } |
| 61 | return nil |
| 62 | } |
| 63 | stopPG, err := startTempPostgresSetURL(ctx, logger, cfg, &pgURL) |
| 64 | if err != nil { |
| 65 | return xerrors.Errorf( |
| 66 | "cannot start temporary postgres: %w\n\ntry --db-reset instead", err) |
| 67 | } |
| 68 | defer stopPG() |
| 69 | } else if cfg.dbReset { |
| 70 | db, err := connectDB(ctx, pgURL) |
| 71 | if err != nil { |
| 72 | return xerrors.Errorf("connect for reset: %w", err) |
| 73 | } |
| 74 | defer db.Close() |
| 75 | _, _ = fmt.Fprintf(os.Stderr, |
| 76 | "\n WARNING: this will DROP all schemas in the external database.\n"+ |
| 77 | " Set CODER_DEV_DB_RESET=1 to confirm.\n\n") |
| 78 | if os.Getenv("CODER_DEV_DB_RESET") != "1" { |
| 79 | return xerrors.New("refusing to reset external database without CODER_DEV_DB_RESET=1") |
| 80 | } |
| 81 | logger.Warn(ctx, "resetting external database (--db-reset)") |
| 82 | return resetSchema(ctx, db) |
| 83 | } |
| 84 | |
| 85 | db, err := connectDB(ctx, pgURL) |
| 86 | if err != nil { |
| 87 | return xerrors.Errorf("connect: %w", err) |
| 88 | } |
| 89 | defer db.Close() |
| 90 | |
| 91 | migrDir := filepath.Join(cfg.projectRoot, "coderd", "database", "migrations") |
| 92 | return checkAndRecover(ctx, logger, db, migrDir, cfg) |
| 93 | } |
| 94 | |
| 95 | // checkAndRecover is the core logic: |
| 96 | // 1. Ensure tracking table exists. |
no test coverage detected