package main import ( "flag" "fmt" "log" "os" "time" "gopkg.in/yaml.v3" "git.warky.dev/wdevs/amcs/internal/config" ) func main() { var ( configPath string dryRun bool toVersion int ) flag.StringVar(&configPath, "config", "", "Path to the YAML config file (default: $AMCS_CONFIG or ./configs/dev.yaml)") flag.BoolVar(&dryRun, "dry-run", false, "Print the migrated config to stdout instead of writing it back") flag.IntVar(&toVersion, "to-version", config.CurrentConfigVersion, "Stop migrating after reaching this version") flag.Parse() if toVersion <= 0 || toVersion > config.CurrentConfigVersion { log.Fatalf("invalid -to-version %d (must be between 1 and %d)", toVersion, config.CurrentConfigVersion) } path := config.ResolvePath(configPath) original, err := os.ReadFile(path) if err != nil { log.Fatalf("read config %q: %v", path, err) } raw := map[string]any{} if err := yaml.Unmarshal(original, &raw); err != nil { log.Fatalf("decode config %q: %v", path, err) } if raw == nil { raw = map[string]any{} } applied, err := migrateUpTo(raw, toVersion) if err != nil { log.Fatalf("migrate: %v", err) } if len(applied) == 0 { fmt.Fprintf(os.Stderr, "%s already at version %d; nothing to do\n", path, currentVersion(raw)) return } out, err := yaml.Marshal(raw) if err != nil { log.Fatalf("marshal migrated config: %v", err) } for _, step := range applied { fmt.Fprintf(os.Stderr, "applied migration v%d -> v%d: %s\n", step.From, step.To, step.Describe) } if dryRun { _, _ = os.Stdout.Write(out) return } backup := fmt.Sprintf("%s.bak.%d", path, time.Now().Unix()) if err := os.WriteFile(backup, original, 0o600); err != nil { log.Fatalf("write backup %q: %v", backup, err) } if err := os.WriteFile(path, out, 0o600); err != nil { log.Fatalf("write migrated config %q: %v", path, err) } fmt.Fprintf(os.Stderr, "wrote migrated config to %s (backup: %s)\n", path, backup) } // migrateUpTo runs the migration ladder but stops at the requested version. func migrateUpTo(raw map[string]any, target int) ([]config.ConfigMigration, error) { if currentVersion(raw) >= target { return nil, nil } if target == config.CurrentConfigVersion { return config.Migrate(raw) } // Partial migrations are rare; for now reject anything other than the // current version target since the migration ladder is short. return nil, fmt.Errorf("partial migration to v%d is not supported (use -to-version=%d)", target, config.CurrentConfigVersion) } func currentVersion(raw map[string]any) int { v, ok := raw["version"] if !ok { return 1 } switch n := v.(type) { case int: return n case int64: return int(n) case float64: return int(n) } return 1 }