main.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Copyright (C) 2019 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package main
  7. import (
  8. "bufio"
  9. "crypto/tls"
  10. "encoding/json"
  11. "flag"
  12. "log"
  13. "os"
  14. "reflect"
  15. "github.com/AudriusButkevicius/recli"
  16. "github.com/flynn-archive/go-shlex"
  17. "github.com/mattn/go-isatty"
  18. "github.com/pkg/errors"
  19. "github.com/syncthing/syncthing/lib/build"
  20. "github.com/syncthing/syncthing/lib/config"
  21. "github.com/syncthing/syncthing/lib/events"
  22. "github.com/syncthing/syncthing/lib/locations"
  23. "github.com/syncthing/syncthing/lib/protocol"
  24. "github.com/urfave/cli"
  25. )
  26. func main() {
  27. // This is somewhat a hack around a chicken and egg problem.
  28. // We need to set the home directory and potentially other flags to know where the syncthing instance is running
  29. // in order to get it's config ... which we then use to construct the actual CLI ... at which point it's too late
  30. // to add flags there...
  31. homeBaseDir := locations.GetBaseDir(locations.ConfigBaseDir)
  32. guiCfg := config.GUIConfiguration{}
  33. flags := flag.NewFlagSet("", flag.ContinueOnError)
  34. flags.StringVar(&guiCfg.RawAddress, "gui-address", guiCfg.RawAddress, "Override GUI address (e.g. \"http://192.0.2.42:8443\")")
  35. flags.StringVar(&guiCfg.APIKey, "gui-apikey", guiCfg.APIKey, "Override GUI API key")
  36. flags.StringVar(&homeBaseDir, "home", homeBaseDir, "Set configuration directory")
  37. // Implement the same flags at the lower CLI, with the same default values (pre-parse), but do nothing with them.
  38. // This is so that we could reuse os.Args
  39. fakeFlags := []cli.Flag{
  40. cli.StringFlag{
  41. Name: "gui-address",
  42. Value: guiCfg.RawAddress,
  43. Usage: "Override GUI address (e.g. \"http://192.0.2.42:8443\")",
  44. },
  45. cli.StringFlag{
  46. Name: "gui-apikey",
  47. Value: guiCfg.APIKey,
  48. Usage: "Override GUI API key",
  49. },
  50. cli.StringFlag{
  51. Name: "home",
  52. Value: homeBaseDir,
  53. Usage: "Set configuration directory",
  54. },
  55. }
  56. // Do not print usage of these flags, and ignore errors as this can't understand plenty of things
  57. flags.Usage = func() {}
  58. _ = flags.Parse(os.Args[1:])
  59. // Now if the API key and address is not provided (we are not connecting to a remote instance),
  60. // try to rip it out of the config.
  61. if guiCfg.RawAddress == "" && guiCfg.APIKey == "" {
  62. // Update the base directory
  63. err := locations.SetBaseDir(locations.ConfigBaseDir, homeBaseDir)
  64. if err != nil {
  65. log.Fatal(errors.Wrap(err, "setting home"))
  66. }
  67. // Load the certs and get the ID
  68. cert, err := tls.LoadX509KeyPair(
  69. locations.Get(locations.CertFile),
  70. locations.Get(locations.KeyFile),
  71. )
  72. if err != nil {
  73. log.Fatal(errors.Wrap(err, "reading device ID"))
  74. }
  75. myID := protocol.NewDeviceID(cert.Certificate[0])
  76. // Load the config
  77. cfg, _, err := config.Load(locations.Get(locations.ConfigFile), myID, events.NoopLogger)
  78. if err != nil {
  79. log.Fatalln(errors.Wrap(err, "loading config"))
  80. }
  81. guiCfg = cfg.GUI()
  82. } else if guiCfg.Address() == "" || guiCfg.APIKey == "" {
  83. log.Fatalln("Both -gui-address and -gui-apikey should be specified")
  84. }
  85. if guiCfg.Address() == "" {
  86. log.Fatalln("Could not find GUI Address")
  87. }
  88. if guiCfg.APIKey == "" {
  89. log.Fatalln("Could not find GUI API key")
  90. }
  91. client := getClient(guiCfg)
  92. cfg, err := getConfig(client)
  93. original := cfg.Copy()
  94. if err != nil {
  95. log.Fatalln(errors.Wrap(err, "getting config"))
  96. }
  97. // Copy the config and set the default flags
  98. recliCfg := recli.DefaultConfig
  99. recliCfg.IDTag.Name = "xml"
  100. recliCfg.SkipTag.Name = "json"
  101. commands, err := recli.New(recliCfg).Construct(&cfg)
  102. if err != nil {
  103. log.Fatalln(errors.Wrap(err, "config reflect"))
  104. }
  105. // Construct the actual CLI
  106. app := cli.NewApp()
  107. app.Name = "stcli"
  108. app.HelpName = app.Name
  109. app.Author = "The Syncthing Authors"
  110. app.Usage = "Syncthing command line interface"
  111. app.Version = build.Version
  112. app.Flags = fakeFlags
  113. app.Metadata = map[string]interface{}{
  114. "client": client,
  115. }
  116. app.Commands = []cli.Command{
  117. {
  118. Name: "config",
  119. HideHelp: true,
  120. Usage: "Configuration modification command group",
  121. Subcommands: commands,
  122. },
  123. showCommand,
  124. operationCommand,
  125. errorsCommand,
  126. }
  127. tty := isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
  128. if !tty {
  129. // Not a TTY, consume from stdin
  130. scanner := bufio.NewScanner(os.Stdin)
  131. for scanner.Scan() {
  132. input, err := shlex.Split(scanner.Text())
  133. if err != nil {
  134. log.Fatalln(errors.Wrap(err, "parsing input"))
  135. }
  136. if len(input) == 0 {
  137. continue
  138. }
  139. err = app.Run(append(os.Args, input...))
  140. if err != nil {
  141. log.Fatalln(err)
  142. }
  143. }
  144. err = scanner.Err()
  145. if err != nil {
  146. log.Fatalln(err)
  147. }
  148. } else {
  149. err = app.Run(os.Args)
  150. if err != nil {
  151. log.Fatalln(err)
  152. }
  153. }
  154. if !reflect.DeepEqual(cfg, original) {
  155. body, err := json.MarshalIndent(cfg, "", " ")
  156. if err != nil {
  157. log.Fatalln(err)
  158. }
  159. resp, err := client.Post("system/config", string(body))
  160. if err != nil {
  161. log.Fatalln(err)
  162. }
  163. if resp.StatusCode != 200 {
  164. body, err := responseToBArray(resp)
  165. if err != nil {
  166. log.Fatalln(err)
  167. }
  168. log.Fatalln(string(body))
  169. }
  170. }
  171. }