package main import ( "fmt" "os" "github.com/spf13/cobra" "go.uber.org/fx" "gopkg.in/yaml.v3" "iop/apps/node/internal/bootstrap" "iop/packages/config" "iop/packages/hostsetup" "iop/packages/version" ) var cfgFile string func main() { if err := rootCmd().Execute(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func rootCmd() *cobra.Command { root := &cobra.Command{ Use: "node", Short: "IOP Node Agent — runs adapter executions on this device", } root.PersistentFlags().StringVarP(&cfgFile, "config", "c", "configs/node.yaml", "config file path") root.AddCommand(serveCmd(), versionCmd(), configCmd(), setupCmd()) return root } func serveCmd() *cobra.Command { return &cobra.Command{ Use: "serve", Short: "Start the IOP node (connects to edge)", RunE: func(cmd *cobra.Command, _ []string) error { cfg, err := config.Load(cfgFile) if err != nil { return fmt.Errorf("load config: %w", err) } app := fx.New(bootstrap.Module(cfg)) app.Run() return nil }, } } func versionCmd() *cobra.Command { return &cobra.Command{ Use: "version", Short: "Print IOP node version", Run: func(cmd *cobra.Command, _ []string) { fmt.Fprintln(cmd.OutOrStdout(), version.Version) }, } } func configCmd() *cobra.Command { cfgGroup := &cobra.Command{ Use: "config", Short: "Config management commands", } cfgGroup.AddCommand(configPrintCmd(), configCheckCmd()) return cfgGroup } func configPrintCmd() *cobra.Command { return &cobra.Command{ Use: "print", Short: "Print the resolved configuration", RunE: func(cmd *cobra.Command, _ []string) error { cfg, err := config.Load(cfgFile) if err != nil { return fmt.Errorf("load config: %w", err) } enc := yaml.NewEncoder(cmd.OutOrStdout()) enc.SetIndent(2) return enc.Encode(cfg) }, } } func configCheckCmd() *cobra.Command { return &cobra.Command{ Use: "check", Short: "Validate node config can be loaded", RunE: func(cmd *cobra.Command, _ []string) error { if _, err := config.Load(cfgFile); err != nil { return fmt.Errorf("load config: %w", err) } fmt.Fprintf(cmd.OutOrStdout(), "OK %s\n", cfgFile) return nil }, } } func setupCmd() *cobra.Command { var opts hostsetup.SetupOptions c := &cobra.Command{ Use: "setup", Short: "Prepare host environment and systemd unit for iop-node", RunE: func(cmd *cobra.Command, _ []string) error { if cmd.Flags().Changed("config") { opts.ConfigPath = cfgFile } else { opts.ConfigPath = "/etc/iop/node.yaml" } return hostsetup.Run(cmd.Context(), hostsetup.NodeSpec(), opts, cmd.OutOrStdout()) }, } // setup --help shows /etc/iop/node.yaml as the --config default, not the dev default. c.SetHelpFunc(func(cmd *cobra.Command, args []string) { if f := cmd.Root().PersistentFlags().Lookup("config"); f != nil { prev := f.DefValue f.DefValue = "/etc/iop/node.yaml" defer func() { f.DefValue = prev }() } cmd.Root().HelpFunc()(cmd, args) }) c.Flags().StringVar(&opts.BinaryPath, "binary", "", "path to iop-node binary (defaults to current executable)") c.Flags().StringVar(&opts.DataDir, "data-dir", "", "data directory (defaults to spec)") c.Flags().StringVar(&opts.UnitPath, "unit", "", "systemd unit path (defaults to spec)") c.Flags().StringVar(&opts.User, "user", "", "service user") c.Flags().StringVar(&opts.Group, "group", "", "service group") c.Flags().BoolVar(&opts.Enable, "enable", false, "enable the systemd unit") c.Flags().BoolVar(&opts.Start, "start", false, "start the systemd unit") c.Flags().BoolVar(&opts.Restart, "restart", false, "restart the systemd unit") c.Flags().BoolVar(&opts.DryRun, "dry-run", false, "print plan and previews without making changes") c.Flags().BoolVar(&opts.OverwriteConfig, "overwrite-config", false, "overwrite an existing config file") return c }