main.go 5.2 KB

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