77 "log"
88 "os"
99 "os/signal"
10+ "strings"
1011 "syscall"
1112 "time"
1213
@@ -16,42 +17,112 @@ import (
1617 "github.com/flatrun/agent/pkg/updater"
1718 "github.com/flatrun/agent/pkg/version"
1819 "github.com/moby/moby/client"
20+ "github.com/spf13/cobra"
1921)
2022
2123func main () {
22- if len (os .Args ) > 1 {
23- switch os .Args [1 ] {
24- case "update" :
25- handleUpdate (os .Args [2 :])
26- return
27- case "setup" :
28- handleSetup (os .Args [2 :])
29- return
30- case "version" :
31- printVersion ()
32- return
24+ root := newRootCmd ()
25+ root .SetArgs (normalizeLegacyFlags (os .Args [1 :]))
26+ if err := root .Execute (); err != nil {
27+ os .Exit (1 )
28+ }
29+ }
30+
31+ // normalizeLegacyFlags keeps the single-dash long forms the previous flag-based
32+ // CLI accepted (-config, -version) working under pflag, which otherwise reads a
33+ // single dash as a cluster of shorthand flags. This preserves existing launch
34+ // commands such as `flatrun-agent -config /etc/flatrun/config.yml`.
35+ func normalizeLegacyFlags (args []string ) []string {
36+ out := make ([]string , 0 , len (args ))
37+ for _ , a := range args {
38+ if a == "-config" || a == "-version" ||
39+ strings .HasPrefix (a , "-config=" ) || strings .HasPrefix (a , "-version=" ) {
40+ a = "-" + a
3341 }
42+ out = append (out , a )
3443 }
44+ return out
45+ }
3546
36- configPath := flag . String ( "config" , "" , "Path to configuration file" )
37- showVersion := flag . Bool ( "version" , false , "Print version information" )
38- flag . Parse ()
47+ func newRootCmd () * cobra. Command {
48+ var configPath string
49+ var showVersion bool
3950
40- if * showVersion {
41- printVersion ()
42- return
51+ root := & cobra.Command {
52+ Use : "flatrun-agent" ,
53+ Short : "Flatrun Agent - flat-file container orchestration" ,
54+ SilenceUsage : true ,
55+ RunE : func (cmd * cobra.Command , args []string ) error {
56+ if showVersion {
57+ printVersion ()
58+ return nil
59+ }
60+ // A bare invocation is most likely someone looking for guidance,
61+ // not an attempt to start the daemon: the service is always launched
62+ // with --config. Show help rather than failing on a missing default
63+ // config. Pass --config (or use `serve`) to actually start.
64+ if ! cmd .Flags ().Changed ("config" ) {
65+ return cmd .Help ()
66+ }
67+ return runServer (configPath )
68+ },
69+ }
70+ root .CompletionOptions .DisableDefaultCmd = true
71+ root .PersistentFlags ().StringVar (& configPath , "config" , "" , "Path to configuration file" )
72+ root .Flags ().BoolVar (& showVersion , "version" , false , "Print version information" )
73+
74+ serve := & cobra.Command {
75+ Use : "serve" ,
76+ Short : "Start the agent server" ,
77+ SilenceUsage : true ,
78+ RunE : func (cmd * cobra.Command , args []string ) error {
79+ return runServer (configPath )
80+ },
4381 }
4482
45- resolvedConfigPath := config .FindConfigPath (* configPath )
83+ setup := & cobra.Command {
84+ Use : "setup <target> <service> [options]" ,
85+ Short : "Deploy an infrastructure service from embedded templates" ,
86+ DisableFlagParsing : true ,
87+ Run : func (cmd * cobra.Command , args []string ) {
88+ handleSetup (args )
89+ },
90+ }
91+
92+ update := & cobra.Command {
93+ Use : "update [options]" ,
94+ Short : "Update the agent to the latest version" ,
95+ DisableFlagParsing : true ,
96+ Run : func (cmd * cobra.Command , args []string ) {
97+ handleUpdate (args )
98+ },
99+ }
100+
101+ versionCmd := & cobra.Command {
102+ Use : "version" ,
103+ Short : "Print version information" ,
104+ Run : func (cmd * cobra.Command , args []string ) {
105+ printVersion ()
106+ },
107+ }
108+
109+ root .AddCommand (serve , setup , update , versionCmd )
110+ return root
111+ }
112+
113+ func runServer (configPath string ) error {
114+ resolvedConfigPath := config .FindConfigPath (configPath )
46115 cfg , err := config .Load (resolvedConfigPath )
47116 if err != nil {
48- log . Fatalf ( "Failed to load config from %s: %v " , resolvedConfigPath , err )
117+ return fmt . Errorf ( "failed to load config from %s: %w " , resolvedConfigPath , err )
49118 }
50119
51- ensureDockerReachable (cfg .DockerSocket )
120+ if err := ensureDockerReachable (cfg .DockerSocket ); err != nil {
121+ return err
122+ }
52123
53124 if err := os .MkdirAll (cfg .DeploymentsPath , 0755 ); err != nil {
54- log . Fatalf ( "Failed to create deployments directory '%s' : %v " , cfg .DeploymentsPath , err )
125+ return fmt . Errorf ( "failed to create deployments directory %q : %w " , cfg .DeploymentsPath , err )
55126 }
56127
57128 log .Printf ("Starting Flatrun Agent v%s" , version .Version )
@@ -61,7 +132,7 @@ func main() {
61132
62133 fileWatcher , err := watcher .New (cfg .DeploymentsPath )
63134 if err != nil {
64- log . Fatalf ( "Failed to create file watcher: %v " , err )
135+ return fmt . Errorf ( "failed to create file watcher: %w " , err )
65136 }
66137 defer fileWatcher .Close ()
67138
@@ -80,9 +151,10 @@ func main() {
80151
81152 log .Println ("Shutting down Flatrun Agent..." )
82153 _ = apiServer .Stop ()
154+ return nil
83155}
84156
85- func ensureDockerReachable (dockerHost string ) {
157+ func ensureDockerReachable (dockerHost string ) error {
86158 log .Println ("Checking if Docker is reachable..." )
87159
88160 opts := []client.Opt {client .FromEnv }
@@ -92,19 +164,18 @@ func ensureDockerReachable(dockerHost string) {
92164
93165 cli , err := client .New (opts ... )
94166 if err != nil {
95- log . Fatalf ( "Failed to create Docker client: %v " , err )
167+ return fmt . Errorf ( "failed to create Docker client: %w " , err )
96168 }
97169 defer cli .Close ()
98170
99171 ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Second )
100172 defer cancel ()
101173
102174 if _ , err := cli .Ping (ctx , client.PingOptions {}); err != nil {
103- log .Fatalf ("Docker is not reachable: %v. " +
104- "Ensure the Docker daemon is running and the socket " +
105- "in config is correct." , err )
175+ return fmt .Errorf ("docker is not reachable: %w. Ensure the Docker daemon is running and the socket in config is correct" , err )
106176 }
107177 log .Println ("Docker is reachable" )
178+ return nil
108179}
109180
110181func printVersion () {
0 commit comments