func devicesAdd(c *cli.Context) { nid := c.Args()[0] id := parseDeviceID(nid) newDevice := config.DeviceConfiguration{ DeviceID: id, Name: nid, Addresses: []string{"dynamic"}, } if len(c.Args()) > 1 { newDevice.Name = c.Args()[1] } if len(c.Args()) > 2 { addresses := c.Args()[2:] for _, item := range addresses { if item == "dynamic" { continue } validAddress(item) } newDevice.Addresses = addresses } cfg := getConfig(c) for _, device := range cfg.Devices { if device.DeviceID == id { die("Device " + nid + " already exists") } } cfg.Devices = append(cfg.Devices, newDevice) setConfig(c, cfg) }
func devicesGet(c *cli.Context) { nid := c.Args()[0] id := parseDeviceID(nid) arg := c.Args()[1] cfg := getConfig(c) for _, device := range cfg.Devices { if device.DeviceID != id { continue } switch strings.ToLower(arg) { case "name": fmt.Println(device.Name) case "address": fmt.Println(strings.Join(device.Addresses, "\n")) case "compression": fmt.Println(device.Compression.String()) case "certname": fmt.Println(device.CertName) case "introducer": fmt.Println(device.Introducer) default: die("Invalid property: " + arg + "\nAvailable properties: name, address, compression, certname, introducer") } return } die("Device " + nid + " not found") }
func guiGet(c *cli.Context) { cfg := getConfig(c).GUI arg := c.Args()[0] switch strings.ToLower(arg) { case "enabled": fmt.Println(cfg.Enabled) case "tls": fmt.Println(cfg.UseTLS) case "address": fmt.Println(cfg.Address) case "user": if cfg.User != "" { fmt.Println(cfg.User) } case "password": if cfg.User != "" { fmt.Println(cfg.Password) } case "apikey": if cfg.APIKey != "" { fmt.Println(cfg.APIKey) } default: die("Invalid setting: " + arg + "\nAvailable settings: enabled, tls, address, user, password, apikey") } }
func optionsSet(c *cli.Context) { config := getConfig(c) arg := c.Args()[0] val := c.Args()[1] switch strings.ToLower(arg) { case "address": for _, item := range c.Args().Tail() { validAddress(item) } config.Options.ListenAddresses = c.Args().Tail() case "globalannenabled": config.Options.GlobalAnnEnabled = parseBool(val) case "globalannserver": for _, item := range c.Args().Tail() { validAddress(item) } config.Options.GlobalAnnServers = c.Args().Tail() case "localannenabled": config.Options.LocalAnnEnabled = parseBool(val) case "localannport": config.Options.LocalAnnPort = parsePort(val) case "maxsend": config.Options.MaxSendKbps = parseUint(val) case "maxrecv": config.Options.MaxRecvKbps = parseUint(val) case "reconnect": config.Options.ReconnectIntervalS = parseUint(val) case "browser": config.Options.StartBrowser = parseBool(val) case "nat": config.Options.NATEnabled = parseBool(val) case "natlease": config.Options.NATLeaseM = parseUint(val) case "natrenew": config.Options.NATRenewalM = parseUint(val) case "reporting": switch strings.ToLower(val) { case "u", "undecided", "unset": config.Options.URAccepted = 0 default: boolvalue := parseBool(val) if boolvalue { config.Options.URAccepted = 1 } else { config.Options.URAccepted = -1 } } case "wake": config.Options.RestartOnWakeup = parseBool(val) default: die("Invalid setting: " + arg + "\nAvailable settings: address, globalannenabled, globalannserver, localannenabled, localannport, maxsend, maxrecv, reconnect, browser, upnp, upnplease, upnprenew, reporting, wake") } setConfig(c, config) }
func foldersAdd(c *cli.Context) { cfg := getConfig(c) abs, err := filepath.Abs(c.Args()[1]) die(err) folder := config.FolderConfiguration{ ID: c.Args()[0], RawPath: filepath.Clean(abs), } cfg.Folders = append(cfg.Folders, folder) setConfig(c, cfg) }
func errorsPush(c *cli.Context) { err := strings.Join(c.Args(), " ") response := httpPost(c, "system/error", strings.TrimSpace(err)) if response.StatusCode != 200 { err = fmt.Sprint("Failed to push error\nStatus code: ", response.StatusCode) body := string(responseToBArray(response)) if body != "" { err += "\nBody: " + body } die(err) } }
func foldersDevicesClear(c *cli.Context) { rid := c.Args()[0] cfg := getConfig(c) for i, folder := range cfg.Folders { if folder.ID != rid { continue } cfg.Folders[i].Devices = []config.FolderDeviceConfiguration{} setConfig(c, cfg) return } die("Folder " + rid + " not found") }
func foldersDevicesAdd(c *cli.Context) { rid := c.Args()[0] nid := parseDeviceID(c.Args()[1]) cfg := getConfig(c) for i, folder := range cfg.Folders { if folder.ID != rid { continue } for _, device := range folder.Devices { if device.DeviceID == nid { die("Device " + c.Args()[1] + " is already part of this folder") } } for _, device := range cfg.Devices { if device.DeviceID == nid { cfg.Folders[i].Devices = append(folder.Devices, config.FolderDeviceConfiguration{ DeviceID: device.DeviceID, }) setConfig(c, cfg) return } } die("Device " + c.Args()[1] + " not found in device list") } die("Folder " + rid + " not found") }
func foldersUnset(c *cli.Context) { rid := c.Args()[0] arg := strings.ToLower(c.Args()[1]) cfg := getConfig(c) for i, folder := range cfg.Folders { if folder.ID != rid { continue } if strings.HasPrefix(arg, "versioning-") { arg = arg[11:] if _, ok := folder.Versioning.Params[arg]; ok { delete(cfg.Folders[i].Versioning.Params, arg) setConfig(c, cfg) return } die("Versioning property " + c.Args()[1][11:] + " not found") } switch arg { case "versioning": cfg.Folders[i].Versioning.Type = "" cfg.Folders[i].Versioning.Params = make(map[string]string) default: die("Invalid property: " + c.Args()[1] + "\nAvailable properties: versioning, versioning-<key>") } setConfig(c, cfg) return } die("Folder " + rid + " not found") }
func foldersSet(c *cli.Context) { rid := c.Args()[0] arg := strings.ToLower(c.Args()[1]) val := strings.Join(c.Args()[2:], " ") cfg := getConfig(c) for i, folder := range cfg.Folders { if folder.ID != rid { continue } if strings.HasPrefix(arg, "versioning-") { cfg.Folders[i].Versioning.Params[arg[11:]] = val setConfig(c, cfg) return } switch arg { case "directory": cfg.Folders[i].RawPath = val case "master": cfg.Folders[i].ReadOnly = parseBool(val) case "permissions": cfg.Folders[i].IgnorePerms = parseBool(val) case "rescan": cfg.Folders[i].RescanIntervalS = parseInt(val) case "versioning": cfg.Folders[i].Versioning.Type = val default: die("Invalid property: " + c.Args()[1] + "\nAvailable properties: directory, master, permissions, versioning, versioning-<key>") } setConfig(c, cfg) return } die("Folder " + rid + " not found") }
func foldersRemove(c *cli.Context) { cfg := getConfig(c) rid := c.Args()[0] for i, folder := range cfg.Folders { if folder.ID == rid { last := len(cfg.Folders) - 1 cfg.Folders[i] = cfg.Folders[last] cfg.Folders = cfg.Folders[:last] setConfig(c, cfg) return } } die("Folder " + rid + " not found") }
func foldersDevicesList(c *cli.Context) { rid := c.Args()[0] cfg := getConfig(c) for _, folder := range cfg.Folders { if folder.ID != rid { continue } for _, device := range folder.Devices { fmt.Println(device.DeviceID) } return } die("Folder " + rid + " not found") }
func guiUnset(c *cli.Context) { cfg := getConfig(c) arg := c.Args()[0] switch strings.ToLower(arg) { case "user": cfg.GUI.User = "" case "password": cfg.GUI.Password = "" case "apikey": cfg.GUI.APIKey = "" default: die("Invalid setting: " + arg + "\nAvailable settings: user, password, apikey") } setConfig(c, cfg) }
func optionsGet(c *cli.Context) { cfg := getConfig(c).Options arg := c.Args()[0] switch strings.ToLower(arg) { case "address": fmt.Println(strings.Join(cfg.ListenAddresses, "\n")) case "globalannenabled": fmt.Println(cfg.GlobalAnnEnabled) case "globalannservers": fmt.Println(strings.Join(cfg.GlobalAnnServers, "\n")) case "localannenabled": fmt.Println(cfg.LocalAnnEnabled) case "localannport": fmt.Println(cfg.LocalAnnPort) case "maxsend": fmt.Println(cfg.MaxSendKbps) case "maxrecv": fmt.Println(cfg.MaxRecvKbps) case "reconnect": fmt.Println(cfg.ReconnectIntervalS) case "browser": fmt.Println(cfg.StartBrowser) case "nat": fmt.Println(cfg.NATEnabled) case "natlease": fmt.Println(cfg.NATLeaseM) case "natrenew": fmt.Println(cfg.NATRenewalM) case "reporting": switch cfg.URAccepted { case -1: fmt.Println("false") case 0: fmt.Println("undecided/false") case 1: fmt.Println("true") default: fmt.Println("unknown") } case "wake": fmt.Println(cfg.RestartOnWakeup) default: die("Invalid setting: " + arg + "\nAvailable settings: address, globalannenabled, globalannserver, localannenabled, localannport, maxsend, maxrecv, reconnect, browser, upnp, upnplease, upnprenew, reporting, wake") } }
func devicesRemove(c *cli.Context) { nid := c.Args()[0] id := parseDeviceID(nid) if nid == getMyID(c) { die("Cannot remove yourself") } cfg := getConfig(c) for i, device := range cfg.Devices { if device.DeviceID == id { last := len(cfg.Devices) - 1 cfg.Devices[i] = cfg.Devices[last] cfg.Devices = cfg.Devices[:last] setConfig(c, cfg) return } } die("Device " + nid + " not found") }
func foldersOverride(c *cli.Context) { cfg := getConfig(c) rid := c.Args()[0] for _, folder := range cfg.Folders { if folder.ID == rid && folder.ReadOnly { response := httpPost(c, "db/override", "") if response.StatusCode != 200 { err := fmt.Sprint("Failed to override changes\nStatus code: ", response.StatusCode) body := string(responseToBArray(response)) if body != "" { err += "\nBody: " + body } die(err) } return } } die("Folder " + rid + " not found or folder not master") }
func guiSet(c *cli.Context) { cfg := getConfig(c) arg := c.Args()[0] val := c.Args()[1] switch strings.ToLower(arg) { case "enabled": cfg.GUI.Enabled = parseBool(val) case "tls": cfg.GUI.UseTLS = parseBool(val) case "address": validAddress(val) cfg.GUI.Address = val case "user": cfg.GUI.User = val case "password": cfg.GUI.Password = val case "apikey": cfg.GUI.APIKey = val default: die("Invalid setting: " + arg + "\nAvailable settings: enabled, tls, address, user, password, apikey") } setConfig(c, cfg) }
func foldersGet(c *cli.Context) { cfg := getConfig(c) rid := c.Args()[0] arg := strings.ToLower(c.Args()[1]) for _, folder := range cfg.Folders { if folder.ID != rid { continue } if strings.HasPrefix(arg, "versioning-") { arg = arg[11:] value, ok := folder.Versioning.Params[arg] if ok { fmt.Println(value) return } die("Versioning property " + c.Args()[1][11:] + " not found") } switch arg { case "directory": fmt.Println(folder.RawPath) case "master": fmt.Println(folder.ReadOnly) case "permissions": fmt.Println(folder.IgnorePerms) case "rescan": fmt.Println(folder.RescanIntervalS) case "versioning": if folder.Versioning.Type != "" { fmt.Println(folder.Versioning.Type) } default: die("Invalid property: " + c.Args()[1] + "\nAvailable properties: directory, master, permissions, versioning, versioning-<key>") } return } die("Folder " + rid + " not found") }
func foldersDevicesRemove(c *cli.Context) { rid := c.Args()[0] nid := parseDeviceID(c.Args()[1]) cfg := getConfig(c) for ri, folder := range cfg.Folders { if folder.ID != rid { continue } for ni, device := range folder.Devices { if device.DeviceID == nid { last := len(folder.Devices) - 1 cfg.Folders[ri].Devices[ni] = folder.Devices[last] cfg.Folders[ri].Devices = cfg.Folders[ri].Devices[:last] setConfig(c, cfg) return } } die("Device " + c.Args()[1] + " not found") } die("Folder " + rid + " not found") }
func devicesSet(c *cli.Context) { nid := c.Args()[0] id := parseDeviceID(nid) arg := c.Args()[1] config := getConfig(c) for i, device := range config.Devices { if device.DeviceID != id { continue } switch strings.ToLower(arg) { case "name": config.Devices[i].Name = strings.Join(c.Args()[2:], " ") case "address": for _, item := range c.Args()[2:] { if item == "dynamic" { continue } validAddress(item) } config.Devices[i].Addresses = c.Args()[2:] case "compression": err := config.Devices[i].Compression.UnmarshalText([]byte(c.Args()[2])) die(err) case "certname": config.Devices[i].CertName = strings.Join(c.Args()[2:], " ") case "introducer": config.Devices[i].Introducer = parseBool(c.Args()[2]) default: die("Invalid property: " + arg + "\nAvailable properties: name, address, compression, certname, introducer") } setConfig(c, config) return } die("Device " + nid + " not found") }
func getClient(c *cli.Context) *APIClient { if instance != nil { return instance } endpoint := c.GlobalString("endpoint") if !strings.HasPrefix(endpoint, "http") { endpoint = "http://" + endpoint } httpClient := http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: c.GlobalBool("insecure"), }, }, } client := APIClient{ httpClient: httpClient, endpoint: endpoint, apikey: c.GlobalString("apikey"), username: c.GlobalString("username"), password: c.GlobalString("password"), } if client.apikey == "" { request, err := http.NewRequest("GET", client.endpoint, nil) die(err) response := client.handleRequest(request) client.id = response.Header.Get("X-Syncthing-ID") if client.id == "" { die("Failed to get device ID") } for _, item := range response.Cookies() { if item.Name == "CSRF-Token-"+client.id[:5] { client.csrf = item.Value goto csrffound } } die("Failed to get CSRF token") csrffound: } instance = &client return &client } func (client *APIClient) handleRequest(request *http.Request) *http.Response { if client.apikey != "" { request.Header.Set("X-API-Key", client.apikey) } if client.username != "" || client.password != "" { request.SetBasicAuth(client.username, client.password) } if client.csrf != "" { request.Header.Set("X-CSRF-Token-"+client.id[:5], client.csrf) } response, err := client.httpClient.Do(request) die(err) if response.StatusCode == 404 { die("Invalid endpoint or API call") } else if response.StatusCode == 401 { die("Invalid username or password") } else if response.StatusCode == 403 { if client.apikey == "" { die("Invalid CSRF token") } die("Invalid API key") } else if response.StatusCode != 200 { body := strings.TrimSpace(string(responseToBArray(response))) if body != "" { die(body) } die("Unknown HTTP status returned: " + response.Status) } return response } func httpGet(c *cli.Context, url string) *http.Response { client := getClient(c) request, err := http.NewRequest("GET", client.endpoint+"/rest/"+url, nil) die(err) return client.handleRequest(request) } func httpPost(c *cli.Context, url string, body string) *http.Response { client := getClient(c) request, err := http.NewRequest("POST", client.endpoint+"/rest/"+url, bytes.NewBufferString(body)) die(err) return client.handleRequest(request) }