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")
}
Exemple #3
0
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")
	}
}
Exemple #4
0
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)
}
Exemple #6
0
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")
}
Exemple #13
0
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)
}
Exemple #14
0
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")
}
Exemple #17
0
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")
}
Exemple #21
0
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)
}