package main import ( "context" "encoding/json" "errors" "flag" "fmt" "os" "path/filepath" "time" "git.yuntuo.hk/k2safe/orbit/internal/config" "git.yuntuo.hk/k2safe/orbit/internal/storage/mysql" core "git.yuntuo.hk/k2safe/orbit/model" ) func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func run() error { if len(os.Args) < 2 || os.Args[1] != "bootstrap" { return errors.New("usage: orbit-admin bootstrap (--application NAME | --application-id ID) --namespace NAME --environment NAME --queue NAME --output NEW_FILE") } flags := flag.NewFlagSet("bootstrap", flag.ContinueOnError) var in mysql.BootstrapInput var output string flags.StringVar(&in.Application, "application", "", "new application name") flags.Func("application-id", "existing application id; mutually exclusive with --application", func(v string) error { in.ExistingApplicationID = core.ID(v) return in.ExistingApplicationID.Validate() }) flags.BoolVar(&in.ResourceAccess, "resource-access", false, "grant application-wide resource read and manage actions") flags.StringVar(&in.Namespace, "namespace", "", "new namespace name") flags.StringVar(&in.Environment, "environment", "", "environment name") flags.StringVar(&in.Queue, "queue", "", "initial queue name") flags.StringVar(&output, "output", "", "new private credentials JSON file") flags.BoolVar(&in.WorkerAccess, "worker-access", false, "also grant Worker connect and consume actions for development") flags.BoolVar(&in.ScheduleAccess, "schedule-access", false, "also grant schedule read and manage actions for development") flags.BoolVar(&in.EventAccess, "event-access", false, "also grant result subscription read, manage and consume actions") flags.BoolVar(&in.RecoveryAccess, "recovery-access", false, "explicitly grant task review, manual retry and operation audit read actions") flags.BoolVar(&in.WorkflowAccess, "workflow-access", false, "explicitly grant workflow definition read and publish actions") flags.BoolVar(&in.WorkflowRunAccess, "workflow-run-access", false, "explicitly grant workflow start and read actions") flags.BoolVar(&in.WorkflowControlAccess, "workflow-control-access", false, "explicitly grant workflow control and read actions") flags.BoolVar(&in.WorkflowSignalAccess, "workflow-signal-access", false, "explicitly grant workflow signal and read actions") flags.BoolVar(&in.WorkflowRecoveryAccess, "workflow-recovery-access", false, "explicitly grant workflow recovery and read actions") flags.BoolVar(&in.WorkflowAuditAccess, "workflow-audit-access", false, "explicitly grant workflow operation audit read action") if err := flags.Parse(os.Args[2:]); err != nil { return err } if flags.NArg() != 0 || output == "" { return errors.New("--output and named resource arguments are required") } cfg, err := config.Load() if err != nil { return err } file, err := os.OpenFile(output, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) if err != nil { return errors.New("cannot create new private output file (existing files are never overwritten)") } defer file.Close() delivered := false defer func() { if !delivered { _ = os.Remove(output) } }() ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() db, err := mysql.Open(ctx, cfg.MySQLDSN, 2, 1) if err != nil { return err } defer db.Close() err = mysql.Bootstrap(ctx, db, in, func(result mysql.BootstrapResult) error { if err := json.NewEncoder(file).Encode(result); err != nil { return err } if err := file.Sync(); err != nil { return err } directory, err := os.Open(filepath.Dir(output)) if err != nil { return err } err = directory.Sync() closeErr := directory.Close() if err != nil { return err } if closeErr != nil { return closeErr } delivered = true return nil }) if err != nil { if delivered { return errors.New("bootstrap commit could not be confirmed; private output was retained; verify its credential before retrying") } return errors.New("bootstrap failed before credential delivery; check schema, names and database access") } fmt.Println("Bootstrap committed. Credentials saved in the requested private file; valid for 30 days.") return nil }