mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add automatic database migration recovery to scripts/develop (#23466)
When developers switch branches, the database may have migrations from the other branch that don't exist in the current binary. This causes coder server to fail at startup, leaving developers stuck. The develop script now detects this before starting the server: 1. Connects to postgres (starts temp embedded instance for built-in postgres, or uses CODER_PG_CONNECTION_URL). 2. Compares DB version against the source's latest migration. 3. If DB is ahead, searches git history for the missing down SQL files and applies them in a transaction. 4. If git recovery fails (ambiguous versions across branches, missing files), falls back to resetting the public schema. Also adds --reset-db and --skip-db-recovery flags.
This commit is contained in:
@@ -119,6 +119,24 @@ func main() {
|
||||
Description: "Starter template to create (empty to skip).",
|
||||
Value: serpent.StringOf(&cfg.starterTemplate),
|
||||
},
|
||||
{
|
||||
Flag: "db-rollback",
|
||||
Env: "CODER_DEV_DB_ROLLBACK",
|
||||
Description: "Roll back database migrations that no longer exist on the current branch.",
|
||||
Value: serpent.BoolOf(&cfg.dbRollback),
|
||||
},
|
||||
{
|
||||
Flag: "db-reset",
|
||||
Env: "CODER_DEV_DB_RESET",
|
||||
Description: "Destroy the development database and start fresh.",
|
||||
Value: serpent.BoolOf(&cfg.dbReset),
|
||||
},
|
||||
{
|
||||
Flag: "db-continue",
|
||||
Env: "CODER_DEV_DB_CONTINUE",
|
||||
Description: "Accept changed migration files and update tracking. Use when you've manually fixed the DB to match the new migrations.",
|
||||
Value: serpent.BoolOf(&cfg.dbContinue),
|
||||
},
|
||||
},
|
||||
Handler: func(inv *serpent.Invocation) error {
|
||||
cfg.serverExtraArgs = inv.Args
|
||||
@@ -152,6 +170,9 @@ type devConfig struct {
|
||||
multiOrg bool
|
||||
debug bool
|
||||
starterTemplate string
|
||||
dbRollback bool
|
||||
dbReset bool
|
||||
dbContinue bool
|
||||
projectRoot string
|
||||
binaryPath string
|
||||
configDir string
|
||||
@@ -167,6 +188,12 @@ func (c *devConfig) validate() error {
|
||||
if c.agpl && c.multiOrg {
|
||||
return xerrors.New("cannot use both --agpl and --multi-organization")
|
||||
}
|
||||
if c.dbRollback && c.dbReset {
|
||||
return xerrors.New("cannot use both --db-rollback and --db-reset")
|
||||
}
|
||||
if c.dbContinue && c.dbReset {
|
||||
return xerrors.New("cannot use both --db-continue and --db-reset")
|
||||
}
|
||||
for _, p := range []struct {
|
||||
name string
|
||||
val int64
|
||||
@@ -337,6 +364,15 @@ func develop(ctx context.Context, logger slog.Logger, cfg *devConfig) error {
|
||||
if err := preflight(sigCtx, logger, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check the database before building. The mismatch check is
|
||||
// a cheap file read; only starts temp postgres on actual
|
||||
// mismatch. This avoids a wasted build cycle when the
|
||||
// developer needs to re-run with --db-rollback or --db-reset.
|
||||
if err := recoverDB(sigCtx, logger, cfg); err != nil {
|
||||
return xerrors.Errorf("database recovery: %w", err)
|
||||
}
|
||||
|
||||
if err := buildBinary(sigCtx, logger, cfg); err != nil {
|
||||
return xerrors.Errorf("build: %w", err)
|
||||
}
|
||||
@@ -386,6 +422,14 @@ func develop(ctx context.Context, logger slog.Logger, cfg *devConfig) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Update migration tracking after the server has applied
|
||||
// any new migrations. This keeps the cache current so the
|
||||
// next run detects mismatches correctly.
|
||||
if err := updateMigrationTracking(ctx, logger, cfg); err != nil {
|
||||
logger.Warn(ctx, "failed to update migration tracking",
|
||||
slog.Error(err))
|
||||
}
|
||||
|
||||
if cfg.useProxy {
|
||||
if err := setupWorkspaceProxy(ctx, cfg, client, group); err != nil {
|
||||
logger.Warn(ctx, "proxy setup failed, continuing",
|
||||
|
||||
Reference in New Issue
Block a user