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 bootstrapMergeBootstrapPR bool var bootstrapManageNetworkBridges bool 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") bootstrapCmd.Flags().BoolVar(&bootstrapMergeBootstrapPR, "merge-bootstrap-pr", false, "Merge the generated Flux repository migration PR before bootstrapping") bootstrapCmd.Flags().BoolVar(&bootstrapManageNetworkBridges, "manage-network-bridges", false, "Persist Terraform management for existing Talos network bridges") } func runBootstrap(cmd *cobra.Command, args []string) error { var cfg config.Config var err error if bootstrapMergeBootstrapPR { if bootstrapConfigPath == "" { return fmt.Errorf("--merge-bootstrap-pr requires --config") } cfg, err = config.Load(bootstrapConfigPath) if err != nil { return err } manager := forgejo.NewRepoManager(cfg.Git.BaseURL, cfg.Git.Token, cfg.Git.Owner, cfg.Git.Username, "", "", cfg.Flux.Branch, "") if err := manager.MergePullRequest(cfg.Flux.RepoName, "maidn/bootstrap-"+cfg.ClusterID); err != nil { return err } } 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 || bootstrapManageNetworkBridges { cfg, err = config.LoadRaw(bootstrapConfigPath) if err == nil { if bootstrapPromptDemocraticCSI { cfg = ui.PromptDemocraticCSI(cfg) } if bootstrapManageNetworkBridges { cfg.Talos.Cluster.ManageNetworkBridges = true } 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() }