123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- // Copyright (C) 2019 The Syncthing Authors.
- //
- // This Source Code Form is subject to the terms of the Mozilla Public
- // License, v. 2.0. If a copy of the MPL was not distributed with this file,
- // You can obtain one at https://mozilla.org/MPL/2.0/.
- package main
- import (
- "bufio"
- "crypto/tls"
- "encoding/json"
- "flag"
- "log"
- "os"
- "reflect"
- "github.com/AudriusButkevicius/recli"
- "github.com/flynn-archive/go-shlex"
- "github.com/mattn/go-isatty"
- "github.com/pkg/errors"
- "github.com/syncthing/syncthing/lib/build"
- "github.com/syncthing/syncthing/lib/config"
- "github.com/syncthing/syncthing/lib/events"
- "github.com/syncthing/syncthing/lib/locations"
- "github.com/syncthing/syncthing/lib/protocol"
- "github.com/urfave/cli"
- )
- func main() {
- // This is somewhat a hack around a chicken and egg problem.
- // We need to set the home directory and potentially other flags to know where the syncthing instance is running
- // in order to get it's config ... which we then use to construct the actual CLI ... at which point it's too late
- // to add flags there...
- homeBaseDir := locations.GetBaseDir(locations.ConfigBaseDir)
- guiCfg := config.GUIConfiguration{}
- flags := flag.NewFlagSet("", flag.ContinueOnError)
- flags.StringVar(&guiCfg.RawAddress, "gui-address", guiCfg.RawAddress, "Override GUI address (e.g. \"http://192.0.2.42:8443\")")
- flags.StringVar(&guiCfg.APIKey, "gui-apikey", guiCfg.APIKey, "Override GUI API key")
- flags.StringVar(&homeBaseDir, "home", homeBaseDir, "Set configuration directory")
- // Implement the same flags at the lower CLI, with the same default values (pre-parse), but do nothing with them.
- // This is so that we could reuse os.Args
- fakeFlags := []cli.Flag{
- cli.StringFlag{
- Name: "gui-address",
- Value: guiCfg.RawAddress,
- Usage: "Override GUI address (e.g. \"http://192.0.2.42:8443\")",
- },
- cli.StringFlag{
- Name: "gui-apikey",
- Value: guiCfg.APIKey,
- Usage: "Override GUI API key",
- },
- cli.StringFlag{
- Name: "home",
- Value: homeBaseDir,
- Usage: "Set configuration directory",
- },
- }
- // Do not print usage of these flags, and ignore errors as this can't understand plenty of things
- flags.Usage = func() {}
- _ = flags.Parse(os.Args[1:])
- // Now if the API key and address is not provided (we are not connecting to a remote instance),
- // try to rip it out of the config.
- if guiCfg.RawAddress == "" && guiCfg.APIKey == "" {
- // Update the base directory
- err := locations.SetBaseDir(locations.ConfigBaseDir, homeBaseDir)
- if err != nil {
- log.Fatal(errors.Wrap(err, "setting home"))
- }
- // Load the certs and get the ID
- cert, err := tls.LoadX509KeyPair(
- locations.Get(locations.CertFile),
- locations.Get(locations.KeyFile),
- )
- if err != nil {
- log.Fatal(errors.Wrap(err, "reading device ID"))
- }
- myID := protocol.NewDeviceID(cert.Certificate[0])
- // Load the config
- cfg, _, err := config.Load(locations.Get(locations.ConfigFile), myID, events.NoopLogger)
- if err != nil {
- log.Fatalln(errors.Wrap(err, "loading config"))
- }
- guiCfg = cfg.GUI()
- } else if guiCfg.Address() == "" || guiCfg.APIKey == "" {
- log.Fatalln("Both -gui-address and -gui-apikey should be specified")
- }
- if guiCfg.Address() == "" {
- log.Fatalln("Could not find GUI Address")
- }
- if guiCfg.APIKey == "" {
- log.Fatalln("Could not find GUI API key")
- }
- client := getClient(guiCfg)
- cfg, err := getConfig(client)
- original := cfg.Copy()
- if err != nil {
- log.Fatalln(errors.Wrap(err, "getting config"))
- }
- // Copy the config and set the default flags
- recliCfg := recli.DefaultConfig
- recliCfg.IDTag.Name = "xml"
- recliCfg.SkipTag.Name = "json"
- commands, err := recli.New(recliCfg).Construct(&cfg)
- if err != nil {
- log.Fatalln(errors.Wrap(err, "config reflect"))
- }
- // Construct the actual CLI
- app := cli.NewApp()
- app.Name = "stcli"
- app.HelpName = app.Name
- app.Author = "The Syncthing Authors"
- app.Usage = "Syncthing command line interface"
- app.Version = build.Version
- app.Flags = fakeFlags
- app.Metadata = map[string]interface{}{
- "client": client,
- }
- app.Commands = []cli.Command{
- {
- Name: "config",
- HideHelp: true,
- Usage: "Configuration modification command group",
- Subcommands: commands,
- },
- showCommand,
- operationCommand,
- errorsCommand,
- }
- tty := isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
- if !tty {
- // Not a TTY, consume from stdin
- scanner := bufio.NewScanner(os.Stdin)
- for scanner.Scan() {
- input, err := shlex.Split(scanner.Text())
- if err != nil {
- log.Fatalln(errors.Wrap(err, "parsing input"))
- }
- if len(input) == 0 {
- continue
- }
- err = app.Run(append(os.Args, input...))
- if err != nil {
- log.Fatalln(err)
- }
- }
- err = scanner.Err()
- if err != nil {
- log.Fatalln(err)
- }
- } else {
- err = app.Run(os.Args)
- if err != nil {
- log.Fatalln(err)
- }
- }
- if !reflect.DeepEqual(cfg, original) {
- body, err := json.MarshalIndent(cfg, "", " ")
- if err != nil {
- log.Fatalln(err)
- }
- resp, err := client.Post("system/config", string(body))
- if err != nil {
- log.Fatalln(err)
- }
- if resp.StatusCode != 200 {
- body, err := responseToBArray(resp)
- if err != nil {
- log.Fatalln(err)
- }
- log.Fatalln(string(body))
- }
- }
- }
|