package cmd import ( "fmt" "github.com/Pingu-Studio/MaidnCLI/internal/bootstrap" "github.com/Pingu-Studio/MaidnCLI/internal/config" "github.com/Pingu-Studio/MaidnCLI/internal/forgejo" "github.com/Pingu-Studio/MaidnCLI/internal/openbao" "github.com/Pingu-Studio/MaidnCLI/internal/ui" "github.com/spf13/cobra" ) var bootstrapConfigPath string var bootstrapOutputPath string var bootstrapMode string var bootstrapYes bool var bootstrapPromptDemocraticCSI bool var bootstrapPromptOperationalSecrets bool var bootstrapInitializeOpenBaoRecovery bool var bootstrapPublishAppFrom string var bootstrapCmd = &cobra.Command{ Use: "bootstrap", Short: "Bootstrap Talos and Flux from config or an interactive wizard.", RunE: runBootstrap, } func init() { rootCmd.AddCommand(bootstrapCmd) bootstrapCmd.Flags().StringVar(&bootstrapConfigPath, "config", "", "Path to bootstrap config YAML") bootstrapCmd.Flags().StringVar(&bootstrapOutputPath, "out", "maidn-bootstrap.yaml", "Path to save generated config") bootstrapCmd.Flags().StringVar(&bootstrapMode, "mode", string(bootstrap.Reconcile), "Lifecycle mode: reconcile or rebuild") bootstrapCmd.Flags().BoolVar(&bootstrapYes, "yes", false, "Confirm destructive rebuild") bootstrapCmd.Flags().BoolVar(&bootstrapPromptDemocraticCSI, "prompt-democratic-csi", false, "Prompt for and save Democratic CSI settings in --config") bootstrapCmd.Flags().BoolVar(&bootstrapPromptOperationalSecrets, "prompt-operational-secrets", false, "Prompt for and encrypt operational secrets for --config") bootstrapCmd.Flags().BoolVar(&bootstrapInitializeOpenBaoRecovery, "initialize-openbao-recovery", false, "Create and save a separate OpenBao recovery age identity for --config") bootstrapCmd.Flags().StringVar(&bootstrapPublishAppFrom, "publish-app-from", "", "Push this app checkout's current branch and create a Forgejo delivery PR") } func runBootstrap(cmd *cobra.Command, args []string) error { var cfg config.Config var err error if bootstrapPublishAppFrom != "" { if bootstrapConfigPath == "" { return fmt.Errorf("--publish-app-from requires --config") } cfg, err = config.Load(bootstrapConfigPath) if err != nil { return err } branch, err := forgejo.CurrentBranch(bootstrapPublishAppFrom) if err != nil { return err } owner, repo, err := forgejo.RepositoryFromURL(cfg.Delivery.AppRepoURL) if err != nil { return err } manager := forgejo.NewRepoManager(cfg.Git.BaseURL, cfg.Git.Token, owner, cfg.Git.Username, "", "", cfg.Delivery.AppRepoRef, "") created, err := manager.EnsureRepository(repo, "Application source for Maidn CI/CD delivery") if err != nil { return err } hasDeliveryBranch, err := manager.HasRemoteBranch(cfg.Delivery.AppRepoURL, cfg.Delivery.AppRepoRef) if err != nil { return err } if created || !hasDeliveryBranch { if err := manager.PushRef(bootstrapPublishAppFrom, cfg.Delivery.AppRepoURL, "HEAD", cfg.Delivery.AppRepoRef); err != nil { return err } } if err := manager.PushBranch(bootstrapPublishAppFrom, cfg.Delivery.AppRepoURL, branch); err != nil { return err } return manager.CreatePullRequest(repo, "feat: migrate delivery to Tekton", branch, cfg.Delivery.AppRepoRef) } if bootstrapConfigPath != "" { if bootstrapPromptDemocraticCSI || bootstrapPromptOperationalSecrets || bootstrapInitializeOpenBaoRecovery { cfg, err = config.LoadRaw(bootstrapConfigPath) if err == nil { if bootstrapPromptDemocraticCSI { cfg = ui.PromptDemocraticCSI(cfg) } cfg, err = config.Resolve(cfg) } if err == nil && bootstrapPromptOperationalSecrets { var secrets map[string]map[string]string secrets, err = ui.PromptOperationalSecrets(cfg) if err == nil { var authorization string authorization, err = bootstrap.NewWebhookAuthorization() if err == nil { secrets["cicd/forgejo-webhook"] = map[string]string{"authorization": authorization} err = bootstrap.WriteOperationalSecrets(cfg.SOPS.OperationalSecretsPath, cfg.SOPS.AgeKeyPath, secrets) } } } if err == nil && bootstrapInitializeOpenBaoRecovery { var recipient string recipient, err = openbao.EnsureRecoveryIdentity(cfg.SOPS.RecoveryIdentityPath) if err == nil { cfg.SOPS.RecoveryRecipient = recipient } } if err == nil { err = config.Save(bootstrapConfigPath, cfg) } } else { cfg, err = config.Load(bootstrapConfigPath) } } else { cfg, err = ui.RunBootstrapWizard(config.Config{}) if err == nil { cfg, err = config.Resolve(cfg) } if err == nil { err = config.Save(bootstrapOutputPath, cfg) if err == nil { fmt.Printf("[INFO] Saved config to %s\n", bootstrapOutputPath) } } } if err != nil { return err } runner := bootstrap.Runner{Config: cfg, Mode: bootstrap.Mode(bootstrapMode), ConfirmRebuild: bootstrapYes} return runner.Run() }